diff --git a/bin/pytop-0-0-1-x64.deb b/bin/pytop-0-0-1-x64.deb
new file mode 100644
index 0000000..e6ad1ca
Binary files /dev/null and b/bin/pytop-0-0-1-x64.deb differ
diff --git a/src/debs/build.sh b/src/debs/build.sh
new file mode 100644
index 0000000..48e85d7
--- /dev/null
+++ b/src/debs/build.sh
@@ -0,0 +1,34 @@
+#!/bin/bash
+
+# Fixes ownershp
+function main() {
+ sudo find . -type f -exec chmod 644 {} +
+ sudo find . -type d -exec chmod 755 {} +
+
+ # Set postrm permissions
+ for i in `find . -name postrm`; do
+ sudo chmod 755 "${i}"
+ done
+
+ # Set pytop permissions
+ for i in `find . -name pytop`; do
+ sudo chmod 755 "${i}"
+ done
+
+ sudo chown -R root:root ./*/
+
+ builder;
+ bash ./chownAll.sh
+}
+
+#builds debs
+function builder() {
+ for i in `ls`; do
+ if [[ -d "${i}" ]]; then
+ dpkg --build "${i}"
+ else
+ echo "Not a dir."
+ fi
+ done
+}
+main;
diff --git a/src/debs/chownAll.sh b/src/debs/chownAll.sh
new file mode 100644
index 0000000..44bef62
--- /dev/null
+++ b/src/debs/chownAll.sh
@@ -0,0 +1,6 @@
+#!/bin/bash
+
+function main() {
+ sudo chown -R abaddon:abaddon .
+}
+main;
diff --git a/src/debs/pyFfm-0-0-1-x64/DEBIAN/control b/src/debs/pyFfm-0-0-1-x64/DEBIAN/control
new file mode 100644
index 0000000..e22da34
--- /dev/null
+++ b/src/debs/pyFfm-0-0-1-x64/DEBIAN/control
@@ -0,0 +1,8 @@
+Package: pytop64
+Version: 0.0-1
+Section: python
+Priority: optional
+Architecture: amd64
+Depends: ffmpegthumbnailer (>= 2.0.10-0.1)
+Maintainer: Maxim Stewart <1itdominator@gmail.com>
+Description: Pytop is a custom desktop GUI.
diff --git a/src/debs/pyFfm-0-0-1-x64/DEBIAN/postrm b/src/debs/pyFfm-0-0-1-x64/DEBIAN/postrm
new file mode 100755
index 0000000..4962c4c
--- /dev/null
+++ b/src/debs/pyFfm-0-0-1-x64/DEBIAN/postrm
@@ -0,0 +1,11 @@
+#!/bin/bash
+#postrm (script executed after uninstalling the package)
+#set -e
+
+if [ -f /bin/pytop ]; then
+ rm /bin/pytop
+fi
+
+if [ -d /opt/Pytop ]; then
+ rm -rf /opt/Pytop
+fi
diff --git a/src/debs/pyFfm-0-0-1-x64/bin/pytop b/src/debs/pyFfm-0-0-1-x64/bin/pytop
new file mode 100755
index 0000000..9f3608f
Binary files /dev/null and b/src/debs/pyFfm-0-0-1-x64/bin/pytop differ
diff --git a/src/debs/pyFfm-0-0-1-x64/opt/PyFM/PyFM.py b/src/debs/pyFfm-0-0-1-x64/opt/PyFM/PyFM.py
new file mode 100644
index 0000000..99795ce
--- /dev/null
+++ b/src/debs/pyFfm-0-0-1-x64/opt/PyFM/PyFM.py
@@ -0,0 +1,36 @@
+#!/usr/bin/python3
+
+# Gtk Imports
+import gi, faulthandler
+gi.require_version('Gtk', '3.0')
+gi.require_version('WebKit2', '4.0')
+
+from gi.repository import Gtk as gtk
+from gi.repository import Gdk as gdk
+from gi.repository import WebKit2 as webkit
+
+# Python imports
+from utils import Settings, Events
+
+gdk.threads_init()
+class Main:
+ def __init__(self):
+ faulthandler.enable()
+ webkit.WebView() # Needed for glade file to load...
+
+ self.builder = gtk.Builder()
+ self.settings = Settings()
+ self.settings.attachBuilder(self.builder)
+ self.builder.connect_signals(Events(self.settings))
+
+ window = self.settings.createWindow()
+ window.fullscreen()
+ window.show_all()
+
+
+if __name__ == "__main__":
+ try:
+ main = Main()
+ gtk.main()
+ except Exception as e:
+ print(e)
diff --git a/src/debs/pyFfm-0-0-1-x64/opt/PyFM/PyFM.sh b/src/debs/pyFfm-0-0-1-x64/opt/PyFM/PyFM.sh
new file mode 100644
index 0000000..de59af8
--- /dev/null
+++ b/src/debs/pyFfm-0-0-1-x64/opt/PyFM/PyFM.sh
@@ -0,0 +1,12 @@
+#!/bin/bash
+
+# set -o xtrace ## To debug scripts
+# set -o errexit ## To exit on error
+# set -o errunset ## To exit if a variable is referenced but not set
+
+
+function main() {
+ # GTK_DEBUG=interactive python3 ./PyFM.py
+ python3 ./PyFM.py
+}
+main $@;
diff --git a/src/debs/pyFfm-0-0-1-x64/opt/PyFM/resources/PyTop.glade b/src/debs/pyFfm-0-0-1-x64/opt/PyFM/resources/PyTop.glade
new file mode 100644
index 0000000..d2d2d35
--- /dev/null
+++ b/src/debs/pyFfm-0-0-1-x64/opt/PyFM/resources/PyTop.glade
@@ -0,0 +1,305 @@
+
+
+
+
+
+
+
+
+
+ False
+ popOutBttn
+ bottom
+
+
+ True
+ False
+ vertical
+
+
+ 300
+ 26
+ True
+ True
+ gtk-edit
+
+
+ False
+ True
+ 0
+
+
+
+
+ True
+ False
+
+
+ gtk-copy
+ True
+ True
+ True
+ True
+ True
+
+
+ False
+ True
+ 0
+
+
+
+
+ gtk-cut
+ True
+ True
+ True
+ True
+ True
+
+
+ False
+ True
+ 1
+
+
+
+
+ gtk-paste
+ True
+ True
+ True
+ True
+ True
+
+
+ False
+ True
+ 2
+
+
+
+
+ gtk-delete
+ True
+ True
+ True
+ 65
+ True
+ True
+
+
+ False
+ True
+ end
+ 3
+
+
+
+
+ False
+ True
+ 1
+
+
+
+
+
+
+ False
+ True
+ True
+ popOutBttn
+ bottom
+
+
+ True
+ False
+ vertical
+
+
+ True
+ False
+
+
+ gtk-home
+ True
+ True
+ True
+ True
+ True
+
+
+
+ False
+ True
+ 0
+
+
+
+
+ gtk-refresh
+ True
+ True
+ True
+ True
+ True
+
+
+
+ False
+ True
+ 1
+
+
+
+
+ True
+ True
+ edit-find-symbolic
+ False
+ False
+
+
+
+ True
+ True
+ 2
+
+
+
+
+ False
+ True
+ 0
+
+
+
+
+ True
+ True
+
+
+
+
+
+
+ False
+ True
+ 1
+
+
+
+
+
+
diff --git a/src/debs/pyFfm-0-0-1-x64/opt/PyFM/resources/icons/archive.png b/src/debs/pyFfm-0-0-1-x64/opt/PyFM/resources/icons/archive.png
new file mode 100644
index 0000000..7943e4e
Binary files /dev/null and b/src/debs/pyFfm-0-0-1-x64/opt/PyFM/resources/icons/archive.png differ
diff --git a/src/debs/pyFfm-0-0-1-x64/opt/PyFM/resources/icons/audio.png b/src/debs/pyFfm-0-0-1-x64/opt/PyFM/resources/icons/audio.png
new file mode 100644
index 0000000..c010134
Binary files /dev/null and b/src/debs/pyFfm-0-0-1-x64/opt/PyFM/resources/icons/audio.png differ
diff --git a/src/debs/pyFfm-0-0-1-x64/opt/PyFM/resources/icons/bin.png b/src/debs/pyFfm-0-0-1-x64/opt/PyFM/resources/icons/bin.png
new file mode 100644
index 0000000..d6954e3
Binary files /dev/null and b/src/debs/pyFfm-0-0-1-x64/opt/PyFM/resources/icons/bin.png differ
diff --git a/src/debs/pyFfm-0-0-1-x64/opt/PyFM/resources/icons/dir.png b/src/debs/pyFfm-0-0-1-x64/opt/PyFM/resources/icons/dir.png
new file mode 100644
index 0000000..a9b5e9f
Binary files /dev/null and b/src/debs/pyFfm-0-0-1-x64/opt/PyFM/resources/icons/dir.png differ
diff --git a/src/debs/pyFfm-0-0-1-x64/opt/PyFM/resources/icons/doc.png b/src/debs/pyFfm-0-0-1-x64/opt/PyFM/resources/icons/doc.png
new file mode 100644
index 0000000..f838826
Binary files /dev/null and b/src/debs/pyFfm-0-0-1-x64/opt/PyFM/resources/icons/doc.png differ
diff --git a/src/debs/pyFfm-0-0-1-x64/opt/PyFM/resources/icons/pdf.png b/src/debs/pyFfm-0-0-1-x64/opt/PyFM/resources/icons/pdf.png
new file mode 100644
index 0000000..9f40122
Binary files /dev/null and b/src/debs/pyFfm-0-0-1-x64/opt/PyFM/resources/icons/pdf.png differ
diff --git a/src/debs/pyFfm-0-0-1-x64/opt/PyFM/resources/icons/presentation.png b/src/debs/pyFfm-0-0-1-x64/opt/PyFM/resources/icons/presentation.png
new file mode 100644
index 0000000..3a339af
Binary files /dev/null and b/src/debs/pyFfm-0-0-1-x64/opt/PyFM/resources/icons/presentation.png differ
diff --git a/src/debs/pyFfm-0-0-1-x64/opt/PyFM/resources/icons/spreadsheet.png b/src/debs/pyFfm-0-0-1-x64/opt/PyFM/resources/icons/spreadsheet.png
new file mode 100644
index 0000000..710efa6
Binary files /dev/null and b/src/debs/pyFfm-0-0-1-x64/opt/PyFM/resources/icons/spreadsheet.png differ
diff --git a/src/debs/pyFfm-0-0-1-x64/opt/PyFM/resources/icons/text.png b/src/debs/pyFfm-0-0-1-x64/opt/PyFM/resources/icons/text.png
new file mode 100644
index 0000000..2546fcd
Binary files /dev/null and b/src/debs/pyFfm-0-0-1-x64/opt/PyFM/resources/icons/text.png differ
diff --git a/src/debs/pyFfm-0-0-1-x64/opt/PyFM/resources/icons/video.png b/src/debs/pyFfm-0-0-1-x64/opt/PyFM/resources/icons/video.png
new file mode 100644
index 0000000..55afa98
Binary files /dev/null and b/src/debs/pyFfm-0-0-1-x64/opt/PyFM/resources/icons/video.png differ
diff --git a/src/debs/pyFfm-0-0-1-x64/opt/PyFM/resources/icons/web.png b/src/debs/pyFfm-0-0-1-x64/opt/PyFM/resources/icons/web.png
new file mode 100644
index 0000000..17017ce
Binary files /dev/null and b/src/debs/pyFfm-0-0-1-x64/opt/PyFM/resources/icons/web.png differ
diff --git a/src/debs/pyFfm-0-0-1-x64/opt/PyFM/resources/stylesheet.css b/src/debs/pyFfm-0-0-1-x64/opt/PyFM/resources/stylesheet.css
new file mode 100644
index 0000000..9addcfa
--- /dev/null
+++ b/src/debs/pyFfm-0-0-1-x64/opt/PyFM/resources/stylesheet.css
@@ -0,0 +1,88 @@
+viewport,
+treeview,
+treeview > header,
+notebook > stack,
+notebook > header {
+ background-color: rgba(0, 0, 0, 0.24);
+}
+
+
+notebook > header {
+ background-color: rgba(0, 0, 0, 0.24);
+ border-color: rgba(0, 232, 255, 0.64);
+}
+
+box,
+iconview {
+ background-color: rgba(0, 0, 0, 0.2);
+ background: rgba(0, 0, 0, 0.2);
+}
+
+treeview,
+treeview.view {
+ background: rgba(0, 0, 0, 0.2);
+ background-color: rgba(0, 0, 0, 0.2);
+}
+
+cell {
+ margin: 0em;
+ padding: 0em;
+ /* float: left; */
+}
+
+cell:focus {
+ outline-style: solid;
+ outline-color: rgba(0, 232, 255, 0.64);
+}
+
+
+/* Ivonview and children default color */
+.view {
+ background-color: rgba(0, 0, 0, 0.22);
+ color: #ebebeb;
+}
+
+
+/* Hover over color when not selected */
+.view:hover {
+ box-shadow: inset 0 0 0 9999px alpha(rgba(0, 232, 255, 0.64), 0.54);
+}
+
+/* Handles the icon selection hover and selected hover color. */
+.view:selected,
+.view:selected:hover {
+ box-shadow: inset 0 0 0 9999px rgba(15, 134, 13, 0.49);
+}
+
+/* Rubberband coloring */
+.rubberband,
+rubberband,
+flowbox rubberband,
+treeview.view rubberband,
+.content-view rubberband,
+.content-view .rubberband,
+XfdesktopIconView.view .rubberband {
+ border: 1px solid #6c6c6c;
+ background-color: rgba(21, 158, 167, 0.57);
+}
+
+XfdesktopIconView.view:active {
+ background-color: rgba(172, 102, 21, 1);
+}
+
+
+XfdesktopIconView.view {
+ border-radius: 4px;
+ background-color: transparent;
+ color: white;
+ text-shadow: 0 1px 1px rgba(0, 0, 0, 0.12), 0 1px 2px rgba(0, 0, 0, 0.24);
+}
+
+XfdesktopIconView.view:active {
+ box-shadow: none;
+ text-shadow: none;
+}
+
+XfdesktopIconView.view .rubberband {
+ border-radius: 0;
+}
diff --git a/src/debs/pyFfm-0-0-1-x64/opt/PyFM/utils/Dragging.py b/src/debs/pyFfm-0-0-1-x64/opt/PyFM/utils/Dragging.py
new file mode 100644
index 0000000..a0b2856
--- /dev/null
+++ b/src/debs/pyFfm-0-0-1-x64/opt/PyFM/utils/Dragging.py
@@ -0,0 +1,79 @@
+import os, gi
+
+gi.require_version('Gdk', '3.0')
+
+from gi.repository import Gdk
+from gi.repository import GObject
+
+
+class Dragging:
+ def __init__(self):
+ # higher values make movement more performant
+ # lower values make movement smoother
+ self.SENSITIVITY = 1
+ self.desktop = None
+ self.EvMask = Gdk.EventMask.BUTTON_PRESS_MASK | Gdk.EventMask.BUTTON1_MOTION_MASK
+ self.offsetx = 0
+ self.offsety = 0
+ self.px = 0
+ self.py = 0
+ self.maxx = 0
+ self.maxy = 0
+
+ def connectEvents(self, desktop, widget):
+ self.desktop = desktop
+ widget.set_events(self.EvMask)
+ widget.connect("button_press_event", self.press_event)
+ widget.connect("motion_notify_event", self.draggingEvent)
+ widget.show()
+
+ def press_event(self, w, event):
+ if event.button == 1:
+ p = w.get_parent()
+ # offset == distance of parent widget from edge of screen ...
+ self.offsetx, self.offsety = p.get_window().get_position()
+ # plus distance from pointer to edge of widget
+ self.offsetx += event.x
+ self.offsety += event.y
+ # self.maxx, self.maxy both relative to the parent
+ # note that we're rounding down now so that these max values don't get
+ # rounded upward later and push the widget off the edge of its parent.
+ self.maxx = self.RoundDownToMultiple(p.get_allocation().width - w.get_allocation().width, self.SENSITIVITY)
+ self.maxy = self.RoundDownToMultiple(p.get_allocation().height - w.get_allocation().height, self.SENSITIVITY)
+
+
+ def draggingEvent(self, widget, event):
+ # x_root,x_root relative to screen
+ # x,y relative to parent (fixed widget)
+ # self.px,self.py stores previous values of x,y
+
+ # get starting values for x,y
+ x = event.x_root - self.offsetx
+ y = event.y_root - self.offsety
+ # make sure the potential coordinates x,y:
+ # 1) will not push any part of the widget outside of its parent container
+ # 2) is a multiple of self.SENSITIVITY
+ x = self.RoundToNearestMultiple(self.Max(self.Min(x, self.maxx), 0), self.SENSITIVITY)
+ y = self.RoundToNearestMultiple(self.Max(self.Min(y, self.maxy), 0), self.SENSITIVITY)
+ if x != self.px or y != self.py:
+ self.px = x
+ self.py = y
+ self.desktop.move(widget, x, y)
+
+ def Min(self, a, b):
+ if b < a:
+ return b
+ return a
+
+ def Max(self, a, b):
+ if b > a:
+ return b
+ return a
+
+ def RoundDownToMultiple(self, i, m):
+ return i/m*m
+
+ def RoundToNearestMultiple(self, i, m):
+ if i % m > m / 2:
+ return (i/m+1)*m
+ return i/m*m
diff --git a/src/debs/pyFfm-0-0-1-x64/opt/PyFM/utils/Events.py b/src/debs/pyFfm-0-0-1-x64/opt/PyFM/utils/Events.py
new file mode 100644
index 0000000..fbd3962
--- /dev/null
+++ b/src/debs/pyFfm-0-0-1-x64/opt/PyFM/utils/Events.py
@@ -0,0 +1,72 @@
+
+# Gtk Imports
+
+# Python imports
+from .Grid import Grid
+from .Dragging import Dragging
+
+class Events:
+ def __init__(self, settings):
+ self.settings = settings
+ self.builder = self.settings.returnBuilder()
+ self.desktop = self.builder.get_object("Desktop")
+ self.webview = self.builder.get_object("webview")
+ self.desktopPath = self.settings.returnDesktopPath()
+
+ self.settings.setDefaultWebviewSettings(self.webview, self.webview.get_settings())
+ self.webview.load_uri(self.settings.returnWebHome())
+
+ # Add filter to allow only folders to be selected
+ selectedDirDialog = self.builder.get_object("selectedDirDialog")
+ filefilter = self.builder.get_object("Folders")
+ selectedDirDialog.add_filter(filefilter)
+ selectedDirDialog.set_filename(self.desktopPath)
+
+ self.grid = None
+ self.setIconViewDir(selectedDirDialog)
+
+ def setIconViewDir(self, widget, data=None):
+ newPath = widget.get_filename()
+ Grid(self.desktop, self.settings, newPath)
+
+
+
+ # File control events
+ def createFile(self):
+ pass
+
+ def updateFile(self, widget, data=None):
+ newName = widget.get_text().strip()
+ if data and data.keyval == 65293: # Enter key event
+ self.grid.updateFile(newName)
+ elif data == None: # Save button 'event'
+ self.grid.updateFile(newName)
+
+ def deleteFile(self, widget, data=None):
+ self.grid.deleteFile()
+
+ def copyFile(self):
+ pass
+
+ def cutFile(self):
+ pass
+
+ def pasteFile(self):
+ pass
+
+ # Webview events
+ def showWebview(self, widget):
+ self.builder.get_object("webViewer").popup()
+
+ def loadHome(self, widget):
+ self.webview.load_uri(self.settings.returnWebHome())
+
+ def runSearchWebview(self, widget, data=None):
+ if data.keyval == 65293:
+ self.webview.load_uri(widget.get_text().strip())
+
+ def refreshPage(self, widget, data=None):
+ self.webview.load_uri(self.webview.get_uri())
+
+ def setUrlBar(self, widget, data=None):
+ self.builder.get_object("webviewSearch").set_text(widget.get_uri())
diff --git a/src/debs/pyFfm-0-0-1-x64/opt/PyFM/utils/FileHandler.py b/src/debs/pyFfm-0-0-1-x64/opt/PyFM/utils/FileHandler.py
new file mode 100644
index 0000000..c4dfa3b
--- /dev/null
+++ b/src/debs/pyFfm-0-0-1-x64/opt/PyFM/utils/FileHandler.py
@@ -0,0 +1,93 @@
+
+import os, shutil, subprocess, threading
+
+
+def threaded(fn):
+ def wrapper(*args, **kwargs):
+ threading.Thread(target=fn, args=args, kwargs=kwargs).start()
+ return wrapper
+
+class FileHandler:
+ def __init__(self):
+ # 'Filters'
+ self.office = ('.doc', '.docx', '.xls', '.xlsx', '.xlt', '.xltx' '.xlm', '.ppt', 'pptx', '.pps', '.ppsx', '.odt', '.rtf')
+ self.vids = ('.mkv', '.avi', '.flv', '.mov', '.m4v', '.mpg', '.wmv', '.mpeg', '.mp4', '.webm')
+ self.txt = ('.txt', '.text', '.sh', '.cfg', '.conf')
+ self.music = ('.psf', '.mp3', '.ogg' , '.flac')
+ self.images = ('.png', '.jpg', '.jpeg', '.gif')
+ self.pdf = ('.pdf')
+
+ # Args
+ self.MEDIAPLAYER = "mpv";
+ self.IMGVIEWER = "mirage";
+ self.MUSICPLAYER = "/opt/deadbeef/bin/deadbeef";
+ self.OFFICEPROG = "libreoffice";
+ self.TEXTVIEWER = "leafpad";
+ self.PDFVIEWER = "evince";
+ self.FILEMANAGER = "spacefm";
+ self.MPLAYER_WH = " -xy 1600 -geometry 50%:50% ";
+ self.MPV_WH = " -geometry 50%:50% ";
+
+ @threaded
+ def openFile(self, file):
+ print("Opening: " + file)
+ if file.lower().endswith(self.vids):
+ subprocess.Popen([self.MEDIAPLAYER, self.MPV_WH, file])
+ elif file.lower().endswith(self.music):
+ subprocess.Popen([self.MUSICPLAYER, file])
+ elif file.lower().endswith(self.images):
+ subprocess.Popen([self.IMGVIEWER, file])
+ elif file.lower().endswith(self.txt):
+ subprocess.Popen([self.TEXTVIEWER, file])
+ elif file.lower().endswith(self.pdf):
+ subprocess.Popen([self.PDFVIEWER, file])
+ elif file.lower().endswith(self.office):
+ subprocess.Popen([self.OFFICEPROG, file])
+ else:
+ subprocess.Popen(['xdg-open', file])
+
+
+ def createFile(self, newFileName):
+ pass
+
+ def updateFile(self, oldFileName, newFileName):
+ try:
+ print("Renaming...")
+ print(oldFileName + " --> " + newFileName)
+ os.rename(oldFileName, newFileName)
+ return 0
+ except Exception as e:
+ print("An error occured renaming the file:")
+ print(e)
+ return 1
+
+ def deleteFile(self, toDeleteFile):
+ try:
+ print("Deleting...")
+ print(toDeleteFile)
+ if os.path.exists(toDeleteFile):
+ if os.path.isfile(toDeleteFile):
+ os.remove(toDeleteFile)
+ elif os.path.isdir(toDeleteFile):
+ shutil.rmtree(toDeleteFile)
+ else:
+ print("An error occured deleting the file:")
+ return 1
+ else:
+ print("The folder/file does not exist")
+ return 1
+ except Exception as e:
+ print("An error occured deleting the file:")
+ print(e)
+ return 1
+
+ return 0
+
+ def copyFile(self):
+ pass
+
+ def cutFile(self):
+ pass
+
+ def pasteFile(self):
+ pass
diff --git a/src/debs/pyFfm-0-0-1-x64/opt/PyFM/utils/Grid.py b/src/debs/pyFfm-0-0-1-x64/opt/PyFM/utils/Grid.py
new file mode 100644
index 0000000..392de55
--- /dev/null
+++ b/src/debs/pyFfm-0-0-1-x64/opt/PyFM/utils/Grid.py
@@ -0,0 +1,214 @@
+
+
+# Gtk Imports
+import gi
+gi.require_version('Gtk', '3.0')
+gi.require_version('Gdk', '3.0')
+
+from gi.repository import Gtk as gtk
+from gi.repository import Gdk as gdk
+from gi.repository import GLib as glib
+from gi.repository import GdkPixbuf
+
+# Python imports
+import os, threading, time
+from os.path import isdir, isfile, join
+from os import listdir
+from .Icon import Icon
+from .FileHandler import FileHandler
+
+
+def threaded(fn):
+ def wrapper(*args, **kwargs):
+ threading.Thread(target=fn, args=args, kwargs=kwargs).start()
+ return wrapper
+
+class Grid:
+ def __init__(self, desktop, settings, newPath):
+ self.desktop = desktop
+ self.settings = settings
+ self.filehandler = FileHandler()
+
+ self.store = gtk.ListStore(GdkPixbuf.Pixbuf, str)
+ self.usrHome = settings.returnUserHome()
+ self.builder = settings.returnBuilder()
+ self.ColumnSize = settings.returnColumnSize()
+ self.currentPath = ""
+ self.selectedFile = ""
+
+ self.desktop.set_model(self.store)
+ self.desktop.set_pixbuf_column(0)
+ self.desktop.set_text_column(1)
+ self.desktop.connect("item-activated", self.iconLeftClickEventManager)
+ self.desktop.connect("button_press_event", self.iconRightClickEventManager, (self.desktop,))
+ self.desktop.connect("selection-changed", self.setIconSelectionArray, (self.desktop,))
+
+ self.vidsList = settings.returnVidsExtensionList()
+ self.imagesList = settings.returnImagesExtensionList()
+ self.gtkLock = False # Thread checks for gtkLock
+ self.threadLock = False # Gtk checks for thread lock
+ self.helperThread = None # Helper thread object
+ self.toWorkPool = [] # Thread fills pool and gtk empties it
+ self.copyCutArry = []
+
+ self.setIconViewDir(newPath)
+
+ def setIconViewDir(self, path):
+ self.store.clear()
+
+ self.currentPath = path
+ dirPaths = ['.', '..']
+ vids = []
+ images = []
+ desktop = []
+ files = []
+
+ for f in listdir(path):
+ file = join(path, f)
+ if self.settings.isHideHiddenFiles():
+ if f.startswith('.'):
+ continue
+ if isfile(file):
+ if file.lower().endswith(self.vidsList):
+ vids.append(f)
+ elif file.lower().endswith(self.imagesList):
+ images.append(f)
+ elif file.lower().endswith((".desktop",)):
+ desktop.append(f)
+ else:
+ files.append(f)
+ else:
+ dirPaths.append(f)
+
+ dirPaths.sort()
+ vids.sort()
+ images.sort()
+ desktop.sort()
+ files.sort()
+ files = dirPaths + vids + images + desktop + files
+
+ if self.helperThread:
+ self.helperThread.terminate()
+ self.helperThread = None
+
+ # Run helper thread...
+ self.threadLock = True
+ self.helperThread = threading.Thread(target=self.generateDirectoryGridIcon, args=(path, files)).start()
+ glib.idle_add(self.addToGrid, (file,)) # This must stay in the main thread b/c
+ # gtk isn't thread safe/aware So, we
+ # make a sad lil thread hot potato 'game'
+ # out of this process.
+
+
+ # @threaded
+ def generateDirectoryGridIcon(self, dirPath, files):
+ # NOTE: We'll be passing pixbuf after retreval to keep Icon.py file more
+ # universaly usable. We can just remove get_pixbuf to get a gtk.Image type
+ for file in files:
+ image = Icon(self.settings).createIcon(dirPath, file)
+ self.toWorkPool.append([image.get_pixbuf(), file])
+ self.threadLock = False
+ self.gtkLock = True
+
+
+ def addToGrid(self, args):
+ # NOTE: Returning true tells gtk to check again in the future when idle.
+ # False ends checks and "continues normal flow"
+ files = args[0]
+
+ if len(self.toWorkPool) > 0:
+ for dataSet in self.toWorkPool:
+ self.store.append(dataSet)
+
+ if len(self.store) == len(files): # Confirm processed all files and cleanup
+ self.gtkLock = False
+ self.threadLock = False
+ self.toWorkPool.clear()
+ return False
+ # Check again when idle; If nothing else is updating, this function
+ # gets called immediatly. So, we play hot potato by passing lock to Thread
+ else:
+ self.toWorkPool.clear()
+ self.gtkLock = False
+ self.threadLock = True
+ time.sleep(.005) # Fixes refresh and up icon not being added.
+ return True
+
+ def setIconSelectionArray(self, widget, data=None):
+ pass
+ # os.system('cls||clear')
+ # print(data)
+
+ def iconLeftClickEventManager(self, widget, item):
+ try:
+ model = widget.get_model()
+ fileName = model[item][1]
+ dir = self.currentPath
+ file = dir + "/" + fileName
+
+ if fileName == ".":
+ self.setIconViewDir(dir)
+ elif fileName == "..":
+ parentDir = os.path.abspath(os.path.join(dir, os.pardir))
+ self.currentPath = parentDir
+ self.setIconViewDir(parentDir)
+ elif isdir(file):
+ self.currentPath = file
+ self.setIconViewDir(self.currentPath)
+ elif isfile(file):
+ self.filehandler.openFile(file)
+ except Exception as e:
+ print(e)
+
+ def iconRightClickEventManager(self, widget, eve, params):
+ try:
+ if eve.type == gdk.EventType.BUTTON_PRESS and eve.button == 3:
+ popover = self.builder.get_object("iconControlsWindow")
+ popover.show_all()
+ popover.popup()
+ # # NOTE: Need to change name of listview box...
+ # children = widget.get_children()[0].get_children()
+ # fileName = children[1].get_text()
+ # dir = self.currentPath
+ # file = dir + "/" + fileName
+ #
+ # input = self.builder.get_object("iconRenameInput")
+ # popover = self.builder.get_object("iconControlsWindow")
+ # self.selectedFile = file # Used for return to caller
+ #
+ # input.set_text(fileName)
+ # popover.set_relative_to(widget)
+ # popover.set_position(gtk.PositionType.RIGHT)
+ # popover.show_all()
+ # popover.popup()
+ except Exception as e:
+ print(e)
+
+
+ # Passthrough file control events
+ def createFile(arg):
+ pass
+
+ def updateFile(self, file):
+ newName = self.currentPath + "/" + file
+ status = self.filehandler.updateFile(self.selectedFile, newName)
+
+ if status == 0:
+ self.selectedFile = newName
+ self.setIconViewDir(self.currentPath)
+
+ def deleteFile(self):
+ status = self.filehandler.deleteFile(self.selectedFile)
+
+ if status == 0:
+ self.selectedFile = ""
+ self.setIconViewDir(self.currentPath)
+
+ def copyFile(self):
+ pass
+
+ def cutFile(self):
+ pass
+
+ def pasteFile(self):
+ pass
diff --git a/src/debs/pyFfm-0-0-1-x64/opt/PyFM/utils/Icon.py b/src/debs/pyFfm-0-0-1-x64/opt/PyFM/utils/Icon.py
new file mode 100644
index 0000000..826e408
--- /dev/null
+++ b/src/debs/pyFfm-0-0-1-x64/opt/PyFM/utils/Icon.py
@@ -0,0 +1,167 @@
+
+# Gtk Imports
+import gi
+gi.require_version('Gtk', '3.0')
+gi.require_version('Gdk', '3.0')
+
+from gi.repository import Gtk as gtk
+from gi.repository import Gio as gio
+from gi.repository import GdkPixbuf
+from xdg.DesktopEntry import DesktopEntry
+
+# Python Imports
+import os, subprocess, hashlib, threading
+
+from os.path import isdir, isfile, join
+
+
+
+def threaded(fn):
+ def wrapper(*args, **kwargs):
+ threading.Thread(target=fn, args=args, kwargs=kwargs).start()
+ return wrapper
+
+class Icon:
+ def __init__(self, settings):
+ self.settings = settings
+ self.thubnailGen = settings.getThumbnailGenerator()
+ self.vidsList = settings.returnVidsExtensionList()
+ self.imagesList = settings.returnImagesExtensionList()
+ self.GTK_ORIENTATION = settings.returnIconImagePos()
+ self.usrHome = settings.returnUserHome()
+ self.iconContainerWH = settings.returnContainerWH()
+ self.systemIconImageWH = settings.returnSystemIconImageWH()
+ self.viIconWH = settings.returnVIIconWH()
+
+
+ def createIcon(self, dir, file):
+ fullPath = dir + "/" + file
+ return self.getIconImage(file, fullPath)
+
+
+ def getIconImage(self, file, fullPath):
+ try:
+ thumbnl = None
+
+ # Video thumbnail
+ if file.lower().endswith(self.vidsList):
+ fileHash = hashlib.sha256(str.encode(fullPath)).hexdigest()
+ hashImgPth = self.usrHome + "/.thumbnails/normal/" + fileHash + ".png"
+
+ if isfile(hashImgPth) == False:
+ self.generateVideoThumbnail(fullPath, hashImgPth)
+
+ thumbnl = self.createIconImageBuffer(hashImgPth, self.viIconWH)
+ # Image Icon
+ elif file.lower().endswith(self.imagesList):
+ thumbnl = self.createIconImageBuffer(fullPath, self.viIconWH)
+ # .desktop file parsing
+ elif fullPath.lower().endswith( ('.desktop',) ):
+ thumbnl = self.parseDesktopFiles(fullPath)
+ # System icons
+ else:
+ thumbnl = self.getSystemThumbnail(fullPath, self.systemIconImageWH[0])
+
+ if thumbnl == None: # If no icon, try stock file icon...
+ thumbnl = gtk.Image.new_from_icon_name("gtk-file", gtk.IconSize.LARGE_TOOLBAR)
+
+ if thumbnl == None: # If no icon whatsoever, return internal default
+ thumbnl = gtk.Image.new_from_file("resources/icons/bin.png")
+
+ return thumbnl
+ except Exception as e:
+ print(e)
+ return gtk.Image.new_from_file("resources/icons/bin.png")
+
+
+ def parseDesktopFiles(self, fullPath):
+ try:
+ xdgObj = DesktopEntry(fullPath)
+ icon = xdgObj.getIcon()
+ iconsDirs = "/usr/share/icons"
+ altIconPath = ""
+
+ if "steam" in icon:
+ steamIconsDir = self.usrHome + "/.thumbnails/steam_icons/"
+ name = xdgObj.getName()
+ fileHash = hashlib.sha256(str.encode(name)).hexdigest()
+
+ if isdir(steamIconsDir) == False:
+ os.mkdir(steamIconsDir)
+
+ hashImgPth = steamIconsDir + fileHash + ".jpg"
+ if isfile(hashImgPth) == True:
+ # Use video sizes since headers are bigger
+ return self.createIconImageBuffer(hashImgPth, self.viIconWH)
+
+ execStr = xdgObj.getExec()
+ parts = execStr.split("steam://rungameid/")
+ id = parts[len(parts) - 1]
+
+ # NOTE: Can try this logic instead...
+ # if command exists use it instead of header image
+ # if "steamcmd app_info_print id":
+ # proc = subprocess.Popen(["steamcmd", "app_info_print", id])
+ # proc.wait()
+ # else:
+ # use the bottom logic
+
+ imageLink = "https://steamcdn-a.akamaihd.net/steam/apps/" + id + "/header.jpg"
+ proc = subprocess.Popen(["wget", "-O", hashImgPth, imageLink])
+ proc.wait()
+
+ # Use video sizes since headers are bigger
+ return self.createIconImageBuffer(hashImgPth, self.viIconWH)
+ elif os.path.exists(icon):
+ return self.createIconImageBuffer(icon, self.systemIconImageWH)
+ else:
+ for (dirpath, dirnames, filenames) in os.walk(iconsDirs):
+ for file in filenames:
+ appNM = "application-x-" + icon
+ if appNM in file:
+ altIconPath = dirpath + "/" + file
+ break
+
+ return self.createIconImageBuffer(altIconPath, self.systemIconImageWH)
+ except Exception as e:
+ print(e)
+ return None
+
+
+ def getSystemThumbnail(self, filename, size):
+ try:
+ iconPath = None
+ if os.path.exists(filename):
+ file = gio.File.new_for_path(filename)
+ info = file.query_info('standard::icon' , 0 , gio.Cancellable())
+ icon = info.get_icon().get_names()[0]
+ iconTheme = gtk.IconTheme.get_default()
+ iconFile = iconTheme.lookup_icon(icon , size , 0)
+
+ if iconFile != None:
+ iconPath = iconFile.get_filename()
+ return self.createIconImageBuffer(iconPath, self.systemIconImageWH)
+ else:
+ return None
+ else:
+ return None
+ except Exception as e:
+ print(e)
+ return None
+
+
+ def createIconImageBuffer(self, path, wxh):
+ try:
+ pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_scale(path, wxh[0], wxh[1], False)
+ except Exception as e:
+ return None
+
+ return gtk.Image.new_from_pixbuf(pixbuf)
+
+
+ def generateVideoThumbnail(self, fullPath, hashImgPth):
+ try:
+ proc = subprocess.Popen([self.thubnailGen, "-t", "65%", "-s", "300", "-c", "jpg", "-i", fullPath, "-o", hashImgPth])
+ proc.wait()
+ except Exception as e:
+ print(e)
diff --git a/src/debs/pyFfm-0-0-1-x64/opt/PyFM/utils/Settings.py b/src/debs/pyFfm-0-0-1-x64/opt/PyFM/utils/Settings.py
new file mode 100644
index 0000000..6c612f5
--- /dev/null
+++ b/src/debs/pyFfm-0-0-1-x64/opt/PyFM/utils/Settings.py
@@ -0,0 +1,139 @@
+
+# Gtk Imports
+import gi, cairo, os
+gi.require_version('Gtk', '3.0')
+gi.require_version('Gdk', '3.0')
+
+from gi.repository import Gtk as gtk
+from gi.repository import Gdk as gdk
+
+class Settings:
+ def __init__(self):
+ self.builder = None
+ self.hideHiddenFiles = True
+
+
+ self.GTK_ORIENTATION = 1 # HORIZONTAL (0) VERTICAL (1)
+ self.THUMB_GENERATOR = "ffmpegthumbnailer"
+ self.DEFAULTCOLOR = gdk.RGBA(0.0, 0.0, 0.0, 0.0) # ~#00000000
+ self.MOUSEOVERCOLOR = gdk.RGBA(0.0, 0.9, 1.0, 0.64) # ~#00e8ff
+ self.SELECTEDCOLOR = gdk.RGBA(0.4, 0.5, 0.1, 0.84)
+
+ self.ColumnSize = 8
+ self.usrHome = os.path.expanduser('~')
+ self.desktopPath = self.usrHome + "/Desktop"
+ self.webHome = 'http://webfm.com/'
+ self.iconContainerWxH = [128, 128]
+ self.systemIconImageWxH = [72, 72]
+ self.viIconWxH = [256, 128]
+ self.vidsExtensionList = ('.mkv', '.avi', '.flv', '.mov', '.m4v', '.mpg', '.wmv', '.mpeg', '.mp4', '.webm')
+ self.imagesExtensionList = ('.png', '.jpg', '.jpeg', '.gif', '.ico', '.tga')
+
+
+ def attachBuilder(self, builder):
+ self.builder = builder
+ self.builder.add_from_file("resources/PyTop.glade")
+
+ def createWindow(self):
+ # Get window and connect signals
+ window = self.builder.get_object("Window")
+ window.connect("delete-event", gtk.main_quit)
+ self.setWindowData(window)
+ return window
+
+ def setWindowData(self, window):
+ screen = window.get_screen()
+ visual = screen.get_rgba_visual()
+ if visual != None and screen.is_composited():
+ window.set_visual(visual)
+
+ # bind css file
+ cssProvider = gtk.CssProvider()
+ cssProvider.load_from_path('resources/stylesheet.css')
+ screen = gdk.Screen.get_default()
+ styleContext = gtk.StyleContext()
+ styleContext.add_provider_for_screen(screen, cssProvider, gtk.STYLE_PROVIDER_PRIORITY_USER)
+
+ window.set_app_paintable(True)
+ monitors = self.getMonitorData(screen)
+ window.resize(monitors[0].width, monitors[0].height)
+
+ def getMonitorData(self, screen):
+ monitors = []
+ for m in range(screen.get_n_monitors()):
+ monitors.append(screen.get_monitor_geometry(m))
+
+ for monitor in monitors:
+ print(str(monitor.width) + "x" + str(monitor.height) + "+" + str(monitor.x) + "+" + str(monitor.y))
+
+ return monitors
+
+
+ def returnBuilder(self): return self.builder
+ def returnUserHome(self): return self.usrHome
+ def returnDesktopPath(self): return self.usrHome + "/Desktop"
+ def returnIconImagePos(self): return self.GTK_ORIENTATION
+ def getThumbnailGenerator(self): return self.THUMB_GENERATOR
+ def returnColumnSize(self): return self.ColumnSize
+ def returnContainerWH(self): return self.iconContainerWxH
+ def returnSystemIconImageWH(self): return self.systemIconImageWxH
+ def returnVIIconWH(self): return self.viIconWxH
+ def returnWebHome(self): return self.webHome
+ def isHideHiddenFiles(self): return self.hideHiddenFiles
+ def returnVidsExtensionList(self): return self.vidsExtensionList
+ def returnImagesExtensionList(self): return self.imagesExtensionList
+
+ def setDefaultWebviewSettings(self, widget, settings=None):
+ # Usability
+ settings.set_property('enable-fullscreen', True)
+ settings.set_property('print-backgrounds', True)
+ settings.set_property('enable-frame-flattening', False)
+ settings.set_property('enable-plugins', True)
+ settings.set_property('enable-java', False)
+ settings.set_property('enable-resizable-text-areas', True)
+ settings.set_property('zoom-text-only', False)
+ settings.set_property('enable-smooth-scrolling', True)
+ settings.set_property('enable-back-forward-navigation-gestures', False)
+ settings.set_property('media-playback-requires-user-gesture', False)
+ settings.set_property('enable-tabs-to-links', True)
+ settings.set_property('enable-caret-browsing', False)
+
+ # Security
+ settings.set_property('user-agent','Mozilla/5.0 (X11; Generic; Linux x86-64) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/11.0 Safari/605.1.15')
+ settings.set_property('enable-private-browsing', False)
+ settings.set_property('enable-xss-auditor', True)
+ settings.set_property('enable-hyperlink-auditing', False)
+ settings.set_property('enable-site-specific-quirks', True)
+ settings.set_property('enable-offline-web-application-cache', True)
+ settings.set_property('enable-page-cache', True)
+ settings.set_property('allow-modal-dialogs', False)
+ settings.set_property('enable-html5-local-storage', True)
+ settings.set_property('enable-html5-database', True)
+ settings.set_property('allow-file-access-from-file-urls', False)
+ settings.set_property('allow-universal-access-from-file-urls', False)
+ settings.set_property('enable-dns-prefetching', False)
+
+ # Media stuff
+ # settings.set_property('hardware-acceleration-policy', 'on-demand')
+ settings.set_property('enable-webgl', False)
+ settings.set_property('enable-webaudio', True)
+ settings.set_property('enable-accelerated-2d-canvas', True)
+ settings.set_property('auto-load-images', True)
+ settings.set_property('enable-media-capabilities', True)
+ settings.set_property('enable-media-stream', True)
+ settings.set_property('enable-mediasource', True)
+ settings.set_property('enable-encrypted-media', True)
+ settings.set_property('media-playback-allows-inline', True)
+
+ # JS
+ settings.set_property('enable-javascript', True)
+ settings.set_property('enable-javascript-markup', True)
+ settings.set_property('javascript-can-access-clipboard', False)
+ settings.set_property('javascript-can-open-windows-automatically', False)
+
+ # Debugging
+ settings.set_property('enable-developer-extras', False)
+ settings.set_property('enable-write-console-messages-to-stdout', False)
+ settings.set_property('draw-compositing-indicators', False)
+ settings.set_property('enable-mock-capture-devices', False)
+ settings.set_property('enable-spatial-navigation', False)
diff --git a/src/debs/pyFfm-0-0-1-x64/opt/PyFM/utils/__init__.py b/src/debs/pyFfm-0-0-1-x64/opt/PyFM/utils/__init__.py
new file mode 100644
index 0000000..e291f0f
--- /dev/null
+++ b/src/debs/pyFfm-0-0-1-x64/opt/PyFM/utils/__init__.py
@@ -0,0 +1,6 @@
+from utils.Dragging import Dragging
+from utils.Settings import Settings
+from utils.Events import Events
+from utils.Grid import Grid
+from utils.Icon import Icon
+from utils.FileHandler import FileHandler
diff --git a/src/debs/pyFfm-0-0-1-x64/usr/share/doc/pytop/copyright b/src/debs/pyFfm-0-0-1-x64/usr/share/doc/pytop/copyright
new file mode 100644
index 0000000..04b188e
--- /dev/null
+++ b/src/debs/pyFfm-0-0-1-x64/usr/share/doc/pytop/copyright
@@ -0,0 +1,22 @@
+Pytop is copyright 2019 Maxim Stewart.
+Pytop is currently developed by ITDominator <1itdominator@gmail.com>.
+
+License: GPLv2+
+
+ 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.
+
+See /usr/share/common-licenses/GPL-2, or
+ for the terms of the latest version
+of the GNU General Public License.
diff --git a/src/versions/pyfm-0.0.1/PyFM/new/pyfm/__init__.py b/src/versions/pyfm-0.0.1/PyFM/new/pyfm/__init__.py
new file mode 100644
index 0000000..5f81b40
--- /dev/null
+++ b/src/versions/pyfm-0.0.1/PyFM/new/pyfm/__init__.py
@@ -0,0 +1,31 @@
+# Python imports
+import inspect
+
+
+# Gtk imports
+
+
+# Application imports
+from utils import Settings
+from signal_classes import Signals
+
+
+class Main:
+ def __init__(self, args):
+ settings = Settings()
+
+ # Gets the methods from the classes and sets to handler.
+ # Then, builder connects to any signals it needs.
+ classes = [Signals(settings)]
+
+ handlers = {}
+ for c in classes:
+ methods = None
+ try:
+ methods = inspect.getmembers(c, predicate=inspect.ismethod)
+ handlers.update(methods)
+ except Exception as e:
+ pass
+
+ settings.builder.connect_signals(handlers)
+ window = settings.createWindow()
diff --git a/src/versions/pyfm-0.0.1/PyFM/new/pyfm/__main__.py b/src/versions/pyfm-0.0.1/PyFM/new/pyfm/__main__.py
new file mode 100644
index 0000000..f824967
--- /dev/null
+++ b/src/versions/pyfm-0.0.1/PyFM/new/pyfm/__main__.py
@@ -0,0 +1,36 @@
+#!/usr/bin/python3
+
+
+# Python imports
+import argparse
+from setproctitle import setproctitle
+
+import tracemalloc
+tracemalloc.start()
+
+
+# Gtk imports
+import gi, faulthandler, traceback, signal
+gi.require_version('Gtk', '3.0')
+from gi.repository import Gtk as gtk
+from gi.repository import GLib
+
+# Application imports
+from __init__ import Main
+
+
+if __name__ == "__main__":
+ try:
+ setproctitle('PyFM')
+ GLib.unix_signal_add(GLib.PRIORITY_DEFAULT, signal.SIGINT, gtk.main_quit)
+ faulthandler.enable() # For better debug info
+ parser = argparse.ArgumentParser()
+ # Add long and short arguments
+ parser.add_argument("--file", "-f", default="default", help="JUST SOME FILE ARG.")
+
+ # Read arguments (If any...)
+ args = parser.parse_args()
+ main = Main(args)
+ gtk.main()
+ except Exception as e:
+ traceback.print_exc()
diff --git a/src/versions/pyfm-0.0.1/PyFM/new/pyfm/resources/Main_Window.glade b/src/versions/pyfm-0.0.1/PyFM/new/pyfm/resources/Main_Window.glade
new file mode 100644
index 0000000..70e7df0
--- /dev/null
+++ b/src/versions/pyfm-0.0.1/PyFM/new/pyfm/resources/Main_Window.glade
@@ -0,0 +1,568 @@
+
+
+
+
+
+ True
+ False
+ gtk-apply
+ 3
+
+
+ True
+ False
+ gtk-apply
+ 3
+
+
+ True
+ False
+ gtk-apply
+ 3
+
+
+ True
+ False
+ gtk-apply
+ 3
+
+
+ 800
+ 600
+ False
+ PyFM
+ center
+ 1200
+ 720
+ pyfm.png
+ center
+
+
+ True
+ False
+ vertical
+ top
+
+
+ True
+ False
+
+
+
+ True
+ True
+ 0
+
+
+
+
+ True
+ False
+ 45
+ 5
+ start
+
+
+ tggl_notebook_1
+ True
+ True
+ True
+ tggl_notebook_1_img
+ True
+
+
+
+ True
+ True
+ 0
+
+
+
+
+ tggl_notebook_2
+ True
+ True
+ True
+ tggl_notebook_2_img
+ True
+
+
+
+ True
+ True
+ 1
+
+
+
+
+ tggl_notebook_3
+ True
+ True
+ True
+ tggl_notebook_3_img
+ True
+
+
+
+ True
+ True
+ 2
+
+
+
+
+ tggl_notebook_4
+ True
+ True
+ True
+ tggl_notebook_4_img
+ True
+
+
+
+ True
+ True
+ 3
+
+
+
+
+ False
+ True
+ 1
+
+
+
+
+ False
+ True
+ 0
+
+
+
+
+ True
+ False
+
+
+ gtk-home
+ go_home
+ True
+ True
+ True
+ True
+ True
+
+
+
+ False
+ True
+ 0
+
+
+
+
+ gtk-refresh
+ refresh_view
+ True
+ True
+ True
+ True
+ True
+
+
+
+ False
+ True
+ 1
+
+
+
+
+ gtk-go-up
+ go_up
+ True
+ True
+ True
+ True
+ True
+
+
+
+ False
+ True
+ 2
+
+
+
+
+ True
+ True
+ True
+ Path...
+
+
+ True
+ True
+ 3
+
+
+
+
+ gtk-add
+ create_tab
+ True
+ True
+ True
+ True
+ True
+
+
+
+ False
+ True
+ 4
+
+
+
+
+ False
+ True
+ 1
+
+
+
+
+ True
+ True
+ True
+ True
+ vertical
+ True
+
+
+ True
+ True
+ 5
+ True
+ True
+ True
+
+
+ notebook1
+ True
+ True
+ 5
+ 5
+ 5
+ 5
+ True
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ True
+ True
+
+
+
+
+ notebook2
+ True
+ True
+ 5
+ 5
+ 5
+ 5
+ True
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ True
+ True
+
+
+
+
+ True
+ True
+
+
+
+
+ True
+ True
+ 5
+ True
+ True
+ True
+
+
+ notebook3
+ True
+ True
+ 5
+ 5
+ 5
+ 5
+ True
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ True
+ True
+
+
+
+
+ notebook4
+ True
+ True
+ 5
+ 5
+ 5
+ True
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ True
+ True
+
+
+
+
+ True
+ True
+
+
+
+
+ True
+ True
+ 2
+
+
+
+
+
+
diff --git a/src/versions/pyfm-0.0.1/PyFM/new/pyfm/resources/pyfm.png b/src/versions/pyfm-0.0.1/PyFM/new/pyfm/resources/pyfm.png
new file mode 100644
index 0000000..83974e1
Binary files /dev/null and b/src/versions/pyfm-0.0.1/PyFM/new/pyfm/resources/pyfm.png differ
diff --git a/src/versions/pyfm-0.0.1/PyFM/new/pyfm/resources/stylesheet.css b/src/versions/pyfm-0.0.1/PyFM/new/pyfm/resources/stylesheet.css
new file mode 100644
index 0000000..f8bb0fa
--- /dev/null
+++ b/src/versions/pyfm-0.0.1/PyFM/new/pyfm/resources/stylesheet.css
@@ -0,0 +1,34 @@
+/* Set fm to have transparent window */
+
+* {
+ background: rgba(0, 0, 0, 0.14);
+}
+
+* selection {
+ background-color: rgba(116, 0, 0, 0.65);
+ color: rgba(255, 255, 255, 0.5);
+}
+
+
+.view:selected,
+.view:selected:hover {
+ box-shadow: inset 0 0 0 9999px rgba(15, 134, 13, 0.49);
+ color: rgba(255, 255, 255, 0.5);;
+}
+
+
+/* Rubberband coloring */
+.rubberband,
+rubberband,
+flowbox rubberband,
+treeview.view rubberband,
+.content-view rubberband,
+.content-view .rubberband,
+XfdesktopIconView.view .rubberband {
+ border: 1px solid #6c6c6c;
+ background-color: rgba(21, 158, 167, 0.57);
+}
+
+XfdesktopIconView.view:active {
+ background-color: rgba(172, 102, 21, 1);
+}
diff --git a/src/versions/pyfm-0.0.1/PyFM/new/pyfm/shellfm/__init__.py b/src/versions/pyfm-0.0.1/PyFM/new/pyfm/shellfm/__init__.py
new file mode 100644
index 0000000..0c8b591
--- /dev/null
+++ b/src/versions/pyfm-0.0.1/PyFM/new/pyfm/shellfm/__init__.py
@@ -0,0 +1 @@
+from .windows import WindowController
diff --git a/src/versions/pyfm-0.0.1/PyFM/new/pyfm/shellfm/windows/Window.py b/src/versions/pyfm-0.0.1/PyFM/new/pyfm/shellfm/windows/Window.py
new file mode 100644
index 0000000..78c5241
--- /dev/null
+++ b/src/versions/pyfm-0.0.1/PyFM/new/pyfm/shellfm/windows/Window.py
@@ -0,0 +1,66 @@
+# Python imports
+from random import randint
+
+
+# Lib imports
+
+
+# Application imports
+from .view import View
+
+
+class Window:
+ def __init__(self):
+ self.id_length = 10
+ self.id = ""
+ self.name = ""
+ self.nickname = ""
+ self.isHidden = False
+ self.views = []
+
+ self.generate_id()
+
+
+ def random_with_N_digits(self, n):
+ range_start = 10**(n-1)
+ range_end = (10**n)-1
+ return randint(range_start, range_end)
+
+ def generate_id(self):
+ self.id = str(self.random_with_N_digits(self.id_length))
+
+ def get_window_id(self):
+ return self.id
+
+ def create_view(self):
+ view = View()
+ self.views.append(view)
+ return view
+
+ def pop_view(self):
+ self.views.pop()
+
+ def delete_view_by_id(self, vid):
+ for view in self.views:
+ if view.id == vid:
+ self.views.remove(view)
+ break
+
+
+ def get_view_by_id(self, vid):
+ for view in self.views:
+ if view.id == vid:
+ return view
+
+ def get_view_by_index(self, index):
+ return self.views[index]
+
+ def get_views_count(self):
+ return len(self.views)
+
+ def get_all_views(self):
+ return self.views
+
+ def list_files_from_views(self):
+ for view in self.views:
+ print(view.files)
diff --git a/src/versions/pyfm-0.0.1/PyFM/new/pyfm/shellfm/windows/WindowController.py b/src/versions/pyfm-0.0.1/PyFM/new/pyfm/shellfm/windows/WindowController.py
new file mode 100644
index 0000000..df7747c
--- /dev/null
+++ b/src/versions/pyfm-0.0.1/PyFM/new/pyfm/shellfm/windows/WindowController.py
@@ -0,0 +1,165 @@
+# Python imports
+import json
+from os import path
+
+# Lib imports
+
+
+# Application imports
+from . import Window
+
+
+class WindowController:
+ def __init__(self):
+ USER_HOME = path.expanduser('~')
+ CONFIG_PATH = USER_HOME + "/.config/pyfm"
+ self.session_file = CONFIG_PATH + "/session.json"
+
+ self.active_window_id = ""
+ self.active_tab_id = ""
+ self.windows = []
+
+
+ def set_active_data(self, wid, tid):
+ self.active_window_id = str(wid)
+ self.active_tab_id = str(tid)
+
+ def get_active_data(self):
+ return self.active_window_id, self.active_tab_id
+
+ def create_window(self):
+ window = Window()
+ window.name = "window_" + window.id
+ window.nickname = "window_" + str(len(self.windows) + 1)
+
+ self.windows.append(window)
+ return window
+
+
+ def add_view_for_window(self, win_id):
+ for window in self.windows:
+ if window.id == win_id:
+ return window.create_view()
+
+ def add_view_for_window_by_name(self, name):
+ for window in self.windows:
+ if window.name == name:
+ return window.create_view()
+
+ def add_view_for_window_by_nickname(self, nickname):
+ for window in self.windows:
+ if window.nickname == nickname:
+ return window.create_view()
+
+ def pop_window(self):
+ self.windows.pop()
+
+ def delete_window_by_id(self, win_id):
+ for window in self.windows:
+ if window.id == win_id:
+ self.windows.remove(window)
+ break
+
+ def delete_window_by_name(self, name):
+ for window in self.windows:
+ if window.name == name:
+ self.windows.remove(window)
+ break
+
+ def delete_window_by_nickname(self, nickname):
+ for window in self.windows:
+ if window.nickname == nickname:
+ self.windows.remove(window)
+ break
+
+ def get_window_by_id(self, win_id):
+ for window in self.windows:
+ if window.id == win_id:
+ return window
+
+ raise(f"No Window by ID {win_id} found!")
+
+ def get_window_by_name(self, name):
+ for window in self.windows:
+ if window.name == name:
+ return window
+
+ raise(f"No Window by Name {name} found!")
+
+ def get_window_by_nickname(self, nickname):
+ for window in self.windows:
+ if window.nickname == nickname:
+ return window
+
+ raise(f"No Window by Nickname {nickname} found!")
+
+ def get_window_by_index(self, index):
+ return self.windows[index]
+
+ def get_all_windows(self):
+ return self.windows
+
+ def set_window_nickname(self, win_id = None, nickname = ""):
+ for window in self.windows:
+ if window.id == win_id:
+ window.nickname = nickname
+
+ def list_windows(self):
+ print("\n[ ---- Windows ---- ]\n")
+ for window in self.windows:
+ print(f"\nID: {window.id}")
+ print(f"Name: {window.name}")
+ print(f"Nickname: {window.nickname}")
+ print(f"Is Hidden: {window.isHidden}")
+ print(f"View Count: {window.get_views_count()}")
+ print("\n-------------------------\n")
+
+
+
+ def list_files_from_views_of_window(self, win_id):
+ for window in self.windows:
+ if window.id == win_id:
+ window.list_files_from_views()
+ break
+
+ def get_views_count(self, win_id):
+ for window in self.windows:
+ if window.id == win_id:
+ return window.get_views_count()
+
+ def get_views_from_window(self, win_id):
+ for window in self.windows:
+ if window.id == win_id:
+ return window.get_all_views()
+
+
+
+
+ def save_state(self):
+ windows = []
+ for window in self.windows:
+ views = []
+ for view in window.views:
+ views.append(view.get_current_directory())
+
+ windows.append(
+ [
+ {
+ 'window':{
+ "ID": window.id,
+ "Name": window.name,
+ "Nickname": window.nickname,
+ "isHidden": f"{window.isHidden}",
+ 'views': views
+ }
+ }
+ ]
+ )
+
+ with open(self.session_file, 'w') as outfile:
+ json.dump(windows, outfile, separators=(',', ':'), indent=4)
+
+ def load_state(self):
+ if path.isfile(self.session_file):
+ with open(self.session_file) as infile:
+ return json.load(infile)
diff --git a/src/versions/pyfm-0.0.1/PyFM/new/pyfm/shellfm/windows/__init__.py b/src/versions/pyfm-0.0.1/PyFM/new/pyfm/shellfm/windows/__init__.py
new file mode 100644
index 0000000..cd9f6ce
--- /dev/null
+++ b/src/versions/pyfm-0.0.1/PyFM/new/pyfm/shellfm/windows/__init__.py
@@ -0,0 +1,2 @@
+from .Window import Window
+from .WindowController import WindowController
diff --git a/src/versions/pyfm-0.0.1/PyFM/new/pyfm/shellfm/windows/view/Path.py b/src/versions/pyfm-0.0.1/PyFM/new/pyfm/shellfm/windows/view/Path.py
new file mode 100644
index 0000000..986dda8
--- /dev/null
+++ b/src/versions/pyfm-0.0.1/PyFM/new/pyfm/shellfm/windows/view/Path.py
@@ -0,0 +1,46 @@
+# Python imports
+import os
+
+# Lib imports
+
+# Application imports
+
+
+class Path:
+ def get_home(self):
+ return os.path.expanduser("~") + self.subpath
+
+ def get_path(self):
+ return "/" + "/".join(self.path)
+
+ def get_path_list(self):
+ return self.path
+
+ def push_to_path(self, dir):
+ self.path.append(dir)
+ self.load_directory()
+
+ def pop_from_path(self):
+ self.path.pop()
+
+ if not self.go_past_home:
+ if self.get_home() not in self.get_path():
+ self.set_to_home()
+
+ self.load_directory()
+
+
+ def set_path(self, path):
+ self.path = list( filter(None, path.replace("\\", "/").split('/')) )
+ self.load_directory()
+
+ def set_path_with_sub_path(self, sub_path):
+ path = os.path.join(self.get_home(), sub_path)
+ self.path = list( filter(None, path.replace("\\", "/").split('/')) )
+ self.load_directory()
+
+ def set_to_home(self):
+ home = os.path.expanduser("~") + self.subpath
+ path = list( filter(None, home.replace("\\", "/").split('/')) )
+ self.path = path
+ self.load_directory()
diff --git a/src/versions/pyfm-0.0.1/PyFM/new/pyfm/shellfm/windows/view/View.py b/src/versions/pyfm-0.0.1/PyFM/new/pyfm/shellfm/windows/view/View.py
new file mode 100644
index 0000000..3d5eed1
--- /dev/null
+++ b/src/versions/pyfm-0.0.1/PyFM/new/pyfm/shellfm/windows/view/View.py
@@ -0,0 +1,196 @@
+# Python imports
+import hashlib
+import os
+from os import listdir
+from os.path import isdir, isfile, join
+
+from random import randint
+
+
+# Lib imports
+
+
+# Application imports
+from .utils import Settings, Launcher
+from .icons import Icon
+from . import Path
+
+
+class View(Settings, Launcher, Icon, Path):
+ def __init__(self):
+ self. logger = None
+ self.id_length = 10
+
+ self.id = ""
+ self.files = []
+ self.dirs = []
+ self.vids = []
+ self.images = []
+ self.desktop = []
+ self.ungrouped = []
+
+ self.generate_id()
+ self.set_to_home()
+
+
+ def random_with_N_digits(self, n):
+ range_start = 10**(n-1)
+ range_end = (10**n)-1
+ return randint(range_start, range_end)
+
+ def generate_id(self):
+ self.id = str(self.random_with_N_digits(self.id_length))
+
+ def get_tab_id(self):
+ return self.id
+
+ def load_directory(self):
+ path = self.get_path()
+ self.dirs = [".", ".."]
+ self.vids = []
+ self.images = []
+ self.desktop = []
+ self.ungrouped = []
+ self.files = []
+
+ if not isdir(path):
+ self.set_to_home()
+ return ""
+
+ for f in listdir(path):
+ file = join(path, f)
+ if self.HIDE_HIDDEN_FILES:
+ if f.startswith('.'):
+ continue
+
+ if isfile(file):
+ lowerName = file.lower()
+ if lowerName.endswith(self.fvideos):
+ self.vids.append(f)
+ elif lowerName.endswith(self.fimages):
+ self.images.append(f)
+ elif lowerName.endswith((".desktop",)):
+ self.desktop.append(f)
+ else:
+ self.ungrouped.append(f)
+ else:
+ self.dirs.append(f)
+
+ self.dirs.sort()
+ self.vids.sort()
+ self.images.sort()
+ self.desktop.sort()
+ self.ungrouped.sort()
+
+ self.files = self.dirs + self.vids + self.images + self.desktop + self.ungrouped
+
+ def hash_text(self, text):
+ return hashlib.sha256(str.encode(text)).hexdigest()[:18]
+
+ def hash_set(self, arry):
+ data = []
+ for arr in arry:
+ data.append([arr, self.hash_text(arr)])
+ return data
+
+ def is_folder_locked(self, hash):
+ if self.lock_folder:
+ path_parts = self.get_path().split('/')
+ file = self.get_path_part_from_hash(hash)
+
+ # Insure chilren folders are locked too.
+ lockedFolderInPath = False
+ for folder in self.locked_folders:
+ if folder in path_parts:
+ lockedFolderInPath = True
+ break
+
+ return (file in self.locked_folders or lockedFolderInPath)
+ else:
+ return False
+
+
+
+
+ def get_path_part_from_hash(self, hash):
+ files = self.get_files()
+ file = None
+
+ for f in files:
+ if hash == f[1]:
+ file = f[0]
+ break
+
+ return file
+
+ def get_files_formatted(self):
+ files = self.hash_set(self.files),
+ dirs = self.hash_set(self.dirs),
+ videos = self.get_videos(),
+ images = self.hash_set(self.images),
+ desktops = self.hash_set(self.desktop),
+ ungrouped = self.hash_set(self.ungrouped)
+
+ return {
+ 'path_head': self.get_path(),
+ 'list': {
+ 'files': files,
+ 'dirs': dirs,
+ 'videos': videos,
+ 'images': images,
+ 'desktops': desktops,
+ 'ungrouped': ungrouped
+ }
+ }
+
+ def get_pixbuf_icon_str_combo(self):
+ data = []
+ dir = self.get_current_directory()
+ for file in self.files:
+ icon = self.create_icon(dir, file).get_pixbuf()
+ data.append([icon, file])
+
+ return data
+
+ def get_gtk_icon_str_combo(self):
+ data = []
+ dir = self.get_current_directory()
+ for file in self.files:
+ icon = self.create_icon(dir, file)
+ data.append([icon, file[0]])
+
+ return data
+
+ def get_current_directory(self):
+ return self.get_path()
+
+ def get_current_sub_path(self):
+ path = self.get_path()
+ home = self.get_home() + "/"
+ return path.replace(home, "")
+
+ def get_end_of_path(self):
+ parts = self.get_current_directory().split("/")
+ size = len(parts)
+ return parts[size - 1]
+
+ def get_dot_dots(self):
+ return self.hash_set(['.', '..'])
+
+ def get_files(self):
+ return self.hash_set(self.files)
+
+ def get_dirs(self):
+ return self.hash_set(self.dirs)
+
+ def get_videos(self):
+ return self.hash_set(self.vids)
+
+ def get_images(self):
+ return self.hash_set(self.images)
+
+ def get_desktops(self):
+ return self.hash_set(self.desktop)
+
+ def get_ungrouped(self):
+ return self.hash_set(self.ungrouped)
diff --git a/src/versions/pyfm-0.0.1/PyFM/new/pyfm/shellfm/windows/view/__init__.py b/src/versions/pyfm-0.0.1/PyFM/new/pyfm/shellfm/windows/view/__init__.py
new file mode 100644
index 0000000..07d9ad7
--- /dev/null
+++ b/src/versions/pyfm-0.0.1/PyFM/new/pyfm/shellfm/windows/view/__init__.py
@@ -0,0 +1,5 @@
+from .utils import *
+from .icons import *
+
+from .Path import Path
+from .View import View
diff --git a/src/versions/pyfm-0.0.1/PyFM/new/pyfm/shellfm/windows/view/icons/Icon.py b/src/versions/pyfm-0.0.1/PyFM/new/pyfm/shellfm/windows/view/icons/Icon.py
new file mode 100644
index 0000000..474127c
--- /dev/null
+++ b/src/versions/pyfm-0.0.1/PyFM/new/pyfm/shellfm/windows/view/icons/Icon.py
@@ -0,0 +1,87 @@
+# Python Imports
+import os, subprocess, threading, hashlib
+from os.path import isfile
+
+# Gtk imports
+import gi
+gi.require_version('Gtk', '3.0')
+
+from gi.repository import Gtk
+
+# Application imports
+from .mixins import *
+
+
+
+def threaded(fn):
+ def wrapper(*args, **kwargs):
+ threading.Thread(target=fn, args=args, kwargs=kwargs).start()
+ return wrapper
+
+
+class Icon(DesktopIconMixin, VideoIconMixin):
+ def create_icon(self, dir, file):
+ full_path = dir + "/" + file
+ return self.get_icon_image(dir, file, full_path)
+
+ def get_icon_image(self, dir, file, full_path):
+ try:
+ thumbnl = None
+
+ if file.lower().endswith(self.fvideos): # Video icon
+ thumbnl = self.create_thumbnail(dir, file)
+ elif file.lower().endswith(self.fimages): # Image Icon
+ thumbnl = self.create_scaled_image(full_path, self.VIDEO_ICON_WH)
+ elif full_path.lower().endswith( ('.desktop',) ): # .desktop file parsing
+ thumbnl = self.parse_desktop_files(full_path)
+ else: # System icons
+ thumbnl = self.get_system_thumbnail(full_path, self.SYS_ICON_WH[0])
+
+ if thumbnl == None: # If no icon whatsoever, return internal default
+ thumbnl = Gtk.Image.new_from_file(self.DEFAULT_ICON)
+
+ return thumbnl
+ except Exception as e:
+ print("Icon generation issue:")
+ print( repr(e) )
+ return Gtk.Image.new_from_file(self.DEFAULT_ICON)
+
+ def create_thumbnail(self, dir, file):
+ full_path = dir + "/" + file
+ try:
+ file_hash = hashlib.sha256(str.encode(full_path)).hexdigest()
+ hash_img_pth = self.ABS_THUMBS_PTH + "/" + file_hash + ".jpg"
+ if isfile(hash_img_pth) == False:
+ self.generate_video_thumbnail(full_path, hash_img_pth)
+
+ thumbnl = self.create_scaled_image(hash_img_pth, self.VIDEO_ICON_WH)
+ if thumbnl == None: # If no icon whatsoever, return internal default
+ thumbnl = Gtk.Image.new_from_file(self.DEFAULT_ICONS + "/video.png")
+
+ return thumbnl
+ except Exception as e:
+ print("Thumbnail generation issue:")
+ print( repr(e) )
+ return Gtk.Image.new_from_file(self.DEFAULT_ICONS + "/video.png")
+
+
+ def create_scaled_image(self, path, wxh):
+ try:
+ pixbuf = Gtk.Image.new_from_file(path).get_pixbuf()
+ scaled_pixbuf = pixbuf.scale_simple(wxh[0], wxh[1], 2) # 2 = BILINEAR and is best by default
+ return Gtk.Image.new_from_pixbuf(scaled_pixbuf)
+ except Exception as e:
+ print("Image Scaling Issue:")
+ print( repr(e) )
+ return None
+
+ def create_from_file(self, path):
+ try:
+ return Gtk.Image.new_from_file(path)
+ except Exception as e:
+ print("Image from file Issue:")
+ print( repr(e) )
+ return None
+
+ def return_generic_icon(self):
+ return Gtk.Image.new_from_file(self.DEFAULT_ICON)
diff --git a/src/versions/pyfm-0.0.1/PyFM/new/pyfm/shellfm/windows/view/icons/__init__.py b/src/versions/pyfm-0.0.1/PyFM/new/pyfm/shellfm/windows/view/icons/__init__.py
new file mode 100644
index 0000000..b946d44
--- /dev/null
+++ b/src/versions/pyfm-0.0.1/PyFM/new/pyfm/shellfm/windows/view/icons/__init__.py
@@ -0,0 +1,4 @@
+from .mixins import DesktopIconMixin
+from .mixins import VideoIconMixin
+
+from .Icon import Icon
diff --git a/src/versions/pyfm-0.0.1/PyFM/new/pyfm/shellfm/windows/view/icons/mixins/DesktopIconMixin.py b/src/versions/pyfm-0.0.1/PyFM/new/pyfm/shellfm/windows/view/icons/mixins/DesktopIconMixin.py
new file mode 100644
index 0000000..720330f
--- /dev/null
+++ b/src/versions/pyfm-0.0.1/PyFM/new/pyfm/shellfm/windows/view/icons/mixins/DesktopIconMixin.py
@@ -0,0 +1,87 @@
+# Python Imports
+import os, subprocess, hashlib
+from os.path import isfile
+
+# Gtk imports
+import gi
+gi.require_version('Gtk', '3.0')
+
+from gi.repository import Gio
+from gi.repository import Gtk
+
+# Application imports
+from .xdg.DesktopEntry import DesktopEntry
+
+
+class DesktopIconMixin:
+ def get_system_thumbnail(self, filename, size):
+ try:
+ if os.path.exists(filename):
+ gioFile = Gio.File.new_for_path(filename)
+ info = gioFile.query_info('standard::icon' , 0, Gio.Cancellable())
+ icon = info.get_icon().get_names()[0]
+ iconTheme = Gtk.IconTheme.get_default()
+ iconData = iconTheme.lookup_icon(icon , size , 0)
+ if iconData:
+ iconPath = iconData.get_filename()
+ return Gtk.Image.new_from_file(iconPath)
+ else:
+ return None
+ else:
+ return None
+ except Exception as e:
+ print("system icon generation issue:")
+ print( repr(e) )
+ return None
+
+ def parse_desktop_files(self, full_path):
+ try:
+ xdgObj = DesktopEntry(full_path)
+ icon = xdgObj.getIcon()
+ alt_icon_path = ""
+
+ if "steam" in icon:
+ name = xdgObj.getName()
+ file_hash = hashlib.sha256(str.encode(name)).hexdigest()
+ hash_img_pth = self.STEAM_ICONS_PTH + "/" + file_hash + ".jpg"
+
+ if isfile(hash_img_pth) == True:
+ # Use video sizes since headers are bigger
+ return self.create_scaled_image(hash_img_pth, self.VIDEO_ICON_WH)
+
+ exec_str = xdgObj.getExec()
+ parts = exec_str.split("steam://rungameid/")
+ id = parts[len(parts) - 1]
+ imageLink = self.STEAM_BASE_URL + id + "/header.jpg"
+ proc = subprocess.Popen(["wget", "-O", hash_img_pth, imageLink])
+ proc.wait()
+
+ # Use video thumbnail sizes since headers are bigger
+ return self.create_scaled_image(hash_img_pth, self.VIDEO_ICON_WH)
+ elif os.path.exists(icon):
+ return self.create_scaled_image(icon, self.SYS_ICON_WH)
+ else:
+ alt_icon_path = ""
+
+ for dir in self.ICON_DIRS:
+ alt_icon_path = self.traverse_icons_folder(dir, icon)
+ if alt_icon_path != "":
+ break
+
+ return self.create_scaled_image(alt_icon_path, self.SYS_ICON_WH)
+ except Exception as e:
+ print(".desktop icon generation issue:")
+ print( repr(e) )
+ return None
+
+ def traverse_icons_folder(self, path, icon):
+ alt_icon_path = ""
+
+ for (dirpath, dirnames, filenames) in os.walk(path):
+ for file in filenames:
+ appNM = "application-x-" + icon
+ if icon in file or appNM in file:
+ alt_icon_path = dirpath + "/" + file
+ break
+
+ return alt_icon_path
diff --git a/src/versions/pyfm-0.0.1/PyFM/new/pyfm/shellfm/windows/view/icons/mixins/VideoIconMixin.py b/src/versions/pyfm-0.0.1/PyFM/new/pyfm/shellfm/windows/view/icons/mixins/VideoIconMixin.py
new file mode 100644
index 0000000..fc35e9d
--- /dev/null
+++ b/src/versions/pyfm-0.0.1/PyFM/new/pyfm/shellfm/windows/view/icons/mixins/VideoIconMixin.py
@@ -0,0 +1,53 @@
+# Python Imports
+import subprocess
+
+# Gtk imports
+
+# Application imports
+
+
+class VideoIconMixin:
+ def generate_video_thumbnail(self, full_path, hash_img_pth):
+ try:
+ proc = subprocess.Popen([self.FFMPG_THUMBNLR, "-t", "65%", "-s", "300", "-c", "jpg", "-i", full_path, "-o", hash_img_pth])
+ proc.wait()
+ except Exception as e:
+ self.logger.debug(repr(e))
+ self.ffprobe_generate_video_thumbnail(full_path, hash_img_pth)
+
+
+ def ffprobe_generate_video_thumbnail(self, full_path, hash_img_pth):
+ proc = None
+ try:
+ # Stream duration
+ command = ["ffprobe", "-v", "error", "-select_streams", "v:0", "-show_entries", "stream=duration", "-of", "default=noprint_wrappers=1:nokey=1", full_path]
+ data = subprocess.run(command, stdout=subprocess.PIPE)
+ duration = data.stdout.decode('utf-8')
+
+ # Format (container) duration
+ if "N/A" in duration:
+ command = ["ffprobe", "-v", "error", "-show_entries", "format=duration", "-of", "default=noprint_wrappers=1:nokey=1", full_path]
+ data = subprocess.run(command , stdout=subprocess.PIPE)
+ duration = data.stdout.decode('utf-8')
+
+ # Stream duration type: image2
+ if "N/A" in duration:
+ command = ["ffprobe", "-v", "error", "-select_streams", "v:0", "-f", "image2", "-show_entries", "stream=duration", "-of", "default=noprint_wrappers=1:nokey=1", full_path]
+ data = subprocess.run(command, stdout=subprocess.PIPE)
+ duration = data.stdout.decode('utf-8')
+
+ # Format (container) duration type: image2
+ if "N/A" in duration:
+ command = ["ffprobe", "-v", "error", "-f", "image2", "-show_entries", "format=duration", "-of", "default=noprint_wrappers=1:nokey=1", full_path]
+ data = subprocess.run(command , stdout=subprocess.PIPE)
+ duration = data.stdout.decode('utf-8')
+
+ # Get frame roughly 35% through video
+ grabTime = str( int( float( duration.split(".")[0] ) * 0.35) )
+ command = ["ffmpeg", "-ss", grabTime, "-an", "-i", full_path, "-s", "320x180", "-vframes", "1", hash_img_pth]
+ proc = subprocess.Popen(command, stdout=subprocess.PIPE)
+ proc.wait()
+ except Exception as e:
+ print("Video thumbnail generation issue in thread:")
+ print( repr(e) )
+ self.logger.debug(repr(e))
diff --git a/src/versions/pyfm-0.0.1/PyFM/new/pyfm/shellfm/windows/view/icons/mixins/__init__.py b/src/versions/pyfm-0.0.1/PyFM/new/pyfm/shellfm/windows/view/icons/mixins/__init__.py
new file mode 100644
index 0000000..54bfe39
--- /dev/null
+++ b/src/versions/pyfm-0.0.1/PyFM/new/pyfm/shellfm/windows/view/icons/mixins/__init__.py
@@ -0,0 +1,4 @@
+from . import xdg
+
+from .VideoIconMixin import VideoIconMixin
+from .DesktopIconMixin import DesktopIconMixin
diff --git a/src/versions/pyfm-0.0.1/PyFM/new/pyfm/shellfm/windows/view/icons/mixins/xdg/BaseDirectory.py b/src/versions/pyfm-0.0.1/PyFM/new/pyfm/shellfm/windows/view/icons/mixins/xdg/BaseDirectory.py
new file mode 100644
index 0000000..a7c31b1
--- /dev/null
+++ b/src/versions/pyfm-0.0.1/PyFM/new/pyfm/shellfm/windows/view/icons/mixins/xdg/BaseDirectory.py
@@ -0,0 +1,160 @@
+"""
+This module is based on a rox module (LGPL):
+
+http://cvs.sourceforge.net/viewcvs.py/rox/ROX-Lib2/python/rox/basedir.py?rev=1.9&view=log
+
+The freedesktop.org Base Directory specification provides a way for
+applications to locate shared data and configuration:
+
+ http://standards.freedesktop.org/basedir-spec/
+
+(based on version 0.6)
+
+This module can be used to load and save from and to these directories.
+
+Typical usage:
+
+ from rox import basedir
+
+ for dir in basedir.load_config_paths('mydomain.org', 'MyProg', 'Options'):
+ print "Load settings from", dir
+
+ dir = basedir.save_config_path('mydomain.org', 'MyProg')
+ print >>file(os.path.join(dir, 'Options'), 'w'), "foo=2"
+
+Note: see the rox.Options module for a higher-level API for managing options.
+"""
+
+import os, stat
+
+_home = os.path.expanduser('~')
+xdg_data_home = os.environ.get('XDG_DATA_HOME') or \
+ os.path.join(_home, '.local', 'share')
+
+xdg_data_dirs = [xdg_data_home] + \
+ (os.environ.get('XDG_DATA_DIRS') or '/usr/local/share:/usr/share').split(':')
+
+xdg_config_home = os.environ.get('XDG_CONFIG_HOME') or \
+ os.path.join(_home, '.config')
+
+xdg_config_dirs = [xdg_config_home] + \
+ (os.environ.get('XDG_CONFIG_DIRS') or '/etc/xdg').split(':')
+
+xdg_cache_home = os.environ.get('XDG_CACHE_HOME') or \
+ os.path.join(_home, '.cache')
+
+xdg_data_dirs = [x for x in xdg_data_dirs if x]
+xdg_config_dirs = [x for x in xdg_config_dirs if x]
+
+def save_config_path(*resource):
+ """Ensure ``$XDG_CONFIG_HOME//`` exists, and return its path.
+ 'resource' should normally be the name of your application. Use this
+ when saving configuration settings.
+ """
+ resource = os.path.join(*resource)
+ assert not resource.startswith('/')
+ path = os.path.join(xdg_config_home, resource)
+ if not os.path.isdir(path):
+ os.makedirs(path, 0o700)
+ return path
+
+def save_data_path(*resource):
+ """Ensure ``$XDG_DATA_HOME//`` exists, and return its path.
+ 'resource' should normally be the name of your application or a shared
+ resource. Use this when saving or updating application data.
+ """
+ resource = os.path.join(*resource)
+ assert not resource.startswith('/')
+ path = os.path.join(xdg_data_home, resource)
+ if not os.path.isdir(path):
+ os.makedirs(path)
+ return path
+
+def save_cache_path(*resource):
+ """Ensure ``$XDG_CACHE_HOME//`` exists, and return its path.
+ 'resource' should normally be the name of your application or a shared
+ resource."""
+ resource = os.path.join(*resource)
+ assert not resource.startswith('/')
+ path = os.path.join(xdg_cache_home, resource)
+ if not os.path.isdir(path):
+ os.makedirs(path)
+ return path
+
+def load_config_paths(*resource):
+ """Returns an iterator which gives each directory named 'resource' in the
+ configuration search path. Information provided by earlier directories should
+ take precedence over later ones, and the user-specific config dir comes
+ first."""
+ resource = os.path.join(*resource)
+ for config_dir in xdg_config_dirs:
+ path = os.path.join(config_dir, resource)
+ if os.path.exists(path): yield path
+
+def load_first_config(*resource):
+ """Returns the first result from load_config_paths, or None if there is nothing
+ to load."""
+ for x in load_config_paths(*resource):
+ return x
+ return None
+
+def load_data_paths(*resource):
+ """Returns an iterator which gives each directory named 'resource' in the
+ application data search path. Information provided by earlier directories
+ should take precedence over later ones."""
+ resource = os.path.join(*resource)
+ for data_dir in xdg_data_dirs:
+ path = os.path.join(data_dir, resource)
+ if os.path.exists(path): yield path
+
+def get_runtime_dir(strict=True):
+ """Returns the value of $XDG_RUNTIME_DIR, a directory path.
+
+ This directory is intended for 'user-specific non-essential runtime files
+ and other file objects (such as sockets, named pipes, ...)', and
+ 'communication and synchronization purposes'.
+
+ As of late 2012, only quite new systems set $XDG_RUNTIME_DIR. If it is not
+ set, with ``strict=True`` (the default), a KeyError is raised. With
+ ``strict=False``, PyXDG will create a fallback under /tmp for the current
+ user. This fallback does *not* provide the same guarantees as the
+ specification requires for the runtime directory.
+
+ The strict default is deliberately conservative, so that application
+ developers can make a conscious decision to allow the fallback.
+ """
+ try:
+ return os.environ['XDG_RUNTIME_DIR']
+ except KeyError:
+ if strict:
+ raise
+
+ import getpass
+ fallback = '/tmp/pyxdg-runtime-dir-fallback-' + getpass.getuser()
+ create = False
+
+ try:
+ # This must be a real directory, not a symlink, so attackers can't
+ # point it elsewhere. So we use lstat to check it.
+ st = os.lstat(fallback)
+ except OSError as e:
+ import errno
+ if e.errno == errno.ENOENT:
+ create = True
+ else:
+ raise
+ else:
+ # The fallback must be a directory
+ if not stat.S_ISDIR(st.st_mode):
+ os.unlink(fallback)
+ create = True
+ # Must be owned by the user and not accessible by anyone else
+ elif (st.st_uid != os.getuid()) \
+ or (st.st_mode & (stat.S_IRWXG | stat.S_IRWXO)):
+ os.rmdir(fallback)
+ create = True
+
+ if create:
+ os.mkdir(fallback, 0o700)
+
+ return fallback
diff --git a/src/versions/pyfm-0.0.1/PyFM/new/pyfm/shellfm/windows/view/icons/mixins/xdg/Config.py b/src/versions/pyfm-0.0.1/PyFM/new/pyfm/shellfm/windows/view/icons/mixins/xdg/Config.py
new file mode 100644
index 0000000..3f5d654
--- /dev/null
+++ b/src/versions/pyfm-0.0.1/PyFM/new/pyfm/shellfm/windows/view/icons/mixins/xdg/Config.py
@@ -0,0 +1,39 @@
+"""
+Functions to configure Basic Settings
+"""
+
+language = "C"
+windowmanager = None
+icon_theme = "hicolor"
+icon_size = 48
+cache_time = 5
+root_mode = False
+
+def setWindowManager(wm):
+ global windowmanager
+ windowmanager = wm
+
+def setIconTheme(theme):
+ global icon_theme
+ icon_theme = theme
+ import xdg.IconTheme
+ xdg.IconTheme.themes = []
+
+def setIconSize(size):
+ global icon_size
+ icon_size = size
+
+def setCacheTime(time):
+ global cache_time
+ cache_time = time
+
+def setLocale(lang):
+ import locale
+ lang = locale.normalize(lang)
+ locale.setlocale(locale.LC_ALL, lang)
+ import xdg.Locale
+ xdg.Locale.update(lang)
+
+def setRootMode(boolean):
+ global root_mode
+ root_mode = boolean
diff --git a/src/versions/pyfm-0.0.1/PyFM/new/pyfm/shellfm/windows/view/icons/mixins/xdg/DesktopEntry.py b/src/versions/pyfm-0.0.1/PyFM/new/pyfm/shellfm/windows/view/icons/mixins/xdg/DesktopEntry.py
new file mode 100644
index 0000000..803993e
--- /dev/null
+++ b/src/versions/pyfm-0.0.1/PyFM/new/pyfm/shellfm/windows/view/icons/mixins/xdg/DesktopEntry.py
@@ -0,0 +1,435 @@
+"""
+Complete implementation of the XDG Desktop Entry Specification
+http://standards.freedesktop.org/desktop-entry-spec/
+
+Not supported:
+- Encoding: Legacy Mixed
+- Does not check exec parameters
+- Does not check URL's
+- Does not completly validate deprecated/kde items
+- Does not completly check categories
+"""
+
+from .IniFile import IniFile
+from . import Locale
+
+from .IniFile import is_ascii
+
+from .Exceptions import ParsingError
+from .util import which
+import os.path
+import re
+import warnings
+
+class DesktopEntry(IniFile):
+ "Class to parse and validate Desktop Entries"
+
+ defaultGroup = 'Desktop Entry'
+
+ def __init__(self, filename=None):
+ """Create a new DesktopEntry.
+
+ If filename exists, it will be parsed as a desktop entry file. If not,
+ or if filename is None, a blank DesktopEntry is created.
+ """
+ self.content = dict()
+ if filename and os.path.exists(filename):
+ self.parse(filename)
+ elif filename:
+ self.new(filename)
+
+ def __str__(self):
+ return self.getName()
+
+ def parse(self, file):
+ """Parse a desktop entry file.
+
+ This can raise :class:`~xdg.Exceptions.ParsingError`,
+ :class:`~xdg.Exceptions.DuplicateGroupError` or
+ :class:`~xdg.Exceptions.DuplicateKeyError`.
+ """
+ IniFile.parse(self, file, ["Desktop Entry", "KDE Desktop Entry"])
+
+ def findTryExec(self):
+ """Looks in the PATH for the executable given in the TryExec field.
+
+ Returns the full path to the executable if it is found, None if not.
+ Raises :class:`~xdg.Exceptions.NoKeyError` if TryExec is not present.
+ """
+ tryexec = self.get('TryExec', strict=True)
+ return which(tryexec)
+
+ # start standard keys
+ def getType(self):
+ return self.get('Type')
+ def getVersion(self):
+ """deprecated, use getVersionString instead """
+ return self.get('Version', type="numeric")
+ def getVersionString(self):
+ return self.get('Version')
+ def getName(self):
+ return self.get('Name', locale=True)
+ def getGenericName(self):
+ return self.get('GenericName', locale=True)
+ def getNoDisplay(self):
+ return self.get('NoDisplay', type="boolean")
+ def getComment(self):
+ return self.get('Comment', locale=True)
+ def getIcon(self):
+ return self.get('Icon', locale=True)
+ def getHidden(self):
+ return self.get('Hidden', type="boolean")
+ def getOnlyShowIn(self):
+ return self.get('OnlyShowIn', list=True)
+ def getNotShowIn(self):
+ return self.get('NotShowIn', list=True)
+ def getTryExec(self):
+ return self.get('TryExec')
+ def getExec(self):
+ return self.get('Exec')
+ def getPath(self):
+ return self.get('Path')
+ def getTerminal(self):
+ return self.get('Terminal', type="boolean")
+ def getMimeType(self):
+ """deprecated, use getMimeTypes instead """
+ return self.get('MimeType', list=True, type="regex")
+ def getMimeTypes(self):
+ return self.get('MimeType', list=True)
+ def getCategories(self):
+ return self.get('Categories', list=True)
+ def getStartupNotify(self):
+ return self.get('StartupNotify', type="boolean")
+ def getStartupWMClass(self):
+ return self.get('StartupWMClass')
+ def getURL(self):
+ return self.get('URL')
+ # end standard keys
+
+ # start kde keys
+ def getServiceTypes(self):
+ return self.get('ServiceTypes', list=True)
+ def getDocPath(self):
+ return self.get('DocPath')
+ def getKeywords(self):
+ return self.get('Keywords', list=True, locale=True)
+ def getInitialPreference(self):
+ return self.get('InitialPreference')
+ def getDev(self):
+ return self.get('Dev')
+ def getFSType(self):
+ return self.get('FSType')
+ def getMountPoint(self):
+ return self.get('MountPoint')
+ def getReadonly(self):
+ return self.get('ReadOnly', type="boolean")
+ def getUnmountIcon(self):
+ return self.get('UnmountIcon', locale=True)
+ # end kde keys
+
+ # start deprecated keys
+ def getMiniIcon(self):
+ return self.get('MiniIcon', locale=True)
+ def getTerminalOptions(self):
+ return self.get('TerminalOptions')
+ def getDefaultApp(self):
+ return self.get('DefaultApp')
+ def getProtocols(self):
+ return self.get('Protocols', list=True)
+ def getExtensions(self):
+ return self.get('Extensions', list=True)
+ def getBinaryPattern(self):
+ return self.get('BinaryPattern')
+ def getMapNotify(self):
+ return self.get('MapNotify')
+ def getEncoding(self):
+ return self.get('Encoding')
+ def getSwallowTitle(self):
+ return self.get('SwallowTitle', locale=True)
+ def getSwallowExec(self):
+ return self.get('SwallowExec')
+ def getSortOrder(self):
+ return self.get('SortOrder', list=True)
+ def getFilePattern(self):
+ return self.get('FilePattern', type="regex")
+ def getActions(self):
+ return self.get('Actions', list=True)
+ # end deprecated keys
+
+ # desktop entry edit stuff
+ def new(self, filename):
+ """Make this instance into a new, blank desktop entry.
+
+ If filename has a .desktop extension, Type is set to Application. If it
+ has a .directory extension, Type is Directory. Other extensions will
+ cause :class:`~xdg.Exceptions.ParsingError` to be raised.
+ """
+ if os.path.splitext(filename)[1] == ".desktop":
+ type = "Application"
+ elif os.path.splitext(filename)[1] == ".directory":
+ type = "Directory"
+ else:
+ raise ParsingError("Unknown extension", filename)
+
+ self.content = dict()
+ self.addGroup(self.defaultGroup)
+ self.set("Type", type)
+ self.filename = filename
+ # end desktop entry edit stuff
+
+ # validation stuff
+ def checkExtras(self):
+ # header
+ if self.defaultGroup == "KDE Desktop Entry":
+ self.warnings.append('[KDE Desktop Entry]-Header is deprecated')
+
+ # file extension
+ if self.fileExtension == ".kdelnk":
+ self.warnings.append("File extension .kdelnk is deprecated")
+ elif self.fileExtension != ".desktop" and self.fileExtension != ".directory":
+ self.warnings.append('Unknown File extension')
+
+ # Type
+ try:
+ self.type = self.content[self.defaultGroup]["Type"]
+ except KeyError:
+ self.errors.append("Key 'Type' is missing")
+
+ # Name
+ try:
+ self.name = self.content[self.defaultGroup]["Name"]
+ except KeyError:
+ self.errors.append("Key 'Name' is missing")
+
+ def checkGroup(self, group):
+ # check if group header is valid
+ if not (group == self.defaultGroup \
+ or re.match("^Desktop Action [a-zA-Z0-9-]+$", group) \
+ or (re.match("^X-", group) and is_ascii(group))):
+ self.errors.append("Invalid Group name: %s" % group)
+ else:
+ #OnlyShowIn and NotShowIn
+ if ("OnlyShowIn" in self.content[group]) and ("NotShowIn" in self.content[group]):
+ self.errors.append("Group may either have OnlyShowIn or NotShowIn, but not both")
+
+ def checkKey(self, key, value, group):
+ # standard keys
+ if key == "Type":
+ if value == "ServiceType" or value == "Service" or value == "FSDevice":
+ self.warnings.append("Type=%s is a KDE extension" % key)
+ elif value == "MimeType":
+ self.warnings.append("Type=MimeType is deprecated")
+ elif not (value == "Application" or value == "Link" or value == "Directory"):
+ self.errors.append("Value of key 'Type' must be Application, Link or Directory, but is '%s'" % value)
+
+ if self.fileExtension == ".directory" and not value == "Directory":
+ self.warnings.append("File extension is .directory, but Type is '%s'" % value)
+ elif self.fileExtension == ".desktop" and value == "Directory":
+ self.warnings.append("Files with Type=Directory should have the extension .directory")
+
+ if value == "Application":
+ if "Exec" not in self.content[group]:
+ self.warnings.append("Type=Application needs 'Exec' key")
+ if value == "Link":
+ if "URL" not in self.content[group]:
+ self.warnings.append("Type=Link needs 'URL' key")
+
+ elif key == "Version":
+ self.checkValue(key, value)
+
+ elif re.match("^Name"+xdg.Locale.regex+"$", key):
+ pass # locale string
+
+ elif re.match("^GenericName"+xdg.Locale.regex+"$", key):
+ pass # locale string
+
+ elif key == "NoDisplay":
+ self.checkValue(key, value, type="boolean")
+
+ elif re.match("^Comment"+xdg.Locale.regex+"$", key):
+ pass # locale string
+
+ elif re.match("^Icon"+xdg.Locale.regex+"$", key):
+ self.checkValue(key, value)
+
+ elif key == "Hidden":
+ self.checkValue(key, value, type="boolean")
+
+ elif key == "OnlyShowIn":
+ self.checkValue(key, value, list=True)
+ self.checkOnlyShowIn(value)
+
+ elif key == "NotShowIn":
+ self.checkValue(key, value, list=True)
+ self.checkOnlyShowIn(value)
+
+ elif key == "TryExec":
+ self.checkValue(key, value)
+ self.checkType(key, "Application")
+
+ elif key == "Exec":
+ self.checkValue(key, value)
+ self.checkType(key, "Application")
+
+ elif key == "Path":
+ self.checkValue(key, value)
+ self.checkType(key, "Application")
+
+ elif key == "Terminal":
+ self.checkValue(key, value, type="boolean")
+ self.checkType(key, "Application")
+
+ elif key == "Actions":
+ self.checkValue(key, value, list=True)
+ self.checkType(key, "Application")
+
+ elif key == "MimeType":
+ self.checkValue(key, value, list=True)
+ self.checkType(key, "Application")
+
+ elif key == "Categories":
+ self.checkValue(key, value)
+ self.checkType(key, "Application")
+ self.checkCategories(value)
+
+ elif re.match("^Keywords"+xdg.Locale.regex+"$", key):
+ self.checkValue(key, value, type="localestring", list=True)
+ self.checkType(key, "Application")
+
+ elif key == "StartupNotify":
+ self.checkValue(key, value, type="boolean")
+ self.checkType(key, "Application")
+
+ elif key == "StartupWMClass":
+ self.checkType(key, "Application")
+
+ elif key == "URL":
+ self.checkValue(key, value)
+ self.checkType(key, "URL")
+
+ # kde extensions
+ elif key == "ServiceTypes":
+ self.checkValue(key, value, list=True)
+ self.warnings.append("Key '%s' is a KDE extension" % key)
+
+ elif key == "DocPath":
+ self.checkValue(key, value)
+ self.warnings.append("Key '%s' is a KDE extension" % key)
+
+ elif key == "InitialPreference":
+ self.checkValue(key, value, type="numeric")
+ self.warnings.append("Key '%s' is a KDE extension" % key)
+
+ elif key == "Dev":
+ self.checkValue(key, value)
+ self.checkType(key, "FSDevice")
+ self.warnings.append("Key '%s' is a KDE extension" % key)
+
+ elif key == "FSType":
+ self.checkValue(key, value)
+ self.checkType(key, "FSDevice")
+ self.warnings.append("Key '%s' is a KDE extension" % key)
+
+ elif key == "MountPoint":
+ self.checkValue(key, value)
+ self.checkType(key, "FSDevice")
+ self.warnings.append("Key '%s' is a KDE extension" % key)
+
+ elif key == "ReadOnly":
+ self.checkValue(key, value, type="boolean")
+ self.checkType(key, "FSDevice")
+ self.warnings.append("Key '%s' is a KDE extension" % key)
+
+ elif re.match("^UnmountIcon"+xdg.Locale.regex+"$", key):
+ self.checkValue(key, value)
+ self.checkType(key, "FSDevice")
+ self.warnings.append("Key '%s' is a KDE extension" % key)
+
+ # deprecated keys
+ elif key == "Encoding":
+ self.checkValue(key, value)
+ self.warnings.append("Key '%s' is deprecated" % key)
+
+ elif re.match("^MiniIcon"+xdg.Locale.regex+"$", key):
+ self.checkValue(key, value)
+ self.warnings.append("Key '%s' is deprecated" % key)
+
+ elif key == "TerminalOptions":
+ self.checkValue(key, value)
+ self.warnings.append("Key '%s' is deprecated" % key)
+
+ elif key == "DefaultApp":
+ self.checkValue(key, value)
+ self.warnings.append("Key '%s' is deprecated" % key)
+
+ elif key == "Protocols":
+ self.checkValue(key, value, list=True)
+ self.warnings.append("Key '%s' is deprecated" % key)
+
+ elif key == "Extensions":
+ self.checkValue(key, value, list=True)
+ self.warnings.append("Key '%s' is deprecated" % key)
+
+ elif key == "BinaryPattern":
+ self.checkValue(key, value)
+ self.warnings.append("Key '%s' is deprecated" % key)
+
+ elif key == "MapNotify":
+ self.checkValue(key, value)
+ self.warnings.append("Key '%s' is deprecated" % key)
+
+ elif re.match("^SwallowTitle"+xdg.Locale.regex+"$", key):
+ self.warnings.append("Key '%s' is deprecated" % key)
+
+ elif key == "SwallowExec":
+ self.checkValue(key, value)
+ self.warnings.append("Key '%s' is deprecated" % key)
+
+ elif key == "FilePattern":
+ self.checkValue(key, value, type="regex", list=True)
+ self.warnings.append("Key '%s' is deprecated" % key)
+
+ elif key == "SortOrder":
+ self.checkValue(key, value, list=True)
+ self.warnings.append("Key '%s' is deprecated" % key)
+
+ # "X-" extensions
+ elif re.match("^X-[a-zA-Z0-9-]+", key):
+ pass
+
+ else:
+ self.errors.append("Invalid key: %s" % key)
+
+ def checkType(self, key, type):
+ if not self.getType() == type:
+ self.errors.append("Key '%s' only allowed in Type=%s" % (key, type))
+
+ def checkOnlyShowIn(self, value):
+ values = self.getList(value)
+ valid = ["GNOME", "KDE", "LXDE", "MATE", "Razor", "ROX", "TDE", "Unity",
+ "XFCE", "Old"]
+ for item in values:
+ if item not in valid and item[0:2] != "X-":
+ self.errors.append("'%s' is not a registered OnlyShowIn value" % item);
+
+ def checkCategories(self, value):
+ values = self.getList(value)
+
+ main = ["AudioVideo", "Audio", "Video", "Development", "Education", "Game", "Graphics", "Network", "Office", "Science", "Settings", "System", "Utility"]
+ if not any(item in main for item in values):
+ self.errors.append("Missing main category")
+
+ additional = ['Building', 'Debugger', 'IDE', 'GUIDesigner', 'Profiling', 'RevisionControl', 'Translation', 'Calendar', 'ContactManagement', 'Database', 'Dictionary', 'Chart', 'Email', 'Finance', 'FlowChart', 'PDA', 'ProjectManagement', 'Presentation', 'Spreadsheet', 'WordProcessor', '2DGraphics', 'VectorGraphics', 'RasterGraphics', '3DGraphics', 'Scanning', 'OCR', 'Photography', 'Publishing', 'Viewer', 'TextTools', 'DesktopSettings', 'HardwareSettings', 'Printing', 'PackageManager', 'Dialup', 'InstantMessaging', 'Chat', 'IRCClient', 'Feed', 'FileTransfer', 'HamRadio', 'News', 'P2P', 'RemoteAccess', 'Telephony', 'TelephonyTools', 'VideoConference', 'WebBrowser', 'WebDevelopment', 'Midi', 'Mixer', 'Sequencer', 'Tuner', 'TV', 'AudioVideoEditing', 'Player', 'Recorder', 'DiscBurning', 'ActionGame', 'AdventureGame', 'ArcadeGame', 'BoardGame', 'BlocksGame', 'CardGame', 'KidsGame', 'LogicGame', 'RolePlaying', 'Shooter', 'Simulation', 'SportsGame', 'StrategyGame', 'Art', 'Construction', 'Music', 'Languages', 'ArtificialIntelligence', 'Astronomy', 'Biology', 'Chemistry', 'ComputerScience', 'DataVisualization', 'Economy', 'Electricity', 'Geography', 'Geology', 'Geoscience', 'History', 'Humanities', 'ImageProcessing', 'Literature', 'Maps', 'Math', 'NumericalAnalysis', 'MedicalSoftware', 'Physics', 'Robotics', 'Spirituality', 'Sports', 'ParallelComputing', 'Amusement', 'Archiving', 'Compression', 'Electronics', 'Emulator', 'Engineering', 'FileTools', 'FileManager', 'TerminalEmulator', 'Filesystem', 'Monitor', 'Security', 'Accessibility', 'Calculator', 'Clock', 'TextEditor', 'Documentation', 'Adult', 'Core', 'KDE', 'GNOME', 'XFCE', 'GTK', 'Qt', 'Motif', 'Java', 'ConsoleOnly']
+ allcategories = additional + main
+
+ for item in values:
+ if item not in allcategories and not item.startswith("X-"):
+ self.errors.append("'%s' is not a registered Category" % item);
+
+ def checkCategorie(self, value):
+ """Deprecated alias for checkCategories - only exists for backwards
+ compatibility.
+ """
+ warnings.warn("checkCategorie is deprecated, use checkCategories",
+ DeprecationWarning)
+ return self.checkCategories(value)
diff --git a/src/versions/pyfm-0.0.1/PyFM/new/pyfm/shellfm/windows/view/icons/mixins/xdg/Exceptions.py b/src/versions/pyfm-0.0.1/PyFM/new/pyfm/shellfm/windows/view/icons/mixins/xdg/Exceptions.py
new file mode 100644
index 0000000..7096b61
--- /dev/null
+++ b/src/versions/pyfm-0.0.1/PyFM/new/pyfm/shellfm/windows/view/icons/mixins/xdg/Exceptions.py
@@ -0,0 +1,84 @@
+"""
+Exception Classes for the xdg package
+"""
+
+debug = False
+
+class Error(Exception):
+ """Base class for exceptions defined here."""
+ def __init__(self, msg):
+ self.msg = msg
+ Exception.__init__(self, msg)
+ def __str__(self):
+ return self.msg
+
+class ValidationError(Error):
+ """Raised when a file fails to validate.
+
+ The filename is the .file attribute.
+ """
+ def __init__(self, msg, file):
+ self.msg = msg
+ self.file = file
+ Error.__init__(self, "ValidationError in file '%s': %s " % (file, msg))
+
+class ParsingError(Error):
+ """Raised when a file cannot be parsed.
+
+ The filename is the .file attribute.
+ """
+ def __init__(self, msg, file):
+ self.msg = msg
+ self.file = file
+ Error.__init__(self, "ParsingError in file '%s', %s" % (file, msg))
+
+class NoKeyError(Error):
+ """Raised when trying to access a nonexistant key in an INI-style file.
+
+ Attributes are .key, .group and .file.
+ """
+ def __init__(self, key, group, file):
+ Error.__init__(self, "No key '%s' in group %s of file %s" % (key, group, file))
+ self.key = key
+ self.group = group
+ self.file = file
+
+class DuplicateKeyError(Error):
+ """Raised when the same key occurs twice in an INI-style file.
+
+ Attributes are .key, .group and .file.
+ """
+ def __init__(self, key, group, file):
+ Error.__init__(self, "Duplicate key '%s' in group %s of file %s" % (key, group, file))
+ self.key = key
+ self.group = group
+ self.file = file
+
+class NoGroupError(Error):
+ """Raised when trying to access a nonexistant group in an INI-style file.
+
+ Attributes are .group and .file.
+ """
+ def __init__(self, group, file):
+ Error.__init__(self, "No group: %s in file %s" % (group, file))
+ self.group = group
+ self.file = file
+
+class DuplicateGroupError(Error):
+ """Raised when the same key occurs twice in an INI-style file.
+
+ Attributes are .group and .file.
+ """
+ def __init__(self, group, file):
+ Error.__init__(self, "Duplicate group: %s in file %s" % (group, file))
+ self.group = group
+ self.file = file
+
+class NoThemeError(Error):
+ """Raised when trying to access a nonexistant icon theme.
+
+ The name of the theme is the .theme attribute.
+ """
+ def __init__(self, theme):
+ Error.__init__(self, "No such icon-theme: %s" % theme)
+ self.theme = theme
diff --git a/src/versions/pyfm-0.0.1/PyFM/new/pyfm/shellfm/windows/view/icons/mixins/xdg/IconTheme.py b/src/versions/pyfm-0.0.1/PyFM/new/pyfm/shellfm/windows/view/icons/mixins/xdg/IconTheme.py
new file mode 100644
index 0000000..2ff3c05
--- /dev/null
+++ b/src/versions/pyfm-0.0.1/PyFM/new/pyfm/shellfm/windows/view/icons/mixins/xdg/IconTheme.py
@@ -0,0 +1,445 @@
+"""
+Complete implementation of the XDG Icon Spec
+http://standards.freedesktop.org/icon-theme-spec/
+"""
+
+import os, time
+import re
+
+from . import IniFile, Config
+from .IniFile import is_ascii
+from .BaseDirectory import xdg_data_dirs
+from .Exceptions import NoThemeError, debug
+
+
+class IconTheme(IniFile):
+ "Class to parse and validate IconThemes"
+ def __init__(self):
+ IniFile.__init__(self)
+
+ def __repr__(self):
+ return self.name
+
+ def parse(self, file):
+ IniFile.parse(self, file, ["Icon Theme", "KDE Icon Theme"])
+ self.dir = os.path.dirname(file)
+ (nil, self.name) = os.path.split(self.dir)
+
+ def getDir(self):
+ return self.dir
+
+ # Standard Keys
+ def getName(self):
+ return self.get('Name', locale=True)
+ def getComment(self):
+ return self.get('Comment', locale=True)
+ def getInherits(self):
+ return self.get('Inherits', list=True)
+ def getDirectories(self):
+ return self.get('Directories', list=True)
+ def getScaledDirectories(self):
+ return self.get('ScaledDirectories', list=True)
+ def getHidden(self):
+ return self.get('Hidden', type="boolean")
+ def getExample(self):
+ return self.get('Example')
+
+ # Per Directory Keys
+ def getSize(self, directory):
+ return self.get('Size', type="integer", group=directory)
+ def getContext(self, directory):
+ return self.get('Context', group=directory)
+ def getType(self, directory):
+ value = self.get('Type', group=directory)
+ if value:
+ return value
+ else:
+ return "Threshold"
+ def getMaxSize(self, directory):
+ value = self.get('MaxSize', type="integer", group=directory)
+ if value or value == 0:
+ return value
+ else:
+ return self.getSize(directory)
+ def getMinSize(self, directory):
+ value = self.get('MinSize', type="integer", group=directory)
+ if value or value == 0:
+ return value
+ else:
+ return self.getSize(directory)
+ def getThreshold(self, directory):
+ value = self.get('Threshold', type="integer", group=directory)
+ if value or value == 0:
+ return value
+ else:
+ return 2
+
+ def getScale(self, directory):
+ value = self.get('Scale', type="integer", group=directory)
+ return value or 1
+
+ # validation stuff
+ def checkExtras(self):
+ # header
+ if self.defaultGroup == "KDE Icon Theme":
+ self.warnings.append('[KDE Icon Theme]-Header is deprecated')
+
+ # file extension
+ if self.fileExtension == ".theme":
+ pass
+ elif self.fileExtension == ".desktop":
+ self.warnings.append('.desktop fileExtension is deprecated')
+ else:
+ self.warnings.append('Unknown File extension')
+
+ # Check required keys
+ # Name
+ try:
+ self.name = self.content[self.defaultGroup]["Name"]
+ except KeyError:
+ self.errors.append("Key 'Name' is missing")
+
+ # Comment
+ try:
+ self.comment = self.content[self.defaultGroup]["Comment"]
+ except KeyError:
+ self.errors.append("Key 'Comment' is missing")
+
+ # Directories
+ try:
+ self.directories = self.content[self.defaultGroup]["Directories"]
+ except KeyError:
+ self.errors.append("Key 'Directories' is missing")
+
+ def checkGroup(self, group):
+ # check if group header is valid
+ if group == self.defaultGroup:
+ try:
+ self.name = self.content[group]["Name"]
+ except KeyError:
+ self.errors.append("Key 'Name' in Group '%s' is missing" % group)
+ try:
+ self.name = self.content[group]["Comment"]
+ except KeyError:
+ self.errors.append("Key 'Comment' in Group '%s' is missing" % group)
+ elif group in self.getDirectories():
+ try:
+ self.type = self.content[group]["Type"]
+ except KeyError:
+ self.type = "Threshold"
+ try:
+ self.name = self.content[group]["Size"]
+ except KeyError:
+ self.errors.append("Key 'Size' in Group '%s' is missing" % group)
+ elif not (re.match(r"^\[X-", group) and is_ascii(group)):
+ self.errors.append("Invalid Group name: %s" % group)
+
+ def checkKey(self, key, value, group):
+ # standard keys
+ if group == self.defaultGroup:
+ if re.match("^Name"+xdg.Locale.regex+"$", key):
+ pass
+ elif re.match("^Comment"+xdg.Locale.regex+"$", key):
+ pass
+ elif key == "Inherits":
+ self.checkValue(key, value, list=True)
+ elif key == "Directories":
+ self.checkValue(key, value, list=True)
+ elif key == "ScaledDirectories":
+ self.checkValue(key, value, list=True)
+ elif key == "Hidden":
+ self.checkValue(key, value, type="boolean")
+ elif key == "Example":
+ self.checkValue(key, value)
+ elif re.match("^X-[a-zA-Z0-9-]+", key):
+ pass
+ else:
+ self.errors.append("Invalid key: %s" % key)
+ elif group in self.getDirectories():
+ if key == "Size":
+ self.checkValue(key, value, type="integer")
+ elif key == "Context":
+ self.checkValue(key, value)
+ elif key == "Type":
+ self.checkValue(key, value)
+ if value not in ["Fixed", "Scalable", "Threshold"]:
+ self.errors.append("Key 'Type' must be one out of 'Fixed','Scalable','Threshold', but is %s" % value)
+ elif key == "MaxSize":
+ self.checkValue(key, value, type="integer")
+ if self.type != "Scalable":
+ self.errors.append("Key 'MaxSize' give, but Type is %s" % self.type)
+ elif key == "MinSize":
+ self.checkValue(key, value, type="integer")
+ if self.type != "Scalable":
+ self.errors.append("Key 'MinSize' give, but Type is %s" % self.type)
+ elif key == "Threshold":
+ self.checkValue(key, value, type="integer")
+ if self.type != "Threshold":
+ self.errors.append("Key 'Threshold' give, but Type is %s" % self.type)
+ elif key == "Scale":
+ self.checkValue(key, value, type="integer")
+ elif re.match("^X-[a-zA-Z0-9-]+", key):
+ pass
+ else:
+ self.errors.append("Invalid key: %s" % key)
+
+
+class IconData(IniFile):
+ "Class to parse and validate IconData Files"
+ def __init__(self):
+ IniFile.__init__(self)
+
+ def __repr__(self):
+ displayname = self.getDisplayName()
+ if displayname:
+ return "" % displayname
+ else:
+ return ""
+
+ def parse(self, file):
+ IniFile.parse(self, file, ["Icon Data"])
+
+ # Standard Keys
+ def getDisplayName(self):
+ """Retrieve the display name from the icon data, if one is specified."""
+ return self.get('DisplayName', locale=True)
+ def getEmbeddedTextRectangle(self):
+ """Retrieve the embedded text rectangle from the icon data as a list of
+ numbers (x0, y0, x1, y1), if it is specified."""
+ return self.get('EmbeddedTextRectangle', type="integer", list=True)
+ def getAttachPoints(self):
+ """Retrieve the anchor points for overlays & emblems from the icon data,
+ as a list of co-ordinate pairs, if they are specified."""
+ return self.get('AttachPoints', type="point", list=True)
+
+ # validation stuff
+ def checkExtras(self):
+ # file extension
+ if self.fileExtension != ".icon":
+ self.warnings.append('Unknown File extension')
+
+ def checkGroup(self, group):
+ # check if group header is valid
+ if not (group == self.defaultGroup \
+ or (re.match(r"^\[X-", group) and is_ascii(group))):
+ self.errors.append("Invalid Group name: %s" % group.encode("ascii", "replace"))
+
+ def checkKey(self, key, value, group):
+ # standard keys
+ if re.match("^DisplayName"+xdg.Locale.regex+"$", key):
+ pass
+ elif key == "EmbeddedTextRectangle":
+ self.checkValue(key, value, type="integer", list=True)
+ elif key == "AttachPoints":
+ self.checkValue(key, value, type="point", list=True)
+ elif re.match("^X-[a-zA-Z0-9-]+", key):
+ pass
+ else:
+ self.errors.append("Invalid key: %s" % key)
+
+
+
+icondirs = []
+for basedir in xdg_data_dirs:
+ icondirs.append(os.path.join(basedir, "icons"))
+ icondirs.append(os.path.join(basedir, "pixmaps"))
+icondirs.append(os.path.expanduser("~/.icons"))
+
+# just cache variables, they give a 10x speed improvement
+themes = []
+theme_cache = {}
+dir_cache = {}
+icon_cache = {}
+
+def getIconPath(iconname, size = None, theme = None, extensions = ["png", "svg", "xpm"]):
+ """Get the path to a specified icon.
+
+ size :
+ Icon size in pixels. Defaults to ``xdg.Config.icon_size``.
+ theme :
+ Icon theme name. Defaults to ``xdg.Config.icon_theme``. If the icon isn't
+ found in the specified theme, it will be looked up in the basic 'hicolor'
+ theme.
+ extensions :
+ List of preferred file extensions.
+
+ Example::
+
+ >>> getIconPath("inkscape", 32)
+ '/usr/share/icons/hicolor/32x32/apps/inkscape.png'
+ """
+
+ global themes
+
+ if size == None:
+ size = xdg.Config.icon_size
+ if theme == None:
+ theme = xdg.Config.icon_theme
+
+ # if we have an absolute path, just return it
+ if os.path.isabs(iconname):
+ return iconname
+
+ # check if it has an extension and strip it
+ if os.path.splitext(iconname)[1][1:] in extensions:
+ iconname = os.path.splitext(iconname)[0]
+
+ # parse theme files
+ if (themes == []) or (themes[0].name != theme):
+ themes = list(__get_themes(theme))
+
+ # more caching (icon looked up in the last 5 seconds?)
+ tmp = (iconname, size, theme, tuple(extensions))
+ try:
+ timestamp, icon = icon_cache[tmp]
+ except KeyError:
+ pass
+ else:
+ if (time.time() - timestamp) >= xdg.Config.cache_time:
+ del icon_cache[tmp]
+ else:
+ return icon
+
+ for thme in themes:
+ icon = LookupIcon(iconname, size, thme, extensions)
+ if icon:
+ icon_cache[tmp] = (time.time(), icon)
+ return icon
+
+ # cache stuff again (directories looked up in the last 5 seconds?)
+ for directory in icondirs:
+ if (directory not in dir_cache \
+ or (int(time.time() - dir_cache[directory][1]) >= xdg.Config.cache_time \
+ and dir_cache[directory][2] < os.path.getmtime(directory))) \
+ and os.path.isdir(directory):
+ dir_cache[directory] = (os.listdir(directory), time.time(), os.path.getmtime(directory))
+
+ for dir, values in dir_cache.items():
+ for extension in extensions:
+ try:
+ if iconname + "." + extension in values[0]:
+ icon = os.path.join(dir, iconname + "." + extension)
+ icon_cache[tmp] = [time.time(), icon]
+ return icon
+ except UnicodeDecodeError as e:
+ if debug:
+ raise e
+ else:
+ pass
+
+ # we haven't found anything? "hicolor" is our fallback
+ if theme != "hicolor":
+ icon = getIconPath(iconname, size, "hicolor")
+ icon_cache[tmp] = [time.time(), icon]
+ return icon
+
+def getIconData(path):
+ """Retrieve the data from the .icon file corresponding to the given file. If
+ there is no .icon file, it returns None.
+
+ Example::
+
+ getIconData("/usr/share/icons/Tango/scalable/places/folder.svg")
+ """
+ if os.path.isfile(path):
+ icon_file = os.path.splitext(path)[0] + ".icon"
+ if os.path.isfile(icon_file):
+ data = IconData()
+ data.parse(icon_file)
+ return data
+
+def __get_themes(themename):
+ """Generator yielding IconTheme objects for a specified theme and any themes
+ from which it inherits.
+ """
+ for dir in icondirs:
+ theme_file = os.path.join(dir, themename, "index.theme")
+ if os.path.isfile(theme_file):
+ break
+ theme_file = os.path.join(dir, themename, "index.desktop")
+ if os.path.isfile(theme_file):
+ break
+ else:
+ if debug:
+ raise NoThemeError(themename)
+ return
+
+ theme = IconTheme()
+ theme.parse(theme_file)
+ yield theme
+ for subtheme in theme.getInherits():
+ for t in __get_themes(subtheme):
+ yield t
+
+def LookupIcon(iconname, size, theme, extensions):
+ # look for the cache
+ if theme.name not in theme_cache:
+ theme_cache[theme.name] = []
+ theme_cache[theme.name].append(time.time() - (xdg.Config.cache_time + 1)) # [0] last time of lookup
+ theme_cache[theme.name].append(0) # [1] mtime
+ theme_cache[theme.name].append(dict()) # [2] dir: [subdir, [items]]
+
+ # cache stuff (directory lookuped up the in the last 5 seconds?)
+ if int(time.time() - theme_cache[theme.name][0]) >= xdg.Config.cache_time:
+ theme_cache[theme.name][0] = time.time()
+ for subdir in theme.getDirectories():
+ for directory in icondirs:
+ dir = os.path.join(directory,theme.name,subdir)
+ if (dir not in theme_cache[theme.name][2] \
+ or theme_cache[theme.name][1] < os.path.getmtime(os.path.join(directory,theme.name))) \
+ and subdir != "" \
+ and os.path.isdir(dir):
+ theme_cache[theme.name][2][dir] = [subdir, os.listdir(dir)]
+ theme_cache[theme.name][1] = os.path.getmtime(os.path.join(directory,theme.name))
+
+ for dir, values in theme_cache[theme.name][2].items():
+ if DirectoryMatchesSize(values[0], size, theme):
+ for extension in extensions:
+ if iconname + "." + extension in values[1]:
+ return os.path.join(dir, iconname + "." + extension)
+
+ minimal_size = 2**31
+ closest_filename = ""
+ for dir, values in theme_cache[theme.name][2].items():
+ distance = DirectorySizeDistance(values[0], size, theme)
+ if distance < minimal_size:
+ for extension in extensions:
+ if iconname + "." + extension in values[1]:
+ closest_filename = os.path.join(dir, iconname + "." + extension)
+ minimal_size = distance
+
+ return closest_filename
+
+def DirectoryMatchesSize(subdir, iconsize, theme):
+ Type = theme.getType(subdir)
+ Size = theme.getSize(subdir)
+ Threshold = theme.getThreshold(subdir)
+ MinSize = theme.getMinSize(subdir)
+ MaxSize = theme.getMaxSize(subdir)
+ if Type == "Fixed":
+ return Size == iconsize
+ elif Type == "Scaleable":
+ return MinSize <= iconsize <= MaxSize
+ elif Type == "Threshold":
+ return Size - Threshold <= iconsize <= Size + Threshold
+
+def DirectorySizeDistance(subdir, iconsize, theme):
+ Type = theme.getType(subdir)
+ Size = theme.getSize(subdir)
+ Threshold = theme.getThreshold(subdir)
+ MinSize = theme.getMinSize(subdir)
+ MaxSize = theme.getMaxSize(subdir)
+ if Type == "Fixed":
+ return abs(Size - iconsize)
+ elif Type == "Scalable":
+ if iconsize < MinSize:
+ return MinSize - iconsize
+ elif iconsize > MaxSize:
+ return MaxSize - iconsize
+ return 0
+ elif Type == "Threshold":
+ if iconsize < Size - Threshold:
+ return MinSize - iconsize
+ elif iconsize > Size + Threshold:
+ return iconsize - MaxSize
+ return 0
diff --git a/src/versions/pyfm-0.0.1/PyFM/new/pyfm/shellfm/windows/view/icons/mixins/xdg/IniFile.py b/src/versions/pyfm-0.0.1/PyFM/new/pyfm/shellfm/windows/view/icons/mixins/xdg/IniFile.py
new file mode 100644
index 0000000..74ab858
--- /dev/null
+++ b/src/versions/pyfm-0.0.1/PyFM/new/pyfm/shellfm/windows/view/icons/mixins/xdg/IniFile.py
@@ -0,0 +1,419 @@
+"""
+Base Class for DesktopEntry, IconTheme and IconData
+"""
+
+import re, os, stat, io
+from .Exceptions import (ParsingError, DuplicateGroupError, NoGroupError,
+ NoKeyError, DuplicateKeyError, ValidationError,
+ debug)
+# import xdg.Locale
+from . import Locale
+from .util import u
+
+def is_ascii(s):
+ """Return True if a string consists entirely of ASCII characters."""
+ try:
+ s.encode('ascii', 'strict')
+ return True
+ except UnicodeError:
+ return False
+
+class IniFile:
+ defaultGroup = ''
+ fileExtension = ''
+
+ filename = ''
+
+ tainted = False
+
+ def __init__(self, filename=None):
+ self.content = dict()
+ if filename:
+ self.parse(filename)
+
+ def __cmp__(self, other):
+ return cmp(self.content, other.content)
+
+ def parse(self, filename, headers=None):
+ '''Parse an INI file.
+
+ headers -- list of headers the parser will try to select as a default header
+ '''
+ # for performance reasons
+ content = self.content
+
+ if not os.path.isfile(filename):
+ raise ParsingError("File not found", filename)
+
+ try:
+ # The content should be UTF-8, but legacy files can have other
+ # encodings, including mixed encodings in one file. We don't attempt
+ # to decode them, but we silence the errors.
+ fd = io.open(filename, 'r', encoding='utf-8', errors='replace')
+ except IOError as e:
+ if debug:
+ raise e
+ else:
+ return
+
+ # parse file
+ for line in fd:
+ line = line.strip()
+ # empty line
+ if not line:
+ continue
+ # comment
+ elif line[0] == '#':
+ continue
+ # new group
+ elif line[0] == '[':
+ currentGroup = line.lstrip("[").rstrip("]")
+ if debug and self.hasGroup(currentGroup):
+ raise DuplicateGroupError(currentGroup, filename)
+ else:
+ content[currentGroup] = {}
+ # key
+ else:
+ try:
+ key, value = line.split("=", 1)
+ except ValueError:
+ raise ParsingError("Invalid line: " + line, filename)
+
+ key = key.strip() # Spaces before/after '=' should be ignored
+ try:
+ if debug and self.hasKey(key, currentGroup):
+ raise DuplicateKeyError(key, currentGroup, filename)
+ else:
+ content[currentGroup][key] = value.strip()
+ except (IndexError, UnboundLocalError):
+ raise ParsingError("Parsing error on key, group missing", filename)
+
+ fd.close()
+
+ self.filename = filename
+ self.tainted = False
+
+ # check header
+ if headers:
+ for header in headers:
+ if header in content:
+ self.defaultGroup = header
+ break
+ else:
+ raise ParsingError("[%s]-Header missing" % headers[0], filename)
+
+ # start stuff to access the keys
+ def get(self, key, group=None, locale=False, type="string", list=False, strict=False):
+ # set default group
+ if not group:
+ group = self.defaultGroup
+
+ # return key (with locale)
+ if (group in self.content) and (key in self.content[group]):
+ if locale:
+ value = self.content[group][self.__addLocale(key, group)]
+ else:
+ value = self.content[group][key]
+ else:
+ if strict or debug:
+ if group not in self.content:
+ raise NoGroupError(group, self.filename)
+ elif key not in self.content[group]:
+ raise NoKeyError(key, group, self.filename)
+ else:
+ value = ""
+
+ if list == True:
+ values = self.getList(value)
+ result = []
+ else:
+ values = [value]
+
+ for value in values:
+ if type == "boolean":
+ value = self.__getBoolean(value)
+ elif type == "integer":
+ try:
+ value = int(value)
+ except ValueError:
+ value = 0
+ elif type == "numeric":
+ try:
+ value = float(value)
+ except ValueError:
+ value = 0.0
+ elif type == "regex":
+ value = re.compile(value)
+ elif type == "point":
+ x, y = value.split(",")
+ value = int(x), int(y)
+
+ if list == True:
+ result.append(value)
+ else:
+ result = value
+
+ return result
+ # end stuff to access the keys
+
+ # start subget
+ def getList(self, string):
+ if re.search(r"(? 0:
+ key = key + "[" + xdg.Locale.langs[0] + "]"
+
+ try:
+ self.content[group][key] = value
+ except KeyError:
+ raise NoGroupError(group, self.filename)
+
+ self.tainted = (value == self.get(key, group))
+
+ def addGroup(self, group):
+ if self.hasGroup(group):
+ if debug:
+ raise DuplicateGroupError(group, self.filename)
+ else:
+ self.content[group] = {}
+ self.tainted = True
+
+ def removeGroup(self, group):
+ existed = group in self.content
+ if existed:
+ del self.content[group]
+ self.tainted = True
+ else:
+ if debug:
+ raise NoGroupError(group, self.filename)
+ return existed
+
+ def removeKey(self, key, group=None, locales=True):
+ # set default group
+ if not group:
+ group = self.defaultGroup
+
+ try:
+ if locales:
+ for name in list(self.content[group]):
+ if re.match("^" + key + xdg.Locale.regex + "$", name) and name != key:
+ del self.content[group][name]
+ value = self.content[group].pop(key)
+ self.tainted = True
+ return value
+ except KeyError as e:
+ if debug:
+ if e == group:
+ raise NoGroupError(group, self.filename)
+ else:
+ raise NoKeyError(key, group, self.filename)
+ else:
+ return ""
+
+ # misc
+ def groups(self):
+ return self.content.keys()
+
+ def hasGroup(self, group):
+ return group in self.content
+
+ def hasKey(self, key, group=None):
+ # set default group
+ if not group:
+ group = self.defaultGroup
+
+ return key in self.content[group]
+
+ def getFileName(self):
+ return self.filename
diff --git a/src/versions/pyfm-0.0.1/PyFM/new/pyfm/shellfm/windows/view/icons/mixins/xdg/Locale.py b/src/versions/pyfm-0.0.1/PyFM/new/pyfm/shellfm/windows/view/icons/mixins/xdg/Locale.py
new file mode 100644
index 0000000..d0a70d2
--- /dev/null
+++ b/src/versions/pyfm-0.0.1/PyFM/new/pyfm/shellfm/windows/view/icons/mixins/xdg/Locale.py
@@ -0,0 +1,79 @@
+"""
+Helper Module for Locale settings
+
+This module is based on a ROX module (LGPL):
+
+http://cvs.sourceforge.net/viewcvs.py/rox/ROX-Lib2/python/rox/i18n.py?rev=1.3&view=log
+"""
+
+import os
+from locale import normalize
+
+regex = r"(\[([a-zA-Z]+)(_[a-zA-Z]+)?(\.[a-zA-Z0-9-]+)?(@[a-zA-Z]+)?\])?"
+
+def _expand_lang(locale):
+ locale = normalize(locale)
+ COMPONENT_CODESET = 1 << 0
+ COMPONENT_MODIFIER = 1 << 1
+ COMPONENT_TERRITORY = 1 << 2
+ # split up the locale into its base components
+ mask = 0
+ pos = locale.find('@')
+ if pos >= 0:
+ modifier = locale[pos:]
+ locale = locale[:pos]
+ mask |= COMPONENT_MODIFIER
+ else:
+ modifier = ''
+ pos = locale.find('.')
+ codeset = ''
+ if pos >= 0:
+ locale = locale[:pos]
+ pos = locale.find('_')
+ if pos >= 0:
+ territory = locale[pos:]
+ locale = locale[:pos]
+ mask |= COMPONENT_TERRITORY
+ else:
+ territory = ''
+ language = locale
+ ret = []
+ for i in range(mask+1):
+ if not (i & ~mask): # if all components for this combo exist ...
+ val = language
+ if i & COMPONENT_TERRITORY: val += territory
+ if i & COMPONENT_CODESET: val += codeset
+ if i & COMPONENT_MODIFIER: val += modifier
+ ret.append(val)
+ ret.reverse()
+ return ret
+
+def expand_languages(languages=None):
+ # Get some reasonable defaults for arguments that were not supplied
+ if languages is None:
+ languages = []
+ for envar in ('LANGUAGE', 'LC_ALL', 'LC_MESSAGES', 'LANG'):
+ val = os.environ.get(envar)
+ if val:
+ languages = val.split(':')
+ break
+ #if 'C' not in languages:
+ # languages.append('C')
+
+ # now normalize and expand the languages
+ nelangs = []
+ for lang in languages:
+ for nelang in _expand_lang(lang):
+ if nelang not in nelangs:
+ nelangs.append(nelang)
+ return nelangs
+
+def update(language=None):
+ global langs
+ if language:
+ langs = expand_languages([language])
+ else:
+ langs = expand_languages()
+
+langs = []
+update()
diff --git a/src/versions/pyfm-0.0.1/PyFM/new/pyfm/shellfm/windows/view/icons/mixins/xdg/Menu.py b/src/versions/pyfm-0.0.1/PyFM/new/pyfm/shellfm/windows/view/icons/mixins/xdg/Menu.py
new file mode 100644
index 0000000..fcf1ac1
--- /dev/null
+++ b/src/versions/pyfm-0.0.1/PyFM/new/pyfm/shellfm/windows/view/icons/mixins/xdg/Menu.py
@@ -0,0 +1,1125 @@
+"""
+Implementation of the XDG Menu Specification
+http://standards.freedesktop.org/menu-spec/
+
+Example code:
+
+from xdg.Menu import parse, Menu, MenuEntry
+
+def print_menu(menu, tab=0):
+ for submenu in menu.Entries:
+ if isinstance(submenu, Menu):
+ print ("\t" * tab) + unicode(submenu)
+ print_menu(submenu, tab+1)
+ elif isinstance(submenu, MenuEntry):
+ print ("\t" * tab) + unicode(submenu.DesktopEntry)
+
+print_menu(parse())
+"""
+
+import os
+import locale
+import subprocess
+import ast
+try:
+ import xml.etree.cElementTree as etree
+except ImportError:
+ import xml.etree.ElementTree as etree
+
+from .BaseDirectory import xdg_data_dirs, xdg_config_dirs
+from . import DesktopEntry, Locale, Config
+from .Exceptions import ParsingError
+from .util import PY3
+
+
+def _strxfrm(s):
+ """Wrapper around locale.strxfrm that accepts unicode strings on Python 2.
+
+ See Python bug #2481.
+ """
+ if (not PY3) and isinstance(s, unicode):
+ s = s.encode('utf-8')
+ return locale.strxfrm(s)
+
+
+DELETED = "Deleted"
+NO_DISPLAY = "NoDisplay"
+HIDDEN = "Hidden"
+EMPTY = "Empty"
+NOT_SHOW_IN = "NotShowIn"
+NO_EXEC = "NoExec"
+
+
+class Menu:
+ """Menu containing sub menus under menu.Entries
+
+ Contains both Menu and MenuEntry items.
+ """
+ def __init__(self):
+ # Public stuff
+ self.Name = ""
+ self.Directory = None
+ self.Entries = []
+ self.Doc = ""
+ self.Filename = ""
+ self.Depth = 0
+ self.Parent = None
+ self.NotInXml = False
+
+ # Can be True, False, DELETED, NO_DISPLAY, HIDDEN, EMPTY or NOT_SHOW_IN
+ self.Show = True
+ self.Visible = 0
+
+ # Private stuff, only needed for parsing
+ self.AppDirs = []
+ self.DefaultLayout = None
+ self.Deleted = None
+ self.Directories = []
+ self.DirectoryDirs = []
+ self.Layout = None
+ self.MenuEntries = []
+ self.Moves = []
+ self.OnlyUnallocated = None
+ self.Rules = []
+ self.Submenus = []
+
+ def __str__(self):
+ return self.Name
+
+ def __add__(self, other):
+ for dir in other.AppDirs:
+ self.AppDirs.append(dir)
+
+ for dir in other.DirectoryDirs:
+ self.DirectoryDirs.append(dir)
+
+ for directory in other.Directories:
+ self.Directories.append(directory)
+
+ if other.Deleted is not None:
+ self.Deleted = other.Deleted
+
+ if other.OnlyUnallocated is not None:
+ self.OnlyUnallocated = other.OnlyUnallocated
+
+ if other.Layout:
+ self.Layout = other.Layout
+
+ if other.DefaultLayout:
+ self.DefaultLayout = other.DefaultLayout
+
+ for rule in other.Rules:
+ self.Rules.append(rule)
+
+ for move in other.Moves:
+ self.Moves.append(move)
+
+ for submenu in other.Submenus:
+ self.addSubmenu(submenu)
+
+ return self
+
+ # FIXME: Performance: cache getName()
+ def __cmp__(self, other):
+ return locale.strcoll(self.getName(), other.getName())
+
+ def _key(self):
+ """Key function for locale-aware sorting."""
+ return _strxfrm(self.getName())
+
+ def __lt__(self, other):
+ try:
+ other = other._key()
+ except AttributeError:
+ pass
+ return self._key() < other
+
+ def __eq__(self, other):
+ try:
+ return self.Name == unicode(other)
+ except NameError: # unicode() becomes str() in Python 3
+ return self.Name == str(other)
+
+ """ PUBLIC STUFF """
+ def getEntries(self, show_hidden=False):
+ """Interator for a list of Entries visible to the user."""
+ for entry in self.Entries:
+ if show_hidden:
+ yield entry
+ elif entry.Show is True:
+ yield entry
+
+ # FIXME: Add searchEntry/seaqrchMenu function
+ # search for name/comment/genericname/desktopfileid
+ # return multiple items
+
+ def getMenuEntry(self, desktopfileid, deep=False):
+ """Searches for a MenuEntry with a given DesktopFileID."""
+ for menuentry in self.MenuEntries:
+ if menuentry.DesktopFileID == desktopfileid:
+ return menuentry
+ if deep:
+ for submenu in self.Submenus:
+ submenu.getMenuEntry(desktopfileid, deep)
+
+ def getMenu(self, path):
+ """Searches for a Menu with a given path."""
+ array = path.split("/", 1)
+ for submenu in self.Submenus:
+ if submenu.Name == array[0]:
+ if len(array) > 1:
+ return submenu.getMenu(array[1])
+ else:
+ return submenu
+
+ def getPath(self, org=False, toplevel=False):
+ """Returns this menu's path in the menu structure."""
+ parent = self
+ names = []
+ while 1:
+ if org:
+ names.append(parent.Name)
+ else:
+ names.append(parent.getName())
+ if parent.Depth > 0:
+ parent = parent.Parent
+ else:
+ break
+ names.reverse()
+ path = ""
+ if not toplevel:
+ names.pop(0)
+ for name in names:
+ path = os.path.join(path, name)
+ return path
+
+ def getName(self):
+ """Returns the menu's localised name."""
+ try:
+ return self.Directory.DesktopEntry.getName()
+ except AttributeError:
+ return self.Name
+
+ def getGenericName(self):
+ """Returns the menu's generic name."""
+ try:
+ return self.Directory.DesktopEntry.getGenericName()
+ except AttributeError:
+ return ""
+
+ def getComment(self):
+ """Returns the menu's comment text."""
+ try:
+ return self.Directory.DesktopEntry.getComment()
+ except AttributeError:
+ return ""
+
+ def getIcon(self):
+ """Returns the menu's icon, filename or simple name"""
+ try:
+ return self.Directory.DesktopEntry.getIcon()
+ except AttributeError:
+ return ""
+
+ def sort(self):
+ self.Entries = []
+ self.Visible = 0
+
+ for submenu in self.Submenus:
+ submenu.sort()
+
+ _submenus = set()
+ _entries = set()
+
+ for order in self.Layout.order:
+ if order[0] == "Filename":
+ _entries.add(order[1])
+ elif order[0] == "Menuname":
+ _submenus.add(order[1])
+
+ for order in self.Layout.order:
+ if order[0] == "Separator":
+ separator = Separator(self)
+ if len(self.Entries) > 0 and isinstance(self.Entries[-1], Separator):
+ separator.Show = False
+ self.Entries.append(separator)
+ elif order[0] == "Filename":
+ menuentry = self.getMenuEntry(order[1])
+ if menuentry:
+ self.Entries.append(menuentry)
+ elif order[0] == "Menuname":
+ submenu = self.getMenu(order[1])
+ if submenu:
+ if submenu.Layout.inline:
+ self.merge_inline(submenu)
+ else:
+ self.Entries.append(submenu)
+ elif order[0] == "Merge":
+ if order[1] == "files" or order[1] == "all":
+ self.MenuEntries.sort()
+ for menuentry in self.MenuEntries:
+ if menuentry.DesktopFileID not in _entries:
+ self.Entries.append(menuentry)
+ elif order[1] == "menus" or order[1] == "all":
+ self.Submenus.sort()
+ for submenu in self.Submenus:
+ if submenu.Name not in _submenus:
+ if submenu.Layout.inline:
+ self.merge_inline(submenu)
+ else:
+ self.Entries.append(submenu)
+
+ # getHidden / NoDisplay / OnlyShowIn / NotOnlyShowIn / Deleted / NoExec
+ for entry in self.Entries:
+ entry.Show = True
+ self.Visible += 1
+ if isinstance(entry, Menu):
+ if entry.Deleted is True:
+ entry.Show = DELETED
+ self.Visible -= 1
+ elif isinstance(entry.Directory, MenuEntry):
+ if entry.Directory.DesktopEntry.getNoDisplay():
+ entry.Show = NO_DISPLAY
+ self.Visible -= 1
+ elif entry.Directory.DesktopEntry.getHidden():
+ entry.Show = HIDDEN
+ self.Visible -= 1
+ elif isinstance(entry, MenuEntry):
+ if entry.DesktopEntry.getNoDisplay():
+ entry.Show = NO_DISPLAY
+ self.Visible -= 1
+ elif entry.DesktopEntry.getHidden():
+ entry.Show = HIDDEN
+ self.Visible -= 1
+ elif entry.DesktopEntry.getTryExec() and not entry.DesktopEntry.findTryExec():
+ entry.Show = NO_EXEC
+ self.Visible -= 1
+ elif xdg.Config.windowmanager:
+ if (entry.DesktopEntry.OnlyShowIn != [] and (
+ xdg.Config.windowmanager not in entry.DesktopEntry.OnlyShowIn
+ )
+ ) or (
+ xdg.Config.windowmanager in entry.DesktopEntry.NotShowIn
+ ):
+ entry.Show = NOT_SHOW_IN
+ self.Visible -= 1
+ elif isinstance(entry, Separator):
+ self.Visible -= 1
+ # remove separators at the beginning and at the end
+ if len(self.Entries) > 0:
+ if isinstance(self.Entries[0], Separator):
+ self.Entries[0].Show = False
+ if len(self.Entries) > 1:
+ if isinstance(self.Entries[-1], Separator):
+ self.Entries[-1].Show = False
+
+ # show_empty tag
+ for entry in self.Entries[:]:
+ if isinstance(entry, Menu) and not entry.Layout.show_empty and entry.Visible == 0:
+ entry.Show = EMPTY
+ self.Visible -= 1
+ if entry.NotInXml is True:
+ self.Entries.remove(entry)
+
+ """ PRIVATE STUFF """
+ def addSubmenu(self, newmenu):
+ for submenu in self.Submenus:
+ if submenu == newmenu:
+ submenu += newmenu
+ break
+ else:
+ self.Submenus.append(newmenu)
+ newmenu.Parent = self
+ newmenu.Depth = self.Depth + 1
+
+ # inline tags
+ def merge_inline(self, submenu):
+ """Appends a submenu's entries to this menu
+ See the section of the spec about the "inline" attribute
+ """
+ if len(submenu.Entries) == 1 and submenu.Layout.inline_alias:
+ menuentry = submenu.Entries[0]
+ menuentry.DesktopEntry.set("Name", submenu.getName(), locale=True)
+ menuentry.DesktopEntry.set("GenericName", submenu.getGenericName(), locale=True)
+ menuentry.DesktopEntry.set("Comment", submenu.getComment(), locale=True)
+ self.Entries.append(menuentry)
+ elif len(submenu.Entries) <= submenu.Layout.inline_limit or submenu.Layout.inline_limit == 0:
+ if submenu.Layout.inline_header:
+ header = Header(submenu.getName(), submenu.getGenericName(), submenu.getComment())
+ self.Entries.append(header)
+ for entry in submenu.Entries:
+ self.Entries.append(entry)
+ else:
+ self.Entries.append(submenu)
+
+
+class Move:
+ "A move operation"
+ def __init__(self, old="", new=""):
+ self.Old = old
+ self.New = new
+
+ def __cmp__(self, other):
+ return cmp(self.Old, other.Old)
+
+
+class Layout:
+ "Menu Layout class"
+ def __init__(self, show_empty=False, inline=False, inline_limit=4,
+ inline_header=True, inline_alias=False):
+ self.show_empty = show_empty
+ self.inline = inline
+ self.inline_limit = inline_limit
+ self.inline_header = inline_header
+ self.inline_alias = inline_alias
+ self._order = []
+ self._default_order = [
+ ['Merge', 'menus'],
+ ['Merge', 'files']
+ ]
+
+ @property
+ def order(self):
+ return self._order if self._order else self._default_order
+
+ @order.setter
+ def order(self, order):
+ self._order = order
+
+
+class Rule:
+ """Include / Exclude Rules Class"""
+
+ TYPE_INCLUDE, TYPE_EXCLUDE = 0, 1
+
+ @classmethod
+ def fromFilename(cls, type, filename):
+ tree = ast.Expression(
+ body=ast.Compare(
+ left=ast.Str(filename),
+ ops=[ast.Eq()],
+ comparators=[ast.Attribute(
+ value=ast.Name(id='menuentry', ctx=ast.Load()),
+ attr='DesktopFileID',
+ ctx=ast.Load()
+ )]
+ ),
+ lineno=1, col_offset=0
+ )
+ ast.fix_missing_locations(tree)
+ rule = Rule(type, tree)
+ return rule
+
+ def __init__(self, type, expression):
+ # Type is TYPE_INCLUDE or TYPE_EXCLUDE
+ self.Type = type
+ # expression is ast.Expression
+ self.expression = expression
+ self.code = compile(self.expression, '', 'eval')
+
+ def __str__(self):
+ return ast.dump(self.expression)
+
+ def apply(self, menuentries, run):
+ for menuentry in menuentries:
+ if run == 2 and (menuentry.MatchedInclude is True or
+ menuentry.Allocated is True):
+ continue
+ if eval(self.code):
+ if self.Type is Rule.TYPE_INCLUDE:
+ menuentry.Add = True
+ menuentry.MatchedInclude = True
+ else:
+ menuentry.Add = False
+ return menuentries
+
+
+class MenuEntry:
+ "Wrapper for 'Menu Style' Desktop Entries"
+
+ TYPE_USER = "User"
+ TYPE_SYSTEM = "System"
+ TYPE_BOTH = "Both"
+
+ def __init__(self, filename, dir="", prefix=""):
+ # Create entry
+ self.DesktopEntry = DesktopEntry(os.path.join(dir, filename))
+ self.setAttributes(filename, dir, prefix)
+
+ # Can True, False DELETED, HIDDEN, EMPTY, NOT_SHOW_IN or NO_EXEC
+ self.Show = True
+
+ # Semi-Private
+ self.Original = None
+ self.Parents = []
+
+ # Private Stuff
+ self.Allocated = False
+ self.Add = False
+ self.MatchedInclude = False
+
+ # Caching
+ self.Categories = self.DesktopEntry.getCategories()
+
+ def save(self):
+ """Save any changes to the desktop entry."""
+ if self.DesktopEntry.tainted:
+ self.DesktopEntry.write()
+
+ def getDir(self):
+ """Return the directory containing the desktop entry file."""
+ return self.DesktopEntry.filename.replace(self.Filename, '')
+
+ def getType(self):
+ """Return the type of MenuEntry, System/User/Both"""
+ if not xdg.Config.root_mode:
+ if self.Original:
+ return self.TYPE_BOTH
+ elif xdg_data_dirs[0] in self.DesktopEntry.filename:
+ return self.TYPE_USER
+ else:
+ return self.TYPE_SYSTEM
+ else:
+ return self.TYPE_USER
+
+ def setAttributes(self, filename, dir="", prefix=""):
+ self.Filename = filename
+ self.Prefix = prefix
+ self.DesktopFileID = os.path.join(prefix, filename).replace("/", "-")
+
+ if not os.path.isabs(self.DesktopEntry.filename):
+ self.__setFilename()
+
+ def updateAttributes(self):
+ if self.getType() == self.TYPE_SYSTEM:
+ self.Original = MenuEntry(self.Filename, self.getDir(), self.Prefix)
+ self.__setFilename()
+
+ def __setFilename(self):
+ if not xdg.Config.root_mode:
+ path = xdg_data_dirs[0]
+ else:
+ path = xdg_data_dirs[1]
+
+ if self.DesktopEntry.getType() == "Application":
+ dir_ = os.path.join(path, "applications")
+ else:
+ dir_ = os.path.join(path, "desktop-directories")
+
+ self.DesktopEntry.filename = os.path.join(dir_, self.Filename)
+
+ def __cmp__(self, other):
+ return locale.strcoll(self.DesktopEntry.getName(), other.DesktopEntry.getName())
+
+ def _key(self):
+ """Key function for locale-aware sorting."""
+ return _strxfrm(self.DesktopEntry.getName())
+
+ def __lt__(self, other):
+ try:
+ other = other._key()
+ except AttributeError:
+ pass
+ return self._key() < other
+
+ def __eq__(self, other):
+ if self.DesktopFileID == str(other):
+ return True
+ else:
+ return False
+
+ def __repr__(self):
+ return self.DesktopFileID
+
+
+class Separator:
+ "Just a dummy class for Separators"
+ def __init__(self, parent):
+ self.Parent = parent
+ self.Show = True
+
+
+class Header:
+ "Class for Inline Headers"
+ def __init__(self, name, generic_name, comment):
+ self.Name = name
+ self.GenericName = generic_name
+ self.Comment = comment
+
+ def __str__(self):
+ return self.Name
+
+
+TYPE_DIR, TYPE_FILE = 0, 1
+
+
+def _check_file_path(value, filename, type):
+ path = os.path.dirname(filename)
+ if not os.path.isabs(value):
+ value = os.path.join(path, value)
+ value = os.path.abspath(value)
+ if not os.path.exists(value):
+ return False
+ if type == TYPE_DIR and os.path.isdir(value):
+ return value
+ if type == TYPE_FILE and os.path.isfile(value):
+ return value
+ return False
+
+
+def _get_menu_file_path(filename):
+ dirs = list(xdg_config_dirs)
+ if xdg.Config.root_mode is True:
+ dirs.pop(0)
+ for d in dirs:
+ menuname = os.path.join(d, "menus", filename)
+ if os.path.isfile(menuname):
+ return menuname
+
+
+def _to_bool(value):
+ if isinstance(value, bool):
+ return value
+ return value.lower() == "true"
+
+
+# remove duplicate entries from a list
+def _dedupe(_list):
+ _set = {}
+ _list.reverse()
+ _list = [_set.setdefault(e, e) for e in _list if e not in _set]
+ _list.reverse()
+ return _list
+
+
+class XMLMenuBuilder(object):
+
+ def __init__(self, debug=False):
+ self.debug = debug
+
+ def parse(self, filename=None):
+ """Load an applications.menu file.
+
+ filename : str, optional
+ The default is ``$XDG_CONFIG_DIRS/menus/${XDG_MENU_PREFIX}applications.menu``.
+ """
+ # convert to absolute path
+ if filename and not os.path.isabs(filename):
+ filename = _get_menu_file_path(filename)
+ # use default if no filename given
+ if not filename:
+ candidate = os.environ.get('XDG_MENU_PREFIX', '') + "applications.menu"
+ filename = _get_menu_file_path(candidate)
+ if not filename:
+ raise ParsingError('File not found', "/etc/xdg/menus/%s" % candidate)
+ # check if it is a .menu file
+ if not filename.endswith(".menu"):
+ raise ParsingError('Not a .menu file', filename)
+ # create xml parser
+ try:
+ tree = etree.parse(filename)
+ except:
+ raise ParsingError('Not a valid .menu file', filename)
+
+ # parse menufile
+ self._merged_files = set()
+ self._directory_dirs = set()
+ self.cache = MenuEntryCache()
+
+ menu = self.parse_menu(tree.getroot(), filename)
+ menu.tree = tree
+ menu.filename = filename
+
+ self.handle_moves(menu)
+ self.post_parse(menu)
+
+ # generate the menu
+ self.generate_not_only_allocated(menu)
+ self.generate_only_allocated(menu)
+
+ # and finally sort
+ menu.sort()
+
+ return menu
+
+ def parse_menu(self, node, filename):
+ menu = Menu()
+ self.parse_node(node, filename, menu)
+ return menu
+
+ def parse_node(self, node, filename, parent=None):
+ num_children = len(node)
+ for child in node:
+ tag, text = child.tag, child.text
+ text = text.strip() if text else None
+ if tag == 'Menu':
+ menu = self.parse_menu(child, filename)
+ parent.addSubmenu(menu)
+ elif tag == 'AppDir' and text:
+ self.parse_app_dir(text, filename, parent)
+ elif tag == 'DefaultAppDirs':
+ self.parse_default_app_dir(filename, parent)
+ elif tag == 'DirectoryDir' and text:
+ self.parse_directory_dir(text, filename, parent)
+ elif tag == 'DefaultDirectoryDirs':
+ self.parse_default_directory_dir(filename, parent)
+ elif tag == 'Name' and text:
+ parent.Name = text
+ elif tag == 'Directory' and text:
+ parent.Directories.append(text)
+ elif tag == 'OnlyUnallocated':
+ parent.OnlyUnallocated = True
+ elif tag == 'NotOnlyUnallocated':
+ parent.OnlyUnallocated = False
+ elif tag == 'Deleted':
+ parent.Deleted = True
+ elif tag == 'NotDeleted':
+ parent.Deleted = False
+ elif tag == 'Include' or tag == 'Exclude':
+ parent.Rules.append(self.parse_rule(child))
+ elif tag == 'MergeFile':
+ if child.attrib.get("type", None) == "parent":
+ self.parse_merge_file("applications.menu", child, filename, parent)
+ elif text:
+ self.parse_merge_file(text, child, filename, parent)
+ elif tag == 'MergeDir' and text:
+ self.parse_merge_dir(text, child, filename, parent)
+ elif tag == 'DefaultMergeDirs':
+ self.parse_default_merge_dirs(child, filename, parent)
+ elif tag == 'Move':
+ parent.Moves.append(self.parse_move(child))
+ elif tag == 'Layout':
+ if num_children > 1:
+ parent.Layout = self.parse_layout(child)
+ elif tag == 'DefaultLayout':
+ if num_children > 1:
+ parent.DefaultLayout = self.parse_layout(child)
+ elif tag == 'LegacyDir' and text:
+ self.parse_legacy_dir(text, child.attrib.get("prefix", ""), filename, parent)
+ elif tag == 'KDELegacyDirs':
+ self.parse_kde_legacy_dirs(filename, parent)
+
+ def parse_layout(self, node):
+ layout = Layout(
+ show_empty=_to_bool(node.attrib.get("show_empty", False)),
+ inline=_to_bool(node.attrib.get("inline", False)),
+ inline_limit=int(node.attrib.get("inline_limit", 4)),
+ inline_header=_to_bool(node.attrib.get("inline_header", True)),
+ inline_alias=_to_bool(node.attrib.get("inline_alias", False))
+ )
+ for child in node:
+ tag, text = child.tag, child.text
+ text = text.strip() if text else None
+ if tag == "Menuname" and text:
+ layout.order.append([
+ "Menuname",
+ text,
+ _to_bool(child.attrib.get("show_empty", False)),
+ _to_bool(child.attrib.get("inline", False)),
+ int(child.attrib.get("inline_limit", 4)),
+ _to_bool(child.attrib.get("inline_header", True)),
+ _to_bool(child.attrib.get("inline_alias", False))
+ ])
+ elif tag == "Separator":
+ layout.order.append(['Separator'])
+ elif tag == "Filename" and text:
+ layout.order.append(["Filename", text])
+ elif tag == "Merge":
+ layout.order.append([
+ "Merge",
+ child.attrib.get("type", "all")
+ ])
+ return layout
+
+ def parse_move(self, node):
+ old, new = "", ""
+ for child in node:
+ tag, text = child.tag, child.text
+ text = text.strip() if text else None
+ if tag == "Old" and text:
+ old = text
+ elif tag == "New" and text:
+ new = text
+ return Move(old, new)
+
+ # ---------- parsing
+
+ def parse_rule(self, node):
+ type = Rule.TYPE_INCLUDE if node.tag == 'Include' else Rule.TYPE_EXCLUDE
+ tree = ast.Expression(lineno=1, col_offset=0)
+ expr = self.parse_bool_op(node, ast.Or())
+ if expr:
+ tree.body = expr
+ else:
+ tree.body = ast.Name('False', ast.Load())
+ ast.fix_missing_locations(tree)
+ return Rule(type, tree)
+
+ def parse_bool_op(self, node, operator):
+ values = []
+ for child in node:
+ rule = self.parse_rule_node(child)
+ if rule:
+ values.append(rule)
+ num_values = len(values)
+ if num_values > 1:
+ return ast.BoolOp(operator, values)
+ elif num_values == 1:
+ return values[0]
+ return None
+
+ def parse_rule_node(self, node):
+ tag = node.tag
+ if tag == 'Or':
+ return self.parse_bool_op(node, ast.Or())
+ elif tag == 'And':
+ return self.parse_bool_op(node, ast.And())
+ elif tag == 'Not':
+ expr = self.parse_bool_op(node, ast.Or())
+ return ast.UnaryOp(ast.Not(), expr) if expr else None
+ elif tag == 'All':
+ return ast.Name('True', ast.Load())
+ elif tag == 'Category':
+ category = node.text
+ return ast.Compare(
+ left=ast.Str(category),
+ ops=[ast.In()],
+ comparators=[ast.Attribute(
+ value=ast.Name(id='menuentry', ctx=ast.Load()),
+ attr='Categories',
+ ctx=ast.Load()
+ )]
+ )
+ elif tag == 'Filename':
+ filename = node.text
+ return ast.Compare(
+ left=ast.Str(filename),
+ ops=[ast.Eq()],
+ comparators=[ast.Attribute(
+ value=ast.Name(id='menuentry', ctx=ast.Load()),
+ attr='DesktopFileID',
+ ctx=ast.Load()
+ )]
+ )
+
+ # ---------- App/Directory Dir Stuff
+
+ def parse_app_dir(self, value, filename, parent):
+ value = _check_file_path(value, filename, TYPE_DIR)
+ if value:
+ parent.AppDirs.append(value)
+
+ def parse_default_app_dir(self, filename, parent):
+ for d in reversed(xdg_data_dirs):
+ self.parse_app_dir(os.path.join(d, "applications"), filename, parent)
+
+ def parse_directory_dir(self, value, filename, parent):
+ value = _check_file_path(value, filename, TYPE_DIR)
+ if value:
+ parent.DirectoryDirs.append(value)
+
+ def parse_default_directory_dir(self, filename, parent):
+ for d in reversed(xdg_data_dirs):
+ self.parse_directory_dir(os.path.join(d, "desktop-directories"), filename, parent)
+
+ # ---------- Merge Stuff
+
+ def parse_merge_file(self, value, child, filename, parent):
+ if child.attrib.get("type", None) == "parent":
+ for d in xdg_config_dirs:
+ rel_file = filename.replace(d, "").strip("/")
+ if rel_file != filename:
+ for p in xdg_config_dirs:
+ if d == p:
+ continue
+ if os.path.isfile(os.path.join(p, rel_file)):
+ self.merge_file(os.path.join(p, rel_file), child, parent)
+ break
+ else:
+ value = _check_file_path(value, filename, TYPE_FILE)
+ if value:
+ self.merge_file(value, child, parent)
+
+ def parse_merge_dir(self, value, child, filename, parent):
+ value = _check_file_path(value, filename, TYPE_DIR)
+ if value:
+ for item in os.listdir(value):
+ try:
+ if item.endswith(".menu"):
+ self.merge_file(os.path.join(value, item), child, parent)
+ except UnicodeDecodeError:
+ continue
+
+ def parse_default_merge_dirs(self, child, filename, parent):
+ basename = os.path.splitext(os.path.basename(filename))[0]
+ for d in reversed(xdg_config_dirs):
+ self.parse_merge_dir(os.path.join(d, "menus", basename + "-merged"), child, filename, parent)
+
+ def merge_file(self, filename, child, parent):
+ # check for infinite loops
+ if filename in self._merged_files:
+ if self.debug:
+ raise ParsingError('Infinite MergeFile loop detected', filename)
+ else:
+ return
+ self._merged_files.add(filename)
+ # load file
+ try:
+ tree = etree.parse(filename)
+ except IOError:
+ if self.debug:
+ raise ParsingError('File not found', filename)
+ else:
+ return
+ except:
+ if self.debug:
+ raise ParsingError('Not a valid .menu file', filename)
+ else:
+ return
+ root = tree.getroot()
+ self.parse_node(root, filename, parent)
+
+ # ---------- Legacy Dir Stuff
+
+ def parse_legacy_dir(self, dir_, prefix, filename, parent):
+ m = self.merge_legacy_dir(dir_, prefix, filename, parent)
+ if m:
+ parent += m
+
+ def merge_legacy_dir(self, dir_, prefix, filename, parent):
+ dir_ = _check_file_path(dir_, filename, TYPE_DIR)
+ if dir_ and dir_ not in self._directory_dirs:
+ self._directory_dirs.add(dir_)
+ m = Menu()
+ m.AppDirs.append(dir_)
+ m.DirectoryDirs.append(dir_)
+ m.Name = os.path.basename(dir_)
+ m.NotInXml = True
+
+ for item in os.listdir(dir_):
+ try:
+ if item == ".directory":
+ m.Directories.append(item)
+ elif os.path.isdir(os.path.join(dir_, item)):
+ m.addSubmenu(self.merge_legacy_dir(
+ os.path.join(dir_, item),
+ prefix,
+ filename,
+ parent
+ ))
+ except UnicodeDecodeError:
+ continue
+
+ self.cache.add_menu_entries([dir_], prefix, True)
+ menuentries = self.cache.get_menu_entries([dir_], False)
+
+ for menuentry in menuentries:
+ categories = menuentry.Categories
+ if len(categories) == 0:
+ r = Rule.fromFilename(Rule.TYPE_INCLUDE, menuentry.DesktopFileID)
+ m.Rules.append(r)
+ if not dir_ in parent.AppDirs:
+ categories.append("Legacy")
+ menuentry.Categories = categories
+
+ return m
+
+ def parse_kde_legacy_dirs(self, filename, parent):
+ try:
+ proc = subprocess.Popen(
+ ['kde-config', '--path', 'apps'],
+ stdout=subprocess.PIPE,
+ universal_newlines=True
+ )
+ output = proc.communicate()[0].splitlines()
+ except OSError:
+ # If kde-config doesn't exist, ignore this.
+ return
+ try:
+ for dir_ in output[0].split(":"):
+ self.parse_legacy_dir(dir_, "kde", filename, parent)
+ except IndexError:
+ pass
+
+ def post_parse(self, menu):
+ # unallocated / deleted
+ if menu.Deleted is None:
+ menu.Deleted = False
+ if menu.OnlyUnallocated is None:
+ menu.OnlyUnallocated = False
+
+ # Layout Tags
+ if not menu.Layout or not menu.DefaultLayout:
+ if menu.DefaultLayout:
+ menu.Layout = menu.DefaultLayout
+ elif menu.Layout:
+ if menu.Depth > 0:
+ menu.DefaultLayout = menu.Parent.DefaultLayout
+ else:
+ menu.DefaultLayout = Layout()
+ else:
+ if menu.Depth > 0:
+ menu.Layout = menu.Parent.DefaultLayout
+ menu.DefaultLayout = menu.Parent.DefaultLayout
+ else:
+ menu.Layout = Layout()
+ menu.DefaultLayout = Layout()
+
+ # add parent's app/directory dirs
+ if menu.Depth > 0:
+ menu.AppDirs = menu.Parent.AppDirs + menu.AppDirs
+ menu.DirectoryDirs = menu.Parent.DirectoryDirs + menu.DirectoryDirs
+
+ # remove duplicates
+ menu.Directories = _dedupe(menu.Directories)
+ menu.DirectoryDirs = _dedupe(menu.DirectoryDirs)
+ menu.AppDirs = _dedupe(menu.AppDirs)
+
+ # go recursive through all menus
+ for submenu in menu.Submenus:
+ self.post_parse(submenu)
+
+ # reverse so handling is easier
+ menu.Directories.reverse()
+ menu.DirectoryDirs.reverse()
+ menu.AppDirs.reverse()
+
+ # get the valid .directory file out of the list
+ for directory in menu.Directories:
+ for dir in menu.DirectoryDirs:
+ if os.path.isfile(os.path.join(dir, directory)):
+ menuentry = MenuEntry(directory, dir)
+ if not menu.Directory:
+ menu.Directory = menuentry
+ elif menuentry.Type == MenuEntry.TYPE_SYSTEM:
+ if menu.Directory.Type == MenuEntry.TYPE_USER:
+ menu.Directory.Original = menuentry
+ if menu.Directory:
+ break
+
+ # Finally generate the menu
+ def generate_not_only_allocated(self, menu):
+ for submenu in menu.Submenus:
+ self.generate_not_only_allocated(submenu)
+
+ if menu.OnlyUnallocated is False:
+ self.cache.add_menu_entries(menu.AppDirs)
+ menuentries = []
+ for rule in menu.Rules:
+ menuentries = rule.apply(self.cache.get_menu_entries(menu.AppDirs), 1)
+
+ for menuentry in menuentries:
+ if menuentry.Add is True:
+ menuentry.Parents.append(menu)
+ menuentry.Add = False
+ menuentry.Allocated = True
+ menu.MenuEntries.append(menuentry)
+
+ def generate_only_allocated(self, menu):
+ for submenu in menu.Submenus:
+ self.generate_only_allocated(submenu)
+
+ if menu.OnlyUnallocated is True:
+ self.cache.add_menu_entries(menu.AppDirs)
+ menuentries = []
+ for rule in menu.Rules:
+ menuentries = rule.apply(self.cache.get_menu_entries(menu.AppDirs), 2)
+ for menuentry in menuentries:
+ if menuentry.Add is True:
+ menuentry.Parents.append(menu)
+ # menuentry.Add = False
+ # menuentry.Allocated = True
+ menu.MenuEntries.append(menuentry)
+
+ def handle_moves(self, menu):
+ for submenu in menu.Submenus:
+ self.handle_moves(submenu)
+ # parse move operations
+ for move in menu.Moves:
+ move_from_menu = menu.getMenu(move.Old)
+ if move_from_menu:
+ # FIXME: this is assigned, but never used...
+ move_to_menu = menu.getMenu(move.New)
+
+ menus = move.New.split("/")
+ oldparent = None
+ while len(menus) > 0:
+ if not oldparent:
+ oldparent = menu
+ newmenu = oldparent.getMenu(menus[0])
+ if not newmenu:
+ newmenu = Menu()
+ newmenu.Name = menus[0]
+ if len(menus) > 1:
+ newmenu.NotInXml = True
+ oldparent.addSubmenu(newmenu)
+ oldparent = newmenu
+ menus.pop(0)
+
+ newmenu += move_from_menu
+ move_from_menu.Parent.Submenus.remove(move_from_menu)
+
+
+class MenuEntryCache:
+ "Class to cache Desktop Entries"
+ def __init__(self):
+ self.cacheEntries = {}
+ self.cacheEntries['legacy'] = []
+ self.cache = {}
+
+ def add_menu_entries(self, dirs, prefix="", legacy=False):
+ for dir_ in dirs:
+ if not dir_ in self.cacheEntries:
+ self.cacheEntries[dir_] = []
+ self.__addFiles(dir_, "", prefix, legacy)
+
+ def __addFiles(self, dir_, subdir, prefix, legacy):
+ for item in os.listdir(os.path.join(dir_, subdir)):
+ if item.endswith(".desktop"):
+ try:
+ menuentry = MenuEntry(os.path.join(subdir, item), dir_, prefix)
+ except ParsingError:
+ continue
+
+ self.cacheEntries[dir_].append(menuentry)
+ if legacy:
+ self.cacheEntries['legacy'].append(menuentry)
+ elif os.path.isdir(os.path.join(dir_, subdir, item)) and not legacy:
+ self.__addFiles(dir_, os.path.join(subdir, item), prefix, legacy)
+
+ def get_menu_entries(self, dirs, legacy=True):
+ entries = []
+ ids = set()
+ # handle legacy items
+ appdirs = dirs[:]
+ if legacy:
+ appdirs.append("legacy")
+ # cache the results again
+ key = "".join(appdirs)
+ try:
+ return self.cache[key]
+ except KeyError:
+ pass
+ for dir_ in appdirs:
+ for menuentry in self.cacheEntries[dir_]:
+ try:
+ if menuentry.DesktopFileID not in ids:
+ ids.add(menuentry.DesktopFileID)
+ entries.append(menuentry)
+ elif menuentry.getType() == MenuEntry.TYPE_SYSTEM:
+ # FIXME: This is only 99% correct, but still...
+ idx = entries.index(menuentry)
+ entry = entries[idx]
+ if entry.getType() == MenuEntry.TYPE_USER:
+ entry.Original = menuentry
+ except UnicodeDecodeError:
+ continue
+ self.cache[key] = entries
+ return entries
+
+
+def parse(filename=None, debug=False):
+ """Helper function.
+ Equivalent to calling xdg.Menu.XMLMenuBuilder().parse(filename)
+ """
+ return XMLMenuBuilder(debug).parse(filename)
diff --git a/src/versions/pyfm-0.0.1/PyFM/new/pyfm/shellfm/windows/view/icons/mixins/xdg/MenuEditor.py b/src/versions/pyfm-0.0.1/PyFM/new/pyfm/shellfm/windows/view/icons/mixins/xdg/MenuEditor.py
new file mode 100644
index 0000000..2c68515
--- /dev/null
+++ b/src/versions/pyfm-0.0.1/PyFM/new/pyfm/shellfm/windows/view/icons/mixins/xdg/MenuEditor.py
@@ -0,0 +1,541 @@
+""" CLass to edit XDG Menus """
+import os
+try:
+ import xml.etree.cElementTree as etree
+except ImportError:
+ import xml.etree.ElementTree as etree
+
+from .Menu import Menu, MenuEntry, Layout, Separator, XMLMenuBuilder
+from .BaseDirectory import xdg_config_dirs, xdg_data_dirs
+from .Exceptions import ParsingError
+from .Config import setRootMode
+
+# XML-Cleanups: Move / Exclude
+# FIXME: proper reverte/delete
+# FIXME: pass AppDirs/DirectoryDirs around in the edit/move functions
+# FIXME: catch Exceptions
+# FIXME: copy functions
+# FIXME: More Layout stuff
+# FIXME: unod/redo function / remove menu...
+# FIXME: Advanced MenuEditing Stuff: LegacyDir/MergeFile
+# Complex Rules/Deleted/OnlyAllocated/AppDirs/DirectoryDirs
+
+
+class MenuEditor(object):
+
+ def __init__(self, menu=None, filename=None, root=False):
+ self.menu = None
+ self.filename = None
+ self.tree = None
+ self.parser = XMLMenuBuilder()
+ self.parse(menu, filename, root)
+
+ # fix for creating two menus with the same name on the fly
+ self.filenames = []
+
+ def parse(self, menu=None, filename=None, root=False):
+ if root:
+ setRootMode(True)
+
+ if isinstance(menu, Menu):
+ self.menu = menu
+ elif menu:
+ self.menu = self.parser.parse(menu)
+ else:
+ self.menu = self.parser.parse()
+
+ if root:
+ self.filename = self.menu.Filename
+ elif filename:
+ self.filename = filename
+ else:
+ self.filename = os.path.join(xdg_config_dirs[0], "menus", os.path.split(self.menu.Filename)[1])
+
+ try:
+ self.tree = etree.parse(self.filename)
+ except IOError:
+ root = etree.fromtring("""
+
+
+""" % self.menu.Filename)
+ self.tree = etree.ElementTree(root)
+ except ParsingError:
+ raise ParsingError('Not a valid .menu file', self.filename)
+
+ #FIXME: is this needed with etree ?
+ self.__remove_whitespace_nodes(self.tree)
+
+ def save(self):
+ self.__saveEntries(self.menu)
+ self.__saveMenu()
+
+ def createMenuEntry(self, parent, name, command=None, genericname=None, comment=None, icon=None, terminal=None, after=None, before=None):
+ menuentry = MenuEntry(self.__getFileName(name, ".desktop"))
+ menuentry = self.editMenuEntry(menuentry, name, genericname, comment, command, icon, terminal)
+
+ self.__addEntry(parent, menuentry, after, before)
+
+ self.menu.sort()
+
+ return menuentry
+
+ def createMenu(self, parent, name, genericname=None, comment=None, icon=None, after=None, before=None):
+ menu = Menu()
+
+ menu.Parent = parent
+ menu.Depth = parent.Depth + 1
+ menu.Layout = parent.DefaultLayout
+ menu.DefaultLayout = parent.DefaultLayout
+
+ menu = self.editMenu(menu, name, genericname, comment, icon)
+
+ self.__addEntry(parent, menu, after, before)
+
+ self.menu.sort()
+
+ return menu
+
+ def createSeparator(self, parent, after=None, before=None):
+ separator = Separator(parent)
+
+ self.__addEntry(parent, separator, after, before)
+
+ self.menu.sort()
+
+ return separator
+
+ def moveMenuEntry(self, menuentry, oldparent, newparent, after=None, before=None):
+ self.__deleteEntry(oldparent, menuentry, after, before)
+ self.__addEntry(newparent, menuentry, after, before)
+
+ self.menu.sort()
+
+ return menuentry
+
+ def moveMenu(self, menu, oldparent, newparent, after=None, before=None):
+ self.__deleteEntry(oldparent, menu, after, before)
+ self.__addEntry(newparent, menu, after, before)
+
+ root_menu = self.__getXmlMenu(self.menu.Name)
+ if oldparent.getPath(True) != newparent.getPath(True):
+ self.__addXmlMove(root_menu, os.path.join(oldparent.getPath(True), menu.Name), os.path.join(newparent.getPath(True), menu.Name))
+
+ self.menu.sort()
+
+ return menu
+
+ def moveSeparator(self, separator, parent, after=None, before=None):
+ self.__deleteEntry(parent, separator, after, before)
+ self.__addEntry(parent, separator, after, before)
+
+ self.menu.sort()
+
+ return separator
+
+ def copyMenuEntry(self, menuentry, oldparent, newparent, after=None, before=None):
+ self.__addEntry(newparent, menuentry, after, before)
+
+ self.menu.sort()
+
+ return menuentry
+
+ def editMenuEntry(self, menuentry, name=None, genericname=None, comment=None, command=None, icon=None, terminal=None, nodisplay=None, hidden=None):
+ deskentry = menuentry.DesktopEntry
+
+ if name:
+ if not deskentry.hasKey("Name"):
+ deskentry.set("Name", name)
+ deskentry.set("Name", name, locale=True)
+ if comment:
+ if not deskentry.hasKey("Comment"):
+ deskentry.set("Comment", comment)
+ deskentry.set("Comment", comment, locale=True)
+ if genericname:
+ if not deskentry.hasKey("GenericName"):
+ deskentry.set("GenericName", genericname)
+ deskentry.set("GenericName", genericname, locale=True)
+ if command:
+ deskentry.set("Exec", command)
+ if icon:
+ deskentry.set("Icon", icon)
+
+ if terminal:
+ deskentry.set("Terminal", "true")
+ elif not terminal:
+ deskentry.set("Terminal", "false")
+
+ if nodisplay is True:
+ deskentry.set("NoDisplay", "true")
+ elif nodisplay is False:
+ deskentry.set("NoDisplay", "false")
+
+ if hidden is True:
+ deskentry.set("Hidden", "true")
+ elif hidden is False:
+ deskentry.set("Hidden", "false")
+
+ menuentry.updateAttributes()
+
+ if len(menuentry.Parents) > 0:
+ self.menu.sort()
+
+ return menuentry
+
+ def editMenu(self, menu, name=None, genericname=None, comment=None, icon=None, nodisplay=None, hidden=None):
+ # Hack for legacy dirs
+ if isinstance(menu.Directory, MenuEntry) and menu.Directory.Filename == ".directory":
+ xml_menu = self.__getXmlMenu(menu.getPath(True, True))
+ self.__addXmlTextElement(xml_menu, 'Directory', menu.Name + ".directory")
+ menu.Directory.setAttributes(menu.Name + ".directory")
+ # Hack for New Entries
+ elif not isinstance(menu.Directory, MenuEntry):
+ if not name:
+ name = menu.Name
+ filename = self.__getFileName(name, ".directory").replace("/", "")
+ if not menu.Name:
+ menu.Name = filename.replace(".directory", "")
+ xml_menu = self.__getXmlMenu(menu.getPath(True, True))
+ self.__addXmlTextElement(xml_menu, 'Directory', filename)
+ menu.Directory = MenuEntry(filename)
+
+ deskentry = menu.Directory.DesktopEntry
+
+ if name:
+ if not deskentry.hasKey("Name"):
+ deskentry.set("Name", name)
+ deskentry.set("Name", name, locale=True)
+ if genericname:
+ if not deskentry.hasKey("GenericName"):
+ deskentry.set("GenericName", genericname)
+ deskentry.set("GenericName", genericname, locale=True)
+ if comment:
+ if not deskentry.hasKey("Comment"):
+ deskentry.set("Comment", comment)
+ deskentry.set("Comment", comment, locale=True)
+ if icon:
+ deskentry.set("Icon", icon)
+
+ if nodisplay is True:
+ deskentry.set("NoDisplay", "true")
+ elif nodisplay is False:
+ deskentry.set("NoDisplay", "false")
+
+ if hidden is True:
+ deskentry.set("Hidden", "true")
+ elif hidden is False:
+ deskentry.set("Hidden", "false")
+
+ menu.Directory.updateAttributes()
+
+ if isinstance(menu.Parent, Menu):
+ self.menu.sort()
+
+ return menu
+
+ def hideMenuEntry(self, menuentry):
+ self.editMenuEntry(menuentry, nodisplay=True)
+
+ def unhideMenuEntry(self, menuentry):
+ self.editMenuEntry(menuentry, nodisplay=False, hidden=False)
+
+ def hideMenu(self, menu):
+ self.editMenu(menu, nodisplay=True)
+
+ def unhideMenu(self, menu):
+ self.editMenu(menu, nodisplay=False, hidden=False)
+ xml_menu = self.__getXmlMenu(menu.getPath(True, True), False)
+ deleted = xml_menu.findall('Deleted')
+ not_deleted = xml_menu.findall('NotDeleted')
+ for node in deleted + not_deleted:
+ xml_menu.remove(node)
+
+ def deleteMenuEntry(self, menuentry):
+ if self.getAction(menuentry) == "delete":
+ self.__deleteFile(menuentry.DesktopEntry.filename)
+ for parent in menuentry.Parents:
+ self.__deleteEntry(parent, menuentry)
+ self.menu.sort()
+ return menuentry
+
+ def revertMenuEntry(self, menuentry):
+ if self.getAction(menuentry) == "revert":
+ self.__deleteFile(menuentry.DesktopEntry.filename)
+ menuentry.Original.Parents = []
+ for parent in menuentry.Parents:
+ index = parent.Entries.index(menuentry)
+ parent.Entries[index] = menuentry.Original
+ index = parent.MenuEntries.index(menuentry)
+ parent.MenuEntries[index] = menuentry.Original
+ menuentry.Original.Parents.append(parent)
+ self.menu.sort()
+ return menuentry
+
+ def deleteMenu(self, menu):
+ if self.getAction(menu) == "delete":
+ self.__deleteFile(menu.Directory.DesktopEntry.filename)
+ self.__deleteEntry(menu.Parent, menu)
+ xml_menu = self.__getXmlMenu(menu.getPath(True, True))
+ parent = self.__get_parent_node(xml_menu)
+ parent.remove(xml_menu)
+ self.menu.sort()
+ return menu
+
+ def revertMenu(self, menu):
+ if self.getAction(menu) == "revert":
+ self.__deleteFile(menu.Directory.DesktopEntry.filename)
+ menu.Directory = menu.Directory.Original
+ self.menu.sort()
+ return menu
+
+ def deleteSeparator(self, separator):
+ self.__deleteEntry(separator.Parent, separator, after=True)
+
+ self.menu.sort()
+
+ return separator
+
+ """ Private Stuff """
+ def getAction(self, entry):
+ if isinstance(entry, Menu):
+ if not isinstance(entry.Directory, MenuEntry):
+ return "none"
+ elif entry.Directory.getType() == "Both":
+ return "revert"
+ elif entry.Directory.getType() == "User" and (
+ len(entry.Submenus) + len(entry.MenuEntries)
+ ) == 0:
+ return "delete"
+
+ elif isinstance(entry, MenuEntry):
+ if entry.getType() == "Both":
+ return "revert"
+ elif entry.getType() == "User":
+ return "delete"
+ else:
+ return "none"
+
+ return "none"
+
+ def __saveEntries(self, menu):
+ if not menu:
+ menu = self.menu
+ if isinstance(menu.Directory, MenuEntry):
+ menu.Directory.save()
+ for entry in menu.getEntries(hidden=True):
+ if isinstance(entry, MenuEntry):
+ entry.save()
+ elif isinstance(entry, Menu):
+ self.__saveEntries(entry)
+
+ def __saveMenu(self):
+ if not os.path.isdir(os.path.dirname(self.filename)):
+ os.makedirs(os.path.dirname(self.filename))
+ self.tree.write(self.filename, encoding='utf-8')
+
+ def __getFileName(self, name, extension):
+ postfix = 0
+ while 1:
+ if postfix == 0:
+ filename = name + extension
+ else:
+ filename = name + "-" + str(postfix) + extension
+ if extension == ".desktop":
+ dir = "applications"
+ elif extension == ".directory":
+ dir = "desktop-directories"
+ if not filename in self.filenames and not os.path.isfile(
+ os.path.join(xdg_data_dirs[0], dir, filename)
+ ):
+ self.filenames.append(filename)
+ break
+ else:
+ postfix += 1
+
+ return filename
+
+ def __getXmlMenu(self, path, create=True, element=None):
+ # FIXME: we should also return the menu's parent,
+ # to avoid looking for it later on
+ # @see Element.getiterator()
+ if not element:
+ element = self.tree
+
+ if "/" in path:
+ (name, path) = path.split("/", 1)
+ else:
+ name = path
+ path = ""
+
+ found = None
+ for node in element.findall("Menu"):
+ name_node = node.find('Name')
+ if name_node.text == name:
+ if path:
+ found = self.__getXmlMenu(path, create, node)
+ else:
+ found = node
+ if found:
+ break
+ if not found and create:
+ node = self.__addXmlMenuElement(element, name)
+ if path:
+ found = self.__getXmlMenu(path, create, node)
+ else:
+ found = node
+
+ return found
+
+ def __addXmlMenuElement(self, element, name):
+ menu_node = etree.SubElement('Menu', element)
+ name_node = etree.SubElement('Name', menu_node)
+ name_node.text = name
+ return menu_node
+
+ def __addXmlTextElement(self, element, name, text):
+ node = etree.SubElement(name, element)
+ node.text = text
+ return node
+
+ def __addXmlFilename(self, element, filename, type_="Include"):
+ # remove old filenames
+ includes = element.findall('Include')
+ excludes = element.findall('Exclude')
+ rules = includes + excludes
+ for rule in rules:
+ #FIXME: this finds only Rules whose FIRST child is a Filename element
+ if rule[0].tag == "Filename" and rule[0].text == filename:
+ element.remove(rule)
+ # shouldn't it remove all occurences, like the following:
+ #filename_nodes = rule.findall('.//Filename'):
+ #for fn in filename_nodes:
+ #if fn.text == filename:
+ ##element.remove(rule)
+ #parent = self.__get_parent_node(fn)
+ #parent.remove(fn)
+
+ # add new filename
+ node = etree.SubElement(type_, element)
+ self.__addXmlTextElement(node, 'Filename', filename)
+ return node
+
+ def __addXmlMove(self, element, old, new):
+ node = etree.SubElement("Move", element)
+ self.__addXmlTextElement(node, 'Old', old)
+ self.__addXmlTextElement(node, 'New', new)
+ return node
+
+ def __addXmlLayout(self, element, layout):
+ # remove old layout
+ for node in element.findall("Layout"):
+ element.remove(node)
+
+ # add new layout
+ node = etree.SubElement("Layout", element)
+ for order in layout.order:
+ if order[0] == "Separator":
+ child = etree.SubElement("Separator", node)
+ elif order[0] == "Filename":
+ child = self.__addXmlTextElement(node, "Filename", order[1])
+ elif order[0] == "Menuname":
+ child = self.__addXmlTextElement(node, "Menuname", order[1])
+ elif order[0] == "Merge":
+ child = etree.SubElement("Merge", node)
+ child.attrib["type"] = order[1]
+ return node
+
+ def __addLayout(self, parent):
+ layout = Layout()
+ layout.order = []
+ layout.show_empty = parent.Layout.show_empty
+ layout.inline = parent.Layout.inline
+ layout.inline_header = parent.Layout.inline_header
+ layout.inline_alias = parent.Layout.inline_alias
+ layout.inline_limit = parent.Layout.inline_limit
+
+ layout.order.append(["Merge", "menus"])
+ for entry in parent.Entries:
+ if isinstance(entry, Menu):
+ layout.parseMenuname(entry.Name)
+ elif isinstance(entry, MenuEntry):
+ layout.parseFilename(entry.DesktopFileID)
+ elif isinstance(entry, Separator):
+ layout.parseSeparator()
+ layout.order.append(["Merge", "files"])
+
+ parent.Layout = layout
+
+ return layout
+
+ def __addEntry(self, parent, entry, after=None, before=None):
+ if after or before:
+ if after:
+ index = parent.Entries.index(after) + 1
+ elif before:
+ index = parent.Entries.index(before)
+ parent.Entries.insert(index, entry)
+ else:
+ parent.Entries.append(entry)
+
+ xml_parent = self.__getXmlMenu(parent.getPath(True, True))
+
+ if isinstance(entry, MenuEntry):
+ parent.MenuEntries.append(entry)
+ entry.Parents.append(parent)
+ self.__addXmlFilename(xml_parent, entry.DesktopFileID, "Include")
+ elif isinstance(entry, Menu):
+ parent.addSubmenu(entry)
+
+ if after or before:
+ self.__addLayout(parent)
+ self.__addXmlLayout(xml_parent, parent.Layout)
+
+ def __deleteEntry(self, parent, entry, after=None, before=None):
+ parent.Entries.remove(entry)
+
+ xml_parent = self.__getXmlMenu(parent.getPath(True, True))
+
+ if isinstance(entry, MenuEntry):
+ entry.Parents.remove(parent)
+ parent.MenuEntries.remove(entry)
+ self.__addXmlFilename(xml_parent, entry.DesktopFileID, "Exclude")
+ elif isinstance(entry, Menu):
+ parent.Submenus.remove(entry)
+
+ if after or before:
+ self.__addLayout(parent)
+ self.__addXmlLayout(xml_parent, parent.Layout)
+
+ def __deleteFile(self, filename):
+ try:
+ os.remove(filename)
+ except OSError:
+ pass
+ try:
+ self.filenames.remove(filename)
+ except ValueError:
+ pass
+
+ def __remove_whitespace_nodes(self, node):
+ for child in node:
+ text = child.text.strip()
+ if not text:
+ child.text = ''
+ tail = child.tail.strip()
+ if not tail:
+ child.tail = ''
+ if len(child):
+ self.__remove_whilespace_nodes(child)
+
+ def __get_parent_node(self, node):
+ # elements in ElementTree doesn't hold a reference to their parent
+ for parent, child in self.__iter_parent():
+ if child is node:
+ return child
+
+ def __iter_parent(self):
+ for parent in self.tree.getiterator():
+ for child in parent:
+ yield parent, child
diff --git a/src/versions/pyfm-0.0.1/PyFM/new/pyfm/shellfm/windows/view/icons/mixins/xdg/Mime.py b/src/versions/pyfm-0.0.1/PyFM/new/pyfm/shellfm/windows/view/icons/mixins/xdg/Mime.py
new file mode 100644
index 0000000..60c4efd
--- /dev/null
+++ b/src/versions/pyfm-0.0.1/PyFM/new/pyfm/shellfm/windows/view/icons/mixins/xdg/Mime.py
@@ -0,0 +1,780 @@
+"""
+This module is based on a rox module (LGPL):
+
+http://cvs.sourceforge.net/viewcvs.py/rox/ROX-Lib2/python/rox/mime.py?rev=1.21&view=log
+
+This module provides access to the shared MIME database.
+
+types is a dictionary of all known MIME types, indexed by the type name, e.g.
+types['application/x-python']
+
+Applications can install information about MIME types by storing an
+XML file as /packages/.xml and running the
+update-mime-database command, which is provided by the freedesktop.org
+shared mime database package.
+
+See http://www.freedesktop.org/standards/shared-mime-info-spec/ for
+information about the format of these files.
+
+(based on version 0.13)
+"""
+
+import os
+import re
+import stat
+import sys
+import fnmatch
+
+from . import BaseDirectory, Locale
+
+from .dom import minidom, XML_NAMESPACE
+from collections import defaultdict
+
+FREE_NS = 'http://www.freedesktop.org/standards/shared-mime-info'
+
+types = {} # Maps MIME names to type objects
+
+exts = None # Maps extensions to types
+globs = None # List of (glob, type) pairs
+literals = None # Maps liternal names to types
+magic = None
+
+PY3 = (sys.version_info[0] >= 3)
+
+def _get_node_data(node):
+ """Get text of XML node"""
+ return ''.join([n.nodeValue for n in node.childNodes]).strip()
+
+def lookup(media, subtype = None):
+ """Get the MIMEtype object for the given type.
+
+ This remains for backwards compatibility; calling MIMEtype now does
+ the same thing.
+
+ The name can either be passed as one part ('text/plain'), or as two
+ ('text', 'plain').
+ """
+ return MIMEtype(media, subtype)
+
+class MIMEtype(object):
+ """Class holding data about a MIME type.
+
+ Calling the class will return a cached instance, so there is only one
+ instance for each MIME type. The name can either be passed as one part
+ ('text/plain'), or as two ('text', 'plain').
+ """
+ def __new__(cls, media, subtype=None):
+ if subtype is None and '/' in media:
+ media, subtype = media.split('/', 1)
+ assert '/' not in subtype
+ media = media.lower()
+ subtype = subtype.lower()
+
+ try:
+ return types[(media, subtype)]
+ except KeyError:
+ mtype = super(MIMEtype, cls).__new__(cls)
+ mtype._init(media, subtype)
+ types[(media, subtype)] = mtype
+ return mtype
+
+ # If this is done in __init__, it is automatically called again each time
+ # the MIMEtype is returned by __new__, which we don't want. So we call it
+ # explicitly only when we construct a new instance.
+ def _init(self, media, subtype):
+ self.media = media
+ self.subtype = subtype
+ self._comment = None
+
+ def _load(self):
+ "Loads comment for current language. Use get_comment() instead."
+ resource = os.path.join('mime', self.media, self.subtype + '.xml')
+ for path in BaseDirectory.load_data_paths(resource):
+ doc = minidom.parse(path)
+ if doc is None:
+ continue
+ for comment in doc.documentElement.getElementsByTagNameNS(FREE_NS, 'comment'):
+ lang = comment.getAttributeNS(XML_NAMESPACE, 'lang') or 'en'
+ goodness = 1 + (lang in xdg.Locale.langs)
+ if goodness > self._comment[0]:
+ self._comment = (goodness, _get_node_data(comment))
+ if goodness == 2: return
+
+ # FIXME: add get_icon method
+ def get_comment(self):
+ """Returns comment for current language, loading it if needed."""
+ # Should we ever reload?
+ if self._comment is None:
+ self._comment = (0, str(self))
+ self._load()
+ return self._comment[1]
+
+ def canonical(self):
+ """Returns the canonical MimeType object if this is an alias."""
+ update_cache()
+ s = str(self)
+ if s in aliases:
+ return lookup(aliases[s])
+ return self
+
+ def inherits_from(self):
+ """Returns a set of Mime types which this inherits from."""
+ update_cache()
+ return set(lookup(t) for t in inheritance[str(self)])
+
+ def __str__(self):
+ return self.media + '/' + self.subtype
+
+ def __repr__(self):
+ return 'MIMEtype(%r, %r)' % (self.media, self.subtype)
+
+ def __hash__(self):
+ return hash(self.media) ^ hash(self.subtype)
+
+class UnknownMagicRuleFormat(ValueError):
+ pass
+
+class DiscardMagicRules(Exception):
+ "Raised when __NOMAGIC__ is found, and caught to discard previous rules."
+ pass
+
+class MagicRule:
+ also = None
+
+ def __init__(self, start, value, mask, word, range):
+ self.start = start
+ self.value = value
+ self.mask = mask
+ self.word = word
+ self.range = range
+
+ rule_ending_re = re.compile(br'(?:~(\d+))?(?:\+(\d+))?\n$')
+
+ @classmethod
+ def from_file(cls, f):
+ """Read a rule from the binary magics file. Returns a 2-tuple of
+ the nesting depth and the MagicRule."""
+ line = f.readline()
+ #print line
+
+ # [indent] '>'
+ nest_depth, line = line.split(b'>', 1)
+ nest_depth = int(nest_depth) if nest_depth else 0
+
+ # start-offset '='
+ start, line = line.split(b'=', 1)
+ start = int(start)
+
+ if line == b'__NOMAGIC__\n':
+ raise DiscardMagicRules
+
+ # value length (2 bytes, big endian)
+ if sys.version_info[0] >= 3:
+ lenvalue = int.from_bytes(line[:2], byteorder='big')
+ else:
+ lenvalue = (ord(line[0])<<8)+ord(line[1])
+ line = line[2:]
+
+ # value
+ # This can contain newlines, so we may need to read more lines
+ while len(line) <= lenvalue:
+ line += f.readline()
+ value, line = line[:lenvalue], line[lenvalue:]
+
+ # ['&' mask]
+ if line.startswith(b'&'):
+ # This can contain newlines, so we may need to read more lines
+ while len(line) <= lenvalue:
+ line += f.readline()
+ mask, line = line[1:lenvalue+1], line[lenvalue+1:]
+ else:
+ mask = None
+
+ # ['~' word-size] ['+' range-length]
+ ending = cls.rule_ending_re.match(line)
+ if not ending:
+ # Per the spec, this will be caught and ignored, to allow
+ # for future extensions.
+ raise UnknownMagicRuleFormat(repr(line))
+
+ word, range = ending.groups()
+ word = int(word) if (word is not None) else 1
+ range = int(range) if (range is not None) else 1
+
+ return nest_depth, cls(start, value, mask, word, range)
+
+ def maxlen(self):
+ l = self.start + len(self.value) + self.range
+ if self.also:
+ return max(l, self.also.maxlen())
+ return l
+
+ def match(self, buffer):
+ if self.match0(buffer):
+ if self.also:
+ return self.also.match(buffer)
+ return True
+
+ def match0(self, buffer):
+ l=len(buffer)
+ lenvalue = len(self.value)
+ for o in range(self.range):
+ s=self.start+o
+ e=s+lenvalue
+ if l [(priority, rule), ...]
+
+ def merge_file(self, fname):
+ """Read a magic binary file, and add its rules to this MagicDB."""
+ with open(fname, 'rb') as f:
+ line = f.readline()
+ if line != b'MIME-Magic\0\n':
+ raise IOError('Not a MIME magic file')
+
+ while True:
+ shead = f.readline().decode('ascii')
+ #print(shead)
+ if not shead:
+ break
+ if shead[0] != '[' or shead[-2:] != ']\n':
+ raise ValueError('Malformed section heading', shead)
+ pri, tname = shead[1:-2].split(':')
+ #print shead[1:-2]
+ pri = int(pri)
+ mtype = lookup(tname)
+ try:
+ rule = MagicMatchAny.from_file(f)
+ except DiscardMagicRules:
+ self.bytype.pop(mtype, None)
+ rule = MagicMatchAny.from_file(f)
+ if rule is None:
+ continue
+ #print rule
+
+ self.bytype[mtype].append((pri, rule))
+
+ def finalise(self):
+ """Prepare the MagicDB for matching.
+
+ This should be called after all rules have been merged into it.
+ """
+ maxlen = 0
+ self.alltypes = [] # (priority, mimetype, rule)
+
+ for mtype, rules in self.bytype.items():
+ for pri, rule in rules:
+ self.alltypes.append((pri, mtype, rule))
+ maxlen = max(maxlen, rule.maxlen())
+
+ self.maxlen = maxlen # Number of bytes to read from files
+ self.alltypes.sort(key=lambda x: x[0], reverse=True)
+
+ def match_data(self, data, max_pri=100, min_pri=0, possible=None):
+ """Do magic sniffing on some bytes.
+
+ max_pri & min_pri can be used to specify the maximum & minimum priority
+ rules to look for. possible can be a list of mimetypes to check, or None
+ (the default) to check all mimetypes until one matches.
+
+ Returns the MIMEtype found, or None if no entries match.
+ """
+ if possible is not None:
+ types = []
+ for mt in possible:
+ for pri, rule in self.bytype[mt]:
+ types.append((pri, mt, rule))
+ types.sort(key=lambda x: x[0])
+ else:
+ types = self.alltypes
+
+ for priority, mimetype, rule in types:
+ #print priority, max_pri, min_pri
+ if priority > max_pri:
+ continue
+ if priority < min_pri:
+ break
+
+ if rule.match(data):
+ return mimetype
+
+ def match(self, path, max_pri=100, min_pri=0, possible=None):
+ """Read data from the file and do magic sniffing on it.
+
+ max_pri & min_pri can be used to specify the maximum & minimum priority
+ rules to look for. possible can be a list of mimetypes to check, or None
+ (the default) to check all mimetypes until one matches.
+
+ Returns the MIMEtype found, or None if no entries match. Raises IOError
+ if the file can't be opened.
+ """
+ with open(path, 'rb') as f:
+ buf = f.read(self.maxlen)
+ return self.match_data(buf, max_pri, min_pri, possible)
+
+ def __repr__(self):
+ return '' % len(self.alltypes)
+
+class GlobDB(object):
+ def __init__(self):
+ """Prepare the GlobDB. It can't actually be used until .finalise() is
+ called, but merge_file() can be used to add data before that.
+ """
+ # Maps mimetype to {(weight, glob, flags), ...}
+ self.allglobs = defaultdict(set)
+
+ def merge_file(self, path):
+ """Loads name matching information from a globs2 file."""#
+ allglobs = self.allglobs
+ with open(path) as f:
+ for line in f:
+ if line.startswith('#'): continue # Comment
+
+ fields = line[:-1].split(':')
+ weight, type_name, pattern = fields[:3]
+ weight = int(weight)
+ mtype = lookup(type_name)
+ if len(fields) > 3:
+ flags = fields[3].split(',')
+ else:
+ flags = ()
+
+ if pattern == '__NOGLOBS__':
+ # This signals to discard any previous globs
+ allglobs.pop(mtype, None)
+ continue
+
+ allglobs[mtype].add((weight, pattern, tuple(flags)))
+
+ def finalise(self):
+ """Prepare the GlobDB for matching.
+
+ This should be called after all files have been merged into it.
+ """
+ self.exts = defaultdict(list) # Maps extensions to [(type, weight),...]
+ self.cased_exts = defaultdict(list)
+ self.globs = [] # List of (regex, type, weight) triplets
+ self.literals = {} # Maps literal names to (type, weight)
+ self.cased_literals = {}
+
+ for mtype, globs in self.allglobs.items():
+ mtype = mtype.canonical()
+ for weight, pattern, flags in globs:
+
+ cased = 'cs' in flags
+
+ if pattern.startswith('*.'):
+ # *.foo -- extension pattern
+ rest = pattern[2:]
+ if not ('*' in rest or '[' in rest or '?' in rest):
+ if cased:
+ self.cased_exts[rest].append((mtype, weight))
+ else:
+ self.exts[rest.lower()].append((mtype, weight))
+ continue
+
+ if ('*' in pattern or '[' in pattern or '?' in pattern):
+ # Translate the glob pattern to a regex & compile it
+ re_flags = 0 if cased else re.I
+ pattern = re.compile(fnmatch.translate(pattern), flags=re_flags)
+ self.globs.append((pattern, mtype, weight))
+ else:
+ # No wildcards - literal pattern
+ if cased:
+ self.cased_literals[pattern] = (mtype, weight)
+ else:
+ self.literals[pattern.lower()] = (mtype, weight)
+
+ # Sort globs by weight & length
+ self.globs.sort(reverse=True, key=lambda x: (x[2], len(x[0].pattern)) )
+
+ def first_match(self, path):
+ """Return the first match found for a given path, or None if no match
+ is found."""
+ try:
+ return next(self._match_path(path))[0]
+ except StopIteration:
+ return None
+
+ def all_matches(self, path):
+ """Return a list of (MIMEtype, glob weight) pairs for the path."""
+ return list(self._match_path(path))
+
+ def _match_path(self, path):
+ """Yields pairs of (mimetype, glob weight)."""
+ leaf = os.path.basename(path)
+
+ # Literals (no wildcards)
+ if leaf in self.cased_literals:
+ yield self.cased_literals[leaf]
+
+ lleaf = leaf.lower()
+ if lleaf in self.literals:
+ yield self.literals[lleaf]
+
+ # Extensions
+ ext = leaf
+ while 1:
+ p = ext.find('.')
+ if p < 0: break
+ ext = ext[p + 1:]
+ if ext in self.cased_exts:
+ for res in self.cased_exts[ext]:
+ yield res
+ ext = lleaf
+ while 1:
+ p = ext.find('.')
+ if p < 0: break
+ ext = ext[p+1:]
+ if ext in self.exts:
+ for res in self.exts[ext]:
+ yield res
+
+ # Other globs
+ for (regex, mime_type, weight) in self.globs:
+ if regex.match(leaf):
+ yield (mime_type, weight)
+
+# Some well-known types
+text = lookup('text', 'plain')
+octet_stream = lookup('application', 'octet-stream')
+inode_block = lookup('inode', 'blockdevice')
+inode_char = lookup('inode', 'chardevice')
+inode_dir = lookup('inode', 'directory')
+inode_fifo = lookup('inode', 'fifo')
+inode_socket = lookup('inode', 'socket')
+inode_symlink = lookup('inode', 'symlink')
+inode_door = lookup('inode', 'door')
+app_exe = lookup('application', 'executable')
+
+_cache_uptodate = False
+
+def _cache_database():
+ global globs, magic, aliases, inheritance, _cache_uptodate
+
+ _cache_uptodate = True
+
+ aliases = {} # Maps alias Mime types to canonical names
+ inheritance = defaultdict(set) # Maps to sets of parent mime types.
+
+ # Load aliases
+ for path in BaseDirectory.load_data_paths(os.path.join('mime', 'aliases')):
+ with open(path, 'r') as f:
+ for line in f:
+ alias, canonical = line.strip().split(None, 1)
+ aliases[alias] = canonical
+
+ # Load filename patterns (globs)
+ globs = GlobDB()
+ for path in BaseDirectory.load_data_paths(os.path.join('mime', 'globs2')):
+ globs.merge_file(path)
+ globs.finalise()
+
+ # Load magic sniffing data
+ magic = MagicDB()
+ for path in BaseDirectory.load_data_paths(os.path.join('mime', 'magic')):
+ magic.merge_file(path)
+ magic.finalise()
+
+ # Load subclasses
+ for path in BaseDirectory.load_data_paths(os.path.join('mime', 'subclasses')):
+ with open(path, 'r') as f:
+ for line in f:
+ sub, parent = line.strip().split(None, 1)
+ inheritance[sub].add(parent)
+
+def update_cache():
+ if not _cache_uptodate:
+ _cache_database()
+
+def get_type_by_name(path):
+ """Returns type of file by its name, or None if not known"""
+ update_cache()
+ return globs.first_match(path)
+
+def get_type_by_contents(path, max_pri=100, min_pri=0):
+ """Returns type of file by its contents, or None if not known"""
+ update_cache()
+
+ return magic.match(path, max_pri, min_pri)
+
+def get_type_by_data(data, max_pri=100, min_pri=0):
+ """Returns type of the data, which should be bytes."""
+ update_cache()
+
+ return magic.match_data(data, max_pri, min_pri)
+
+def _get_type_by_stat(st_mode):
+ """Match special filesystem objects to Mimetypes."""
+ if stat.S_ISDIR(st_mode): return inode_dir
+ elif stat.S_ISCHR(st_mode): return inode_char
+ elif stat.S_ISBLK(st_mode): return inode_block
+ elif stat.S_ISFIFO(st_mode): return inode_fifo
+ elif stat.S_ISLNK(st_mode): return inode_symlink
+ elif stat.S_ISSOCK(st_mode): return inode_socket
+ return inode_door
+
+def get_type(path, follow=True, name_pri=100):
+ """Returns type of file indicated by path.
+
+ This function is *deprecated* - :func:`get_type2` is more accurate.
+
+ :param path: pathname to check (need not exist)
+ :param follow: when reading file, follow symbolic links
+ :param name_pri: Priority to do name matches. 100=override magic
+
+ This tries to use the contents of the file, and falls back to the name. It
+ can also handle special filesystem objects like directories and sockets.
+ """
+ update_cache()
+
+ try:
+ if follow:
+ st = os.stat(path)
+ else:
+ st = os.lstat(path)
+ except:
+ t = get_type_by_name(path)
+ return t or text
+
+ if stat.S_ISREG(st.st_mode):
+ # Regular file
+ t = get_type_by_contents(path, min_pri=name_pri)
+ if not t: t = get_type_by_name(path)
+ if not t: t = get_type_by_contents(path, max_pri=name_pri)
+ if t is None:
+ if stat.S_IMODE(st.st_mode) & 0o111:
+ return app_exe
+ else:
+ return text
+ return t
+ else:
+ return _get_type_by_stat(st.st_mode)
+
+def get_type2(path, follow=True):
+ """Find the MIMEtype of a file using the XDG recommended checking order.
+
+ This first checks the filename, then uses file contents if the name doesn't
+ give an unambiguous MIMEtype. It can also handle special filesystem objects
+ like directories and sockets.
+
+ :param path: file path to examine (need not exist)
+ :param follow: whether to follow symlinks
+
+ :rtype: :class:`MIMEtype`
+
+ .. versionadded:: 1.0
+ """
+ update_cache()
+
+ try:
+ st = os.stat(path) if follow else os.lstat(path)
+ except OSError:
+ return get_type_by_name(path) or octet_stream
+
+ if not stat.S_ISREG(st.st_mode):
+ # Special filesystem objects
+ return _get_type_by_stat(st.st_mode)
+
+ mtypes = sorted(globs.all_matches(path), key=(lambda x: x[1]), reverse=True)
+ if mtypes:
+ max_weight = mtypes[0][1]
+ i = 1
+ for mt, w in mtypes[1:]:
+ if w < max_weight:
+ break
+ i += 1
+ mtypes = mtypes[:i]
+ if len(mtypes) == 1:
+ return mtypes[0][0]
+
+ possible = [mt for mt,w in mtypes]
+ else:
+ possible = None # Try all magic matches
+
+ try:
+ t = magic.match(path, possible=possible)
+ except IOError:
+ t = None
+
+ if t:
+ return t
+ elif mtypes:
+ return mtypes[0][0]
+ elif stat.S_IMODE(st.st_mode) & 0o111:
+ return app_exe
+ else:
+ return text if is_text_file(path) else octet_stream
+
+def is_text_file(path):
+ """Guess whether a file contains text or binary data.
+
+ Heuristic: binary if the first 32 bytes include ASCII control characters.
+ This rule may change in future versions.
+
+ .. versionadded:: 1.0
+ """
+ try:
+ f = open(path, 'rb')
+ except IOError:
+ return False
+
+ with f:
+ return _is_text(f.read(32))
+
+if PY3:
+ def _is_text(data):
+ return not any(b <= 0x8 or 0xe <= b < 0x20 or b == 0x7f for b in data)
+else:
+ def _is_text(data):
+ return not any(b <= '\x08' or '\x0e' <= b < '\x20' or b == '\x7f' \
+ for b in data)
+
+_mime2ext_cache = None
+_mime2ext_cache_uptodate = False
+
+def get_extensions(mimetype):
+ """Retrieve the set of filename extensions matching a given MIMEtype.
+
+ Extensions are returned without a leading dot, e.g. 'py'. If no extensions
+ are registered for the MIMEtype, returns an empty set.
+
+ The extensions are stored in a cache the first time this is called.
+
+ .. versionadded:: 1.0
+ """
+ global _mime2ext_cache, _mime2ext_cache_uptodate
+ update_cache()
+ if not _mime2ext_cache_uptodate:
+ _mime2ext_cache = defaultdict(set)
+ for ext, mtypes in globs.exts.items():
+ for mtype, prio in mtypes:
+ _mime2ext_cache[mtype].add(ext)
+ _mime2ext_cache_uptodate = True
+
+ return _mime2ext_cache[mimetype]
+
+
+def install_mime_info(application, package_file):
+ """Copy 'package_file' as ``~/.local/share/mime/packages/.xml.``
+ If package_file is None, install ``/.xml``.
+ If already installed, does nothing. May overwrite an existing
+ file with the same name (if the contents are different)"""
+ application += '.xml'
+
+ new_data = open(package_file).read()
+
+ # See if the file is already installed
+ package_dir = os.path.join('mime', 'packages')
+ resource = os.path.join(package_dir, application)
+ for x in BaseDirectory.load_data_paths(resource):
+ try:
+ old_data = open(x).read()
+ except:
+ continue
+ if old_data == new_data:
+ return # Already installed
+
+ global _cache_uptodate
+ _cache_uptodate = False
+
+ # Not already installed; add a new copy
+ # Create the directory structure...
+ new_file = os.path.join(BaseDirectory.save_data_path(package_dir), application)
+
+ # Write the file...
+ open(new_file, 'w').write(new_data)
+
+ # Update the database...
+ command = 'update-mime-database'
+ if os.spawnlp(os.P_WAIT, command, command, BaseDirectory.save_data_path('mime')):
+ os.unlink(new_file)
+ raise Exception("The '%s' command returned an error code!\n" \
+ "Make sure you have the freedesktop.org shared MIME package:\n" \
+ "http://standards.freedesktop.org/shared-mime-info/" % command)
diff --git a/src/versions/pyfm-0.0.1/PyFM/new/pyfm/shellfm/windows/view/icons/mixins/xdg/RecentFiles.py b/src/versions/pyfm-0.0.1/PyFM/new/pyfm/shellfm/windows/view/icons/mixins/xdg/RecentFiles.py
new file mode 100644
index 0000000..fbe608c
--- /dev/null
+++ b/src/versions/pyfm-0.0.1/PyFM/new/pyfm/shellfm/windows/view/icons/mixins/xdg/RecentFiles.py
@@ -0,0 +1,181 @@
+"""
+Implementation of the XDG Recent File Storage Specification
+http://standards.freedesktop.org/recent-file-spec
+"""
+
+import xml.dom.minidom, xml.sax.saxutils
+import os, time, fcntl
+from .Exceptions import ParsingError
+
+class RecentFiles:
+ def __init__(self):
+ self.RecentFiles = []
+ self.filename = ""
+
+ def parse(self, filename=None):
+ """Parse a list of recently used files.
+
+ filename defaults to ``~/.recently-used``.
+ """
+ if not filename:
+ filename = os.path.join(os.getenv("HOME"), ".recently-used")
+
+ try:
+ doc = xml.dom.minidom.parse(filename)
+ except IOError:
+ raise ParsingError('File not found', filename)
+ except xml.parsers.expat.ExpatError:
+ raise ParsingError('Not a valid .menu file', filename)
+
+ self.filename = filename
+
+ for child in doc.childNodes:
+ if child.nodeType == xml.dom.Node.ELEMENT_NODE:
+ if child.tagName == "RecentFiles":
+ for recent in child.childNodes:
+ if recent.nodeType == xml.dom.Node.ELEMENT_NODE:
+ if recent.tagName == "RecentItem":
+ self.__parseRecentItem(recent)
+
+ self.sort()
+
+ def __parseRecentItem(self, item):
+ recent = RecentFile()
+ self.RecentFiles.append(recent)
+
+ for attribute in item.childNodes:
+ if attribute.nodeType == xml.dom.Node.ELEMENT_NODE:
+ if attribute.tagName == "URI":
+ recent.URI = attribute.childNodes[0].nodeValue
+ elif attribute.tagName == "Mime-Type":
+ recent.MimeType = attribute.childNodes[0].nodeValue
+ elif attribute.tagName == "Timestamp":
+ recent.Timestamp = int(attribute.childNodes[0].nodeValue)
+ elif attribute.tagName == "Private":
+ recent.Prviate = True
+ elif attribute.tagName == "Groups":
+
+ for group in attribute.childNodes:
+ if group.nodeType == xml.dom.Node.ELEMENT_NODE:
+ if group.tagName == "Group":
+ recent.Groups.append(group.childNodes[0].nodeValue)
+
+ def write(self, filename=None):
+ """Write the list of recently used files to disk.
+
+ If the instance is already associated with a file, filename can be
+ omitted to save it there again.
+ """
+ if not filename and not self.filename:
+ raise ParsingError('File not found', filename)
+ elif not filename:
+ filename = self.filename
+
+ f = open(filename, "w")
+ fcntl.lockf(f, fcntl.LOCK_EX)
+ f.write('\n')
+ f.write("\n")
+
+ for r in self.RecentFiles:
+ f.write(" \n")
+ f.write(" %s\n" % xml.sax.saxutils.escape(r.URI))
+ f.write(" %s\n" % r.MimeType)
+ f.write(" %s\n" % r.Timestamp)
+ if r.Private == True:
+ f.write(" \n")
+ if len(r.Groups) > 0:
+ f.write(" \n")
+ for group in r.Groups:
+ f.write(" %s\n" % group)
+ f.write(" \n")
+ f.write(" \n")
+
+ f.write("\n")
+ fcntl.lockf(f, fcntl.LOCK_UN)
+ f.close()
+
+ def getFiles(self, mimetypes=None, groups=None, limit=0):
+ """Get a list of recently used files.
+
+ The parameters can be used to filter by mime types, by group, or to
+ limit the number of items returned. By default, the entire list is
+ returned, except for items marked private.
+ """
+ tmp = []
+ i = 0
+ for item in self.RecentFiles:
+ if groups:
+ for group in groups:
+ if group in item.Groups:
+ tmp.append(item)
+ i += 1
+ elif mimetypes:
+ for mimetype in mimetypes:
+ if mimetype == item.MimeType:
+ tmp.append(item)
+ i += 1
+ else:
+ if item.Private == False:
+ tmp.append(item)
+ i += 1
+ if limit != 0 and i == limit:
+ break
+
+ return tmp
+
+ def addFile(self, item, mimetype, groups=None, private=False):
+ """Add a recently used file.
+
+ item should be the URI of the file, typically starting with ``file:///``.
+ """
+ # check if entry already there
+ if item in self.RecentFiles:
+ index = self.RecentFiles.index(item)
+ recent = self.RecentFiles[index]
+ else:
+ # delete if more then 500 files
+ if len(self.RecentFiles) == 500:
+ self.RecentFiles.pop()
+ # add entry
+ recent = RecentFile()
+ self.RecentFiles.append(recent)
+
+ recent.URI = item
+ recent.MimeType = mimetype
+ recent.Timestamp = int(time.time())
+ recent.Private = private
+ if groups:
+ recent.Groups = groups
+
+ self.sort()
+
+ def deleteFile(self, item):
+ """Remove a recently used file, by URI, from the list.
+ """
+ if item in self.RecentFiles:
+ self.RecentFiles.remove(item)
+
+ def sort(self):
+ self.RecentFiles.sort()
+ self.RecentFiles.reverse()
+
+
+class RecentFile:
+ def __init__(self):
+ self.URI = ""
+ self.MimeType = ""
+ self.Timestamp = ""
+ self.Private = False
+ self.Groups = []
+
+ def __cmp__(self, other):
+ return cmp(self.Timestamp, other.Timestamp)
+
+ def __lt__ (self, other):
+ return self.Timestamp < other.Timestamp
+
+ def __eq__(self, other):
+ return self.URI == str(other)
+
+ def __str__(self):
+ return self.URI
diff --git a/src/versions/pyfm-0.0.1/PyFM/new/pyfm/shellfm/windows/view/icons/mixins/xdg/__init__.py b/src/versions/pyfm-0.0.1/PyFM/new/pyfm/shellfm/windows/view/icons/mixins/xdg/__init__.py
new file mode 100644
index 0000000..b5a117e
--- /dev/null
+++ b/src/versions/pyfm-0.0.1/PyFM/new/pyfm/shellfm/windows/view/icons/mixins/xdg/__init__.py
@@ -0,0 +1,3 @@
+__all__ = [ "BaseDirectory", "DesktopEntry", "Menu", "Exceptions", "IniFile", "IconTheme", "Locale", "Config", "Mime", "RecentFiles", "MenuEditor" ]
+
+__version__ = "0.26"
diff --git a/src/versions/pyfm-0.0.1/PyFM/new/pyfm/shellfm/windows/view/icons/mixins/xdg/util.py b/src/versions/pyfm-0.0.1/PyFM/new/pyfm/shellfm/windows/view/icons/mixins/xdg/util.py
new file mode 100644
index 0000000..1637aa5
--- /dev/null
+++ b/src/versions/pyfm-0.0.1/PyFM/new/pyfm/shellfm/windows/view/icons/mixins/xdg/util.py
@@ -0,0 +1,75 @@
+import sys
+
+PY3 = sys.version_info[0] >= 3
+
+if PY3:
+ def u(s):
+ return s
+else:
+ # Unicode-like literals
+ def u(s):
+ return s.decode('utf-8')
+
+try:
+ # which() is available from Python 3.3
+ from shutil import which
+except ImportError:
+ import os
+ # This is a copy of which() from Python 3.3
+ def which(cmd, mode=os.F_OK | os.X_OK, path=None):
+ """Given a command, mode, and a PATH string, return the path which
+ conforms to the given mode on the PATH, or None if there is no such
+ file.
+
+ `mode` defaults to os.F_OK | os.X_OK. `path` defaults to the result
+ of os.environ.get("PATH"), or can be overridden with a custom search
+ path.
+
+ """
+ # Check that a given file can be accessed with the correct mode.
+ # Additionally check that `file` is not a directory, as on Windows
+ # directories pass the os.access check.
+ def _access_check(fn, mode):
+ return (os.path.exists(fn) and os.access(fn, mode)
+ and not os.path.isdir(fn))
+
+ # If we're given a path with a directory part, look it up directly rather
+ # than referring to PATH directories. This includes checking relative to the
+ # current directory, e.g. ./script
+ if os.path.dirname(cmd):
+ if _access_check(cmd, mode):
+ return cmd
+ return None
+
+ path = (path or os.environ.get("PATH", os.defpath)).split(os.pathsep)
+
+ if sys.platform == "win32":
+ # The current directory takes precedence on Windows.
+ if not os.curdir in path:
+ path.insert(0, os.curdir)
+
+ # PATHEXT is necessary to check on Windows.
+ pathext = os.environ.get("PATHEXT", "").split(os.pathsep)
+ # See if the given file matches any of the expected path extensions.
+ # This will allow us to short circuit when given "python.exe".
+ # If it does match, only test that one, otherwise we have to try
+ # others.
+ if any(cmd.lower().endswith(ext.lower()) for ext in pathext):
+ files = [cmd]
+ else:
+ files = [cmd + ext for ext in pathext]
+ else:
+ # On other platforms you don't have things like PATHEXT to tell you
+ # what file suffixes are executable, so just pass on cmd as-is.
+ files = [cmd]
+
+ seen = set()
+ for dir in path:
+ normdir = os.path.normcase(dir)
+ if not normdir in seen:
+ seen.add(normdir)
+ for thefile in files:
+ name = os.path.join(dir, thefile)
+ if _access_check(name, mode):
+ return name
+ return None
diff --git a/src/versions/pyfm-0.0.1/PyFM/new/pyfm/shellfm/windows/view/utils/Launcher.py b/src/versions/pyfm-0.0.1/PyFM/new/pyfm/shellfm/windows/view/utils/Launcher.py
new file mode 100644
index 0000000..ba50e47
--- /dev/null
+++ b/src/versions/pyfm-0.0.1/PyFM/new/pyfm/shellfm/windows/view/utils/Launcher.py
@@ -0,0 +1,93 @@
+# System import
+import os, subprocess, threading
+
+
+# Lib imports
+
+
+# Apoplication imports
+
+
+class Launcher:
+ def open_file_locally(self, file):
+ lowerName = file.lower()
+ command = []
+
+ if lowerName.endswith(self.fvideos):
+ command = [self.media_app]
+
+ if "mplayer" in self.media_app:
+ command += self.mplayer_options
+
+ command += [file]
+ elif lowerName.endswith(self.fimages):
+ command = [self.image_app, file]
+ elif lowerName.endswith(self.fmusic):
+ command = [self.music_app, file]
+ elif lowerName.endswith(self.foffice):
+ command = [self.office_app, file]
+ elif lowerName.endswith(self.ftext):
+ command = [self.text_app, file]
+ elif lowerName.endswith(self.fpdf):
+ command = [self.pdf_app, file]
+ else:
+ command = [self.file_manager_app, file]
+
+ self.logger.debug(command)
+ DEVNULL = open(os.devnull, 'w')
+ subprocess.Popen(command, start_new_session=True, stdout=DEVNULL, stderr=DEVNULL, close_fds=True)
+
+
+ def remux_video(self, hash, file):
+ remux_vid_pth = self.REMUX_FOLDER + "/" + hash + ".mp4"
+ self.logger.debug(remux_vid_pth)
+
+ if not os.path.isfile(remux_vid_pth):
+ self.check_remux_space()
+
+ command = ["ffmpeg", "-i", file, "-hide_banner", "-movflags", "+faststart"]
+ if file.endswith("mkv"):
+ command += ["-codec", "copy", "-strict", "-2"]
+ if file.endswith("avi"):
+ command += ["-c:v", "libx264", "-crf", "21", "-c:a", "aac", "-b:a", "192k", "-ac", "2"]
+ if file.endswith("wmv"):
+ command += ["-c:v", "libx264", "-crf", "23", "-c:a", "aac", "-strict", "-2", "-q:a", "100"]
+ if file.endswith("f4v") or file.endswith("flv"):
+ command += ["-vcodec", "copy"]
+
+ command += [remux_vid_pth]
+ try:
+ proc = subprocess.Popen(command)
+ proc.wait()
+ except Exception as e:
+ self.logger.debug(message)
+ self.logger.debug(e)
+ return False
+
+ return True
+
+ def check_remux_space(self):
+ limit = self.remux_folder_max_disk_usage
+ try:
+ limit = int(limit)
+ except Exception as e:
+ self.logger.debug(e)
+ return
+
+ usage = self.get_remux_folder_usage(self.REMUX_FOLDER)
+ if usage > limit:
+ files = os.listdir(self.REMUX_FOLDER)
+ for file in files:
+ fp = os.path.join(self.REMUX_FOLDER, file)
+ os.unlink(fp)
+
+
+ def get_remux_folder_usage(self, start_path = "."):
+ total_size = 0
+ for dirpath, dirnames, filenames in os.walk(start_path):
+ for f in filenames:
+ fp = os.path.join(dirpath, f)
+ if not os.path.islink(fp): # Skip if it is symbolic link
+ total_size += os.path.getsize(fp)
+
+ return total_size
diff --git a/src/versions/pyfm-0.0.1/PyFM/new/pyfm/shellfm/windows/view/utils/Settings.py b/src/versions/pyfm-0.0.1/PyFM/new/pyfm/shellfm/windows/view/utils/Settings.py
new file mode 100644
index 0000000..7d098bc
--- /dev/null
+++ b/src/versions/pyfm-0.0.1/PyFM/new/pyfm/shellfm/windows/view/utils/Settings.py
@@ -0,0 +1,93 @@
+# System import
+import json
+import os
+from os import path
+
+# Lib imports
+
+
+# Apoplication imports
+
+
+
+class Settings:
+ logger = None
+
+ USER_HOME = path.expanduser('~')
+ CONFIG_PATH = USER_HOME + "/.config/pyfm"
+ CONFIG_FILE = CONFIG_PATH + "/settings.json"
+ HIDE_HIDDEN_FILES = True
+
+ GTK_ORIENTATION = 1 # HORIZONTAL (0) VERTICAL (1)
+ DEFAULT_ICONS = CONFIG_PATH + "/icons"
+ DEFAULT_ICON = DEFAULT_ICONS + "/text.png"
+ FFMPG_THUMBNLR = CONFIG_PATH + "/ffmpegthumbnailer" # Thumbnail generator binary
+ REMUX_FOLDER = USER_HOME + "/.remuxs" # Remuxed files folder
+
+ STEAM_BASE_URL = "https://steamcdn-a.akamaihd.net/steam/apps/"
+ ICON_DIRS = ["/usr/share/pixmaps", "/usr/share/icons", USER_HOME + "/.icons" ,]
+ BASE_THUMBS_PTH = USER_HOME + "/.thumbnails" # Used for thumbnail generation
+ ABS_THUMBS_PTH = BASE_THUMBS_PTH + "/normal" # Used for thumbnail generation
+ STEAM_ICONS_PTH = BASE_THUMBS_PTH + "/steam_icons"
+ CONTAINER_ICON_WH = [128, 128]
+ VIDEO_ICON_WH = [128, 64]
+ SYS_ICON_WH = [56, 56]
+
+ # CONTAINER_ICON_WH = [128, 128]
+ # VIDEO_ICON_WH = [96, 48]
+ # SYS_ICON_WH = [96, 96]
+
+ subpath = ""
+ go_past_home = None
+ lock_folder = None
+ locked_folders = None
+ mplayer_options = None
+ music_app = None
+ media_app = None
+ image_app = None
+ office_app = None
+ pdf_app = None
+ text_app = None
+ file_manager_app = None
+ remux_folder_max_disk_usage = None
+
+ if path.isfile(CONFIG_FILE):
+ with open(CONFIG_FILE) as infile:
+ settings = json.load(infile)["settings"]
+
+ subpath = settings["base_of_home"]
+ HIDE_HIDDEN_FILES = True if settings["hide_hidden_files"] == "true" else False
+ go_past_home = True if settings["go_past_home"] == "true" else False
+ lock_folder = True if settings["lock_folder"] == "true" else False
+ locked_folders = settings["locked_folders"].split("::::")
+ mplayer_options = settings["mplayer_options"].split()
+ music_app = settings["music_app"]
+ media_app = settings["media_app"]
+ image_app = settings["image_app"]
+ office_app = settings["office_app"]
+ pdf_app = settings["pdf_app"]
+ text_app = settings["text_app"]
+ file_manager_app = settings["file_manager_app"]
+ remux_folder_max_disk_usage = settings["remux_folder_max_disk_usage"]
+
+ # Filters
+ fvideos = ('.mkv', '.avi', '.flv', '.mov', '.m4v', '.mpg', '.wmv', '.mpeg', '.mp4', '.webm')
+ foffice = ('.doc', '.docx', '.xls', '.xlsx', '.xlt', '.xltx', '.xlm', '.ppt', 'pptx', '.pps', '.ppsx', '.odt', '.rtf')
+ fimages = ('.png', '.jpg', '.jpeg', '.gif', '.ico', '.tga')
+ ftext = ('.txt', '.text', '.sh', '.cfg', '.conf')
+ fmusic = ('.psf', '.mp3', '.ogg', '.flac', '.m4a')
+ fpdf = ('.pdf')
+
+
+ # Dire structure check
+ if path.isdir(REMUX_FOLDER) == False:
+ os.mkdir(REMUX_FOLDER)
+
+ if path.isdir(BASE_THUMBS_PTH) == False:
+ os.mkdir(BASE_THUMBS_PTH)
+
+ if path.isdir(ABS_THUMBS_PTH) == False:
+ os.mkdir(ABS_THUMBS_PTH)
+
+ if path.isdir(STEAM_ICONS_PTH) == False:
+ os.mkdir(STEAM_ICONS_PTH)
diff --git a/src/versions/pyfm-0.0.1/PyFM/new/pyfm/shellfm/windows/view/utils/__init__.py b/src/versions/pyfm-0.0.1/PyFM/new/pyfm/shellfm/windows/view/utils/__init__.py
new file mode 100644
index 0000000..3c05646
--- /dev/null
+++ b/src/versions/pyfm-0.0.1/PyFM/new/pyfm/shellfm/windows/view/utils/__init__.py
@@ -0,0 +1,2 @@
+from .Settings import Settings
+from .Launcher import Launcher
diff --git a/src/versions/pyfm-0.0.1/PyFM/new/pyfm/signal_classes/Signals.py b/src/versions/pyfm-0.0.1/PyFM/new/pyfm/signal_classes/Signals.py
new file mode 100644
index 0000000..323d1b7
--- /dev/null
+++ b/src/versions/pyfm-0.0.1/PyFM/new/pyfm/signal_classes/Signals.py
@@ -0,0 +1,76 @@
+# Python imports
+import threading, subprocess, os
+
+# Gtk imports
+
+# Application imports
+from .mixins import *
+# from pyfm.shellfm.windows import WindowController
+from shellfm import WindowController
+
+
+
+def threaded(fn):
+ def wrapper(*args, **kwargs):
+ threading.Thread(target=fn, args=args, kwargs=kwargs).start()
+
+ return wrapper
+
+
+class Signals(WindowMixin, PaneMixin):
+ def __init__(self, settings):
+ self.settings = settings
+ self.builder = self.settings.builder
+ self.logger = self.settings.logger
+
+ self.window_controller = WindowController()
+ self.state = self.window_controller.load_state()
+
+ self.window = self.builder.get_object("Main_Window")
+ self.window1 = self.builder.get_object("window1")
+ self.window2 = self.builder.get_object("window2")
+ self.window3 = self.builder.get_object("window3")
+ self.window4 = self.builder.get_object("window4")
+ self.notebooks = [self.window1, self.window2, self.window3, self.window4]
+
+ self.single_click_open = False
+ self.is_pane1_hidden = False
+ self.is_pane2_hidden = False
+ self.is_pane3_hidden = False
+ self.is_pane4_hidden = False
+
+ self.window.show()
+ self.generate_windows(self.state)
+
+ def generate_windows(self, data = None):
+ if data:
+ for j, value in enumerate(data):
+ i = j + 1
+ isHidden = True if value[0]["window"]["isHidden"] == "True" else False
+ object = self.builder.get_object(f"tggl_notebook_{i}")
+ views = value[0]["window"]["views"]
+ self.window_controller.create_window()
+
+ for view in views:
+ self.create_new_view_notebook(None, view, i)
+
+ if isHidden:
+ self.toggle_notebook_pane(object)
+ else:
+ for j in range(0, 4):
+ i = j + 1
+ self.window_controller.create_window()
+ self.create_new_view_notebook(None, None, i)
+
+
+ def getClipboardData(self):
+ proc = subprocess.Popen(['xclip','-selection', 'clipboard', '-o'], stdout=subprocess.PIPE)
+ retcode = proc.wait()
+ data = proc.stdout.read()
+ return data.decode("utf-8").strip()
+
+ def setClipboardData(self, data):
+ proc = subprocess.Popen(['xclip','-selection','clipboard'], stdin=subprocess.PIPE)
+ proc.stdin.write(data)
+ proc.stdin.close()
+ retcode = proc.wait()
diff --git a/src/versions/pyfm-0.0.1/PyFM/new/pyfm/signal_classes/__init__.py b/src/versions/pyfm-0.0.1/PyFM/new/pyfm/signal_classes/__init__.py
new file mode 100644
index 0000000..159e1e6
--- /dev/null
+++ b/src/versions/pyfm-0.0.1/PyFM/new/pyfm/signal_classes/__init__.py
@@ -0,0 +1,5 @@
+"""
+ Gtk Bound Signal Module
+"""
+from .mixins import *
+from .Signals import Signals
diff --git a/src/versions/pyfm-0.0.1/PyFM/new/pyfm/signal_classes/mixins/PaneMixin.py b/src/versions/pyfm-0.0.1/PyFM/new/pyfm/signal_classes/mixins/PaneMixin.py
new file mode 100644
index 0000000..cad970e
--- /dev/null
+++ b/src/versions/pyfm-0.0.1/PyFM/new/pyfm/signal_classes/mixins/PaneMixin.py
@@ -0,0 +1,54 @@
+
+
+
+# # TODO: Should rewrite to try and support more windows more naturally
+class PaneMixin:
+ """docstring for PaneMixin"""
+
+ def toggle_pane(self, child):
+ if child.is_visible():
+ child.hide()
+ else:
+ child.show()
+
+ def run_flag_toggle(self, pane_index):
+ if pane_index == 1:
+ self.is_pane1_hidden = not self.is_pane1_hidden
+ return self.is_pane1_hidden
+ elif pane_index == 2:
+ self.is_pane2_hidden = not self.is_pane2_hidden
+ return self.is_pane2_hidden
+ elif pane_index == 3:
+ self.is_pane3_hidden = not self.is_pane3_hidden
+ return self.is_pane3_hidden
+ elif pane_index == 4:
+ self.is_pane4_hidden = not self.is_pane4_hidden
+ return self.is_pane4_hidden
+
+ def toggle_notebook_pane(self, widget, eve=None):
+ name = widget.get_name()
+ pane_index = int(name[-1])
+ pane = None
+
+ master_pane = self.builder.get_object("pane_master")
+ pane = self.builder.get_object("pane_top") if pane_index in [1, 2] else self.builder.get_object("pane_bottom")
+
+ state = self.run_flag_toggle(pane_index)
+ if self.is_pane1_hidden and self.is_pane2_hidden and self.is_pane3_hidden and self.is_pane4_hidden:
+ state = self.run_flag_toggle(pane_index)
+ self._save_state(state, pane_index)
+ return
+
+ child = None
+ if pane_index in [1, 3]:
+ child = pane.get_child1()
+ elif pane_index in [2, 4]:
+ child = pane.get_child2()
+
+ self.toggle_pane(child)
+ self._save_state(state, pane_index)
+
+ def _save_state(self, state, pane_index):
+ window = self.window_controller.get_window_by_index(pane_index - 1)
+ window.isHidden = state
+ self.window_controller.save_state()
diff --git a/src/versions/pyfm-0.0.1/PyFM/new/pyfm/signal_classes/mixins/TabMixin.py b/src/versions/pyfm-0.0.1/PyFM/new/pyfm/signal_classes/mixins/TabMixin.py
new file mode 100644
index 0000000..b00d450
--- /dev/null
+++ b/src/versions/pyfm-0.0.1/PyFM/new/pyfm/signal_classes/mixins/TabMixin.py
@@ -0,0 +1,146 @@
+# Python imports
+import copy
+from os.path import isdir, isfile
+
+# Lib imports
+import gi
+
+from gi.repository import Gdk
+
+# Application imports
+from . import WidgetMixin
+
+
+class TabMixin(WidgetMixin):
+ """docstring for TabMixin"""
+
+ def get_fm_window(self, wid):
+ return self.window_controller.get_window_by_nickname(f"window_{wid}")
+
+ def create_tab(self, wid, path=None, save_state=True):
+ notebook = self.builder.get_object(f"window_{wid}")
+ path_entry = self.builder.get_object(f"path_entry")
+ view = self.window_controller.add_view_for_window_by_nickname(f"window_{wid}")
+ view.logger = self.logger
+
+ if path: view.set_path(path)
+
+ tab = self.create_tab_widget(view)
+ scroll, store = self.create_grid_iconview_widget(view, wid)
+ index = notebook.append_page(scroll, tab)
+
+ # scroll, store = self.create_grid_treeview_widget(view, wid)
+ self.load_store(view, store, save_state)
+
+ self.window_controller.set_active_data(wid, view.get_tab_id())
+ path_entry.set_text(view.get_current_directory())
+ notebook.show_all()
+ notebook.set_current_page(index)
+
+ def close_tab(self, widget, eve):
+ notebook = widget.get_parent().get_parent()
+ page = notebook.get_current_page()
+
+ tid = self.get_tab_id_from_widget(widget.get_parent())
+ wid = int(notebook.get_name()[-1])
+
+ self.get_fm_window(wid).delete_view_by_id(tid)
+ notebook.remove_page(page)
+ self.window_controller.save_state()
+
+ def icon_double_left_click(self, widget, item):
+ try:
+ wid, tid = self.window_controller.get_active_data()
+ notebook = self.builder.get_object(f"window_{wid}")
+ path_entry = self.builder.get_object(f"path_entry")
+ tab_label = self.get_tab_label_widget_from_widget(notebook, widget)
+
+ view = self.get_fm_window(wid).get_view_by_id(tid)
+ model = widget.get_model()
+
+ fileName = model[item][1]
+ dir = view.get_current_directory()
+ file = dir + "/" + fileName
+ refresh = True
+
+ if fileName == ".":
+ view.load_directory()
+ elif fileName == "..":
+ view.pop_from_path()
+ elif isdir(file):
+ view.set_path(file)
+ elif isfile(file):
+ refresh = False
+ view.open_file_locally(file)
+
+ if refresh == True:
+ self.load_store(view, model)
+ tab_label.set_label(view.get_end_of_path())
+ path_entry.set_text(view.get_current_directory())
+ except Exception as e:
+ print(repr(e))
+
+ def icon_single_click(self, widget, eve):
+ try:
+ wid, tid = widget.get_name().split("|")
+ self.window_controller.set_active_data(wid, tid)
+
+ if eve.type == Gdk.EventType.BUTTON_RELEASE and eve.button == 1: # l-click
+ self.set_path_text(wid, tid)
+
+ if self.single_click_open: # FIXME: need to find a way to pass the model index
+ self.icon_double_left_click(widget)
+ elif eve.type == Gdk.EventType.BUTTON_RELEASE and eve.button == 3: # r-click
+ pass
+ # input = self.builder.get_object("filenameInput")
+ # controls = self.builder.get_object("iconControlsWindow")
+ # iconsButtonBox = self.builder.get_object("iconsButtonBox")
+ # menuButtonBox = self.builder.get_object("menuButtonBox")
+ #
+ #
+ # if len(self.selectedFiles) == 1:
+ # parts = self.selectedFiles[0].split("/")
+ # input.set_text(parts[len(parts) - 1])
+ # input.show()
+ # iconsButtonBox.show()
+ # menuButtonBox.hide()
+ # controls.show()
+ # elif len(self.selectedFiles) > 1:
+ # input.set_text("")
+ # input.hide()
+ # menuButtonBox.hide()
+ # iconsButtonBox.show()
+ # controls.show()
+ # else:
+ # input.set_text("")
+ # input.show()
+ # menuButtonBox.show()
+ # iconsButtonBox.hide()
+ # controls.show()
+
+ except Exception as e:
+ print(repr(e))
+
+ def do_action_from_bar_controls(self, widget, eve=None):
+ action = widget.get_name()
+ wid, tid = self.window_controller.get_active_data()
+ notebook = self.builder.get_object(f"window_{wid}")
+ icon_view, tab_label = self.get_icon_view_and_label_from_notebook(notebook, f"{wid}|{tid}")
+
+ view = self.get_fm_window(wid).get_view_by_id(tid)
+ store = icon_view.get_model()
+
+ if action == "go_up":
+ view.pop_from_path()
+ if action == "go_home":
+ view.set_to_home()
+ if action == "refresh_view":
+ view.load_directory()
+ if action == "create_tab" :
+ dir = view.get_current_directory()
+ self.create_tab(wid, dir)
+ return
+
+ self.load_store(view, store, True)
+ self.set_path_text(wid, tid)
+ tab_label.set_label(view.get_end_of_path())
diff --git a/src/versions/pyfm-0.0.1/PyFM/new/pyfm/signal_classes/mixins/WidgetMixin.py b/src/versions/pyfm-0.0.1/PyFM/new/pyfm/signal_classes/mixins/WidgetMixin.py
new file mode 100644
index 0000000..1f28634
--- /dev/null
+++ b/src/versions/pyfm-0.0.1/PyFM/new/pyfm/signal_classes/mixins/WidgetMixin.py
@@ -0,0 +1,136 @@
+# Python imports
+
+# Lib imports
+import gi
+
+gi.require_version("Gtk", "3.0")
+from gi.repository import Gtk
+from gi.repository import GdkPixbuf
+
+# Application imports
+
+
+class WidgetMixin:
+ # This feels ugly but I don't see a better option than itterating over the list.
+ def load_store(self, view, store, save_state=True):
+ store.clear()
+ files = view.get_pixbuf_icon_str_combo()
+
+ for data in files:
+ store.append(data)
+
+ if save_state:
+ self.window_controller.save_state()
+
+ def create_tab_widget(self, view):
+ tab = Gtk.Box()
+ label = Gtk.Label()
+ tid = Gtk.Label()
+ close = Gtk.EventBox()
+ icon = Gtk.Image(stock=Gtk.STOCK_CLOSE)
+
+ label.set_label(f"{view.get_end_of_path()}")
+ tid.set_label(f"{view.id}")
+
+ close.add(icon)
+ tab.add(label)
+ tab.add(close)
+ tab.add(tid)
+
+ close.connect("button_release_event", self.close_tab)
+ tab.show_all()
+ tid.hide()
+ return tab
+
+ def create_grid_iconview_widget(self, view, wid):
+ scroll = Gtk.ScrolledWindow()
+ grid = Gtk.IconView()
+ store = Gtk.ListStore(GdkPixbuf.Pixbuf, str)
+
+ grid.set_model(store)
+ grid.set_pixbuf_column(0)
+ grid.set_text_column(1)
+
+ grid.set_item_orientation(0)
+ grid.set_selection_mode(3)
+ grid.set_item_width(152)
+ grid.set_item_padding(2)
+ grid.set_margin(2)
+ grid.set_row_spacing(2)
+ grid.set_columns(-1)
+ grid.set_spacing(1)
+ grid.set_column_spacing(2)
+
+ grid.connect("button_release_event", self.icon_single_click)
+ grid.connect("item-activated", self.icon_double_left_click)
+
+ grid.show_all()
+ scroll.add(grid)
+ grid.set_name(f"{wid}|{view.id}")
+ return scroll, store
+
+ def create_grid_treeview_widget(self, view, wid):
+ scroll = Gtk.ScrolledWindow()
+ grid = Gtk.TreeView()
+ store = Gtk.ListStore(GdkPixbuf.Pixbuf, str)
+ column = Gtk.TreeViewColumn("Icons")
+ icon = Gtk.CellRendererPixbuf()
+ name = Gtk.CellRendererText()
+
+ grid.set_model(store)
+ column.pack_start(icon, False)
+ column.pack_start(name, True)
+ column.add_attribute(icon, "pixbuf", 0)
+ column.add_attribute(name, "text", 1)
+ column.set_expand(True)
+
+ grid.append_column(column)
+ grid.set_search_column(1)
+ grid.set_rubber_banding(True)
+ grid.set_headers_visible(False)
+ grid.set_enable_tree_lines(False)
+ grid.set_visible(True)
+
+ # grid.connect("button_release_event", self.icon_single_click)
+ # grid.connect("item-activated", self.icon_double_left_click)
+
+ column.set_visible(True)
+ icon.set_visible(True)
+ name.set_visible(True)
+
+ grid.show_all()
+ scroll.add(grid)
+ grid.set_name(f"{wid}|{view.id}")
+ return scroll, store
+
+
+
+
+ def on_tab_switch_update(self, notebook, content=None, index=None):
+ wid, tid = content.get_children()[0].get_name().split("|")
+ self.window_controller.set_active_data(wid, tid)
+ self.set_path_text(wid, tid)
+
+ def set_path_text(self, wid, tid):
+ path_entry = self.builder.get_object("path_entry")
+ view = self.get_fm_window(wid).get_view_by_id(tid)
+ path_entry.set_text(view.get_current_directory())
+
+ def get_tab_id_from_widget(self, tab_box):
+ tid = tab_box.get_children()[2]
+ return tid.get_text()
+
+ def get_tab_label_widget_from_widget(self, notebook, widget):
+ return notebook.get_tab_label(widget.get_parent()).get_children()[0]
+
+ def get_icon_view_and_label_from_notebook(self, notebook, _name):
+ icon_view = None
+ tab_label = None
+
+ for obj in notebook.get_children():
+ icon_view = obj.get_children()[0]
+ name = icon_view.get_name()
+ if name == _name:
+ tab_label = notebook.get_tab_label(obj).get_children()[0]
+
+ return icon_view, tab_label
diff --git a/src/versions/pyfm-0.0.1/PyFM/new/pyfm/signal_classes/mixins/WindowMixin.py b/src/versions/pyfm-0.0.1/PyFM/new/pyfm/signal_classes/mixins/WindowMixin.py
new file mode 100644
index 0000000..d950be4
--- /dev/null
+++ b/src/versions/pyfm-0.0.1/PyFM/new/pyfm/signal_classes/mixins/WindowMixin.py
@@ -0,0 +1,8 @@
+from . import TabMixin
+
+
+class WindowMixin(TabMixin):
+ """docstring for WindowMixin"""
+
+ def create_new_view_notebook(self, widget=None, path=None, wid=None):
+ self.create_tab(wid, path, save_state=False)
diff --git a/src/versions/pyfm-0.0.1/PyFM/new/pyfm/signal_classes/mixins/__init__.py b/src/versions/pyfm-0.0.1/PyFM/new/pyfm/signal_classes/mixins/__init__.py
new file mode 100644
index 0000000..1f74fca
--- /dev/null
+++ b/src/versions/pyfm-0.0.1/PyFM/new/pyfm/signal_classes/mixins/__init__.py
@@ -0,0 +1,4 @@
+from .PaneMixin import PaneMixin
+from .WidgetMixin import WidgetMixin
+from .TabMixin import TabMixin
+from .WindowMixin import WindowMixin
diff --git a/src/versions/pyfm-0.0.1/PyFM/new/pyfm/utils/Logger.py b/src/versions/pyfm-0.0.1/PyFM/new/pyfm/utils/Logger.py
new file mode 100644
index 0000000..c7f294e
--- /dev/null
+++ b/src/versions/pyfm-0.0.1/PyFM/new/pyfm/utils/Logger.py
@@ -0,0 +1,56 @@
+# Python imports
+import os, logging
+
+# Application imports
+
+
+class Logger:
+ def __init__(self):
+ pass
+
+ def get_logger(self, loggerName = "NO_LOGGER_NAME_PASSED", createFile = True):
+ """
+ Create a new logging object and return it.
+ :note:
+ NOSET # Don't know the actual log level of this... (defaulting or literally none?)
+ Log Levels (From least to most)
+ Type Value
+ CRITICAL 50
+ ERROR 40
+ WARNING 30
+ INFO 20
+ DEBUG 10
+ :param loggerName: Sets the name of the logger object. (Used in log lines)
+ :param createFile: Whether we create a log file or just pump to terminal
+
+ :return: the logging object we created
+ """
+
+ globalLogLvl = logging.DEBUG # Keep this at highest so that handlers can filter to their desired levels
+ chLogLevel = logging.CRITICAL # Prety musch the only one we change ever
+ fhLogLevel = logging.DEBUG
+ log = logging.getLogger(loggerName)
+ log.setLevel(globalLogLvl)
+
+ # Set our log output styles
+ fFormatter = logging.Formatter('[%(asctime)s] %(pathname)s:%(lineno)d %(levelname)s - %(message)s', '%m-%d %H:%M:%S')
+ cFormatter = logging.Formatter('%(pathname)s:%(lineno)d] %(levelname)s - %(message)s')
+
+ ch = logging.StreamHandler()
+ ch.setLevel(level=chLogLevel)
+ ch.setFormatter(cFormatter)
+ log.addHandler(ch)
+
+ if createFile:
+ folder = "logs"
+ file = folder + "/application.log"
+
+ if not os.path.exists(folder):
+ os.mkdir(folder)
+
+ fh = logging.FileHandler(file)
+ fh.setLevel(level=fhLogLevel)
+ fh.setFormatter(fFormatter)
+ log.addHandler(fh)
+
+ return log
diff --git a/src/versions/pyfm-0.0.1/PyFM/new/pyfm/utils/Settings.py b/src/versions/pyfm-0.0.1/PyFM/new/pyfm/utils/Settings.py
new file mode 100644
index 0000000..85a38cf
--- /dev/null
+++ b/src/versions/pyfm-0.0.1/PyFM/new/pyfm/utils/Settings.py
@@ -0,0 +1,65 @@
+# Python imports
+import os
+
+# Gtk imports
+import gi, cairo
+gi.require_version('Gtk', '3.0')
+gi.require_version('Gdk', '3.0')
+
+from gi.repository import Gtk as gtk
+from gi.repository import Gdk as gdk
+
+
+# Application imports
+from . import Logger
+
+
+class Settings:
+ def __init__(self):
+ self.SCRIPT_PTH = os.path.dirname(os.path.realpath(__file__))
+ self.builder = gtk.Builder()
+ self.logger = Logger().get_logger()
+ self.builder.add_from_file(self.SCRIPT_PTH + "/../resources/Main_Window.glade")
+
+
+ def createWindow(self):
+ # Get window and connect signals
+ window = self.builder.get_object("Main_Window")
+ window.connect("delete-event", gtk.main_quit)
+ self.setWindowData(window, False)
+ return window
+
+ def setWindowData(self, window, paintable):
+ screen = window.get_screen()
+ visual = screen.get_rgba_visual()
+
+ if visual != None and screen.is_composited():
+ window.set_visual(visual)
+
+ # bind css file
+ cssProvider = gtk.CssProvider()
+ cssProvider.load_from_path(self.SCRIPT_PTH + '/../resources/stylesheet.css')
+ screen = gdk.Screen.get_default()
+ styleContext = gtk.StyleContext()
+ styleContext.add_provider_for_screen(screen, cssProvider, gtk.STYLE_PROVIDER_PRIORITY_USER)
+
+ window.set_app_paintable(paintable)
+ if paintable:
+ window.connect("draw", self.area_draw)
+
+ def getMonitorData(self):
+ screen = self.builder.get_object("Main_Window").get_screen()
+ monitors = []
+ for m in range(screen.get_n_monitors()):
+ monitors.append(screen.get_monitor_geometry(m))
+
+ for monitor in monitors:
+ print("{}x{}+{}+{}".format(monitor.width, monitor.height, monitor.x, monitor.y))
+
+ return monitors
+
+ def area_draw(self, widget, cr):
+ cr.set_source_rgba(0, 0, 0, 0.54)
+ cr.set_operator(cairo.OPERATOR_SOURCE)
+ cr.paint()
+ cr.set_operator(cairo.OPERATOR_OVER)
diff --git a/src/versions/pyfm-0.0.1/PyFM/new/pyfm/utils/__init__.py b/src/versions/pyfm-0.0.1/PyFM/new/pyfm/utils/__init__.py
new file mode 100644
index 0000000..415301e
--- /dev/null
+++ b/src/versions/pyfm-0.0.1/PyFM/new/pyfm/utils/__init__.py
@@ -0,0 +1,6 @@
+"""
+ Utils module
+"""
+
+from .Logger import Logger
+from .Settings import Settings
diff --git a/src/versions/pyfm-0.0.1/PyFM/old/PyFM.py b/src/versions/pyfm-0.0.1/PyFM/old/PyFM.py
new file mode 100755
index 0000000..fefc784
--- /dev/null
+++ b/src/versions/pyfm-0.0.1/PyFM/old/PyFM.py
@@ -0,0 +1,38 @@
+#!/usr/bin/python3
+
+# Gtk Imports
+import gi, faulthandler, signal
+gi.require_version('Gtk', '3.0')
+gi.require_version('WebKit2', '4.0')
+
+from gi.repository import Gtk as gtk
+from gi.repository import Gdk as gdk
+from gi.repository import WebKit2 as webkit
+from gi.repository import GLib
+
+# Python imports
+from utils import Settings, Events
+
+gdk.threads_init()
+class Main:
+ def __init__(self):
+ faulthandler.enable()
+ webkit.WebView() # Needed for glade file to load...
+
+ self.builder = gtk.Builder()
+ self.settings = Settings()
+ self.settings.attachBuilder(self.builder)
+ GLib.unix_signal_add(GLib.PRIORITY_DEFAULT, signal.SIGINT, gtk.main_quit)
+ self.builder.connect_signals(Events(self.settings))
+
+ window = self.settings.createWindow()
+ window.fullscreen()
+ window.show_all()
+
+
+if __name__ == "__main__":
+ try:
+ main = Main()
+ gtk.main()
+ except Exception as e:
+ print(e)
diff --git a/src/versions/pyfm-0.0.1/PyFM/old/PyFM.sh b/src/versions/pyfm-0.0.1/PyFM/old/PyFM.sh
new file mode 100755
index 0000000..de59af8
--- /dev/null
+++ b/src/versions/pyfm-0.0.1/PyFM/old/PyFM.sh
@@ -0,0 +1,12 @@
+#!/bin/bash
+
+# set -o xtrace ## To debug scripts
+# set -o errexit ## To exit on error
+# set -o errunset ## To exit if a variable is referenced but not set
+
+
+function main() {
+ # GTK_DEBUG=interactive python3 ./PyFM.py
+ python3 ./PyFM.py
+}
+main $@;
diff --git a/src/versions/pyfm-0.0.1/PyFM/old/resources/PyFM.glade b/src/versions/pyfm-0.0.1/PyFM/old/resources/PyFM.glade
new file mode 100644
index 0000000..01aef11
--- /dev/null
+++ b/src/versions/pyfm-0.0.1/PyFM/old/resources/PyFM.glade
@@ -0,0 +1,1049 @@
+
+
+
+
+
+
+
+ inode/directory
+
+
+
+ False
+ bottom
+
+
+ True
+ False
+ vertical
+
+
+ 300
+ 26
+ True
+ True
+ gtk-edit
+
+
+ False
+ True
+ 0
+
+
+
+
+ True
+ False
+
+
+ gtk-copy
+ True
+ True
+ True
+ True
+ True
+
+
+ False
+ True
+ 0
+
+
+
+
+ gtk-cut
+ True
+ True
+ True
+ True
+ True
+
+
+ False
+ True
+ 1
+
+
+
+
+ gtk-paste
+ True
+ True
+ True
+ True
+ True
+
+
+ False
+ True
+ 2
+
+
+
+
+ gtk-delete
+ True
+ True
+ True
+ 65
+ True
+ True
+
+
+ False
+ True
+ end
+ 3
+
+
+
+
+ False
+ True
+ 1
+
+
+
+
+
+
+ True
+ False
+ gtk-ok
+
+
+ 800
+ 600
+ False
+ 800
+ 600
+ desktop
+ False
+ center
+
+
+
+
+
+ True
+ False
+ vertical
+
+
+ True
+ False
+
+
+
+ False
+ True
+ 0
+
+
+
+
+ True
+ False
+ start
+
+
+ button
+ True
+ True
+ True
+
+
+ True
+ True
+ end
+ 0
+
+
+
+
+ button
+ True
+ True
+ True
+
+
+ True
+ True
+ end
+ 1
+
+
+
+
+ True
+ True
+ True
+
+
+ True
+ True
+ end
+ 2
+
+
+
+
+ True
+ True
+ True
+ toggleViewChecked
+ True
+
+
+ True
+ True
+ end
+ 3
+
+
+
+
+ False
+ True
+ 1
+
+
+
+
+ False
+ True
+ 0
+
+
+
+
+ True
+ True
+ vertical
+ 263
+ True
+
+
+ True
+ True
+ True
+
+
+ True
+ False
+ vertical
+
+
+ True
+ False
+ start
+
+
+
+
+
+
+
+
+
+
+
+ False
+ True
+ 0
+
+
+
+
+ True
+ True
+
+
+
+
+
+ True
+ False
+ page 1
+
+
+ False
+
+
+
+
+
+
+
+ True
+ False
+ page 2
+
+
+ 1
+ False
+
+
+
+
+
+
+
+ True
+ False
+ page 3
+
+
+ 2
+ False
+
+
+
+
+ True
+ True
+ 1
+
+
+
+
+ True
+ False
+
+
+ False
+ 6
+ end
+
+
+
+
+
+
+
+
+
+
+
+ False
+ False
+ 0
+
+
+
+
+ False
+ 16
+
+
+
+
+
+
+
+
+
+
+
+ False
+ False
+ 0
+
+
+
+
+
+
+
+ False
+ True
+ 2
+
+
+
+
+ False
+ True
+
+
+
+
+ True
+ False
+ vertical
+
+
+ True
+ False
+ start
+
+
+
+
+
+
+
+
+
+
+
+ False
+ True
+ 0
+
+
+
+
+ True
+ True
+
+
+
+
+
+ True
+ False
+ page 1
+
+
+ False
+
+
+
+
+
+
+
+ True
+ False
+ page 2
+
+
+ 1
+ False
+
+
+
+
+
+
+
+ True
+ False
+ page 3
+
+
+ 2
+ False
+
+
+
+
+ True
+ True
+ 1
+
+
+
+
+ True
+ False
+
+
+ False
+ 6
+ end
+
+
+
+
+
+
+
+
+
+
+
+ False
+ False
+ 0
+
+
+
+
+ False
+ 16
+
+
+
+
+
+
+
+
+
+
+
+ False
+ False
+ 0
+
+
+
+
+
+
+
+ False
+ True
+ 2
+
+
+
+
+ True
+ True
+
+
+
+
+ False
+ True
+
+
+
+
+ True
+ True
+ 50
+ True
+
+
+ True
+ False
+ vertical
+
+
+ True
+ False
+ start
+
+
+
+
+
+
+
+
+
+
+
+ False
+ True
+ 0
+
+
+
+
+ True
+ True
+
+
+
+
+
+ True
+ False
+ page 1
+
+
+ False
+
+
+
+
+
+
+
+ True
+ False
+ page 2
+
+
+ 1
+ False
+
+
+
+
+
+
+
+ True
+ False
+ page 3
+
+
+ 2
+ False
+
+
+
+
+ True
+ True
+ 1
+
+
+
+
+ True
+ False
+
+
+ False
+ 6
+ end
+
+
+
+
+
+
+
+
+
+
+
+ False
+ False
+ 0
+
+
+
+
+ False
+ 16
+
+
+
+
+
+
+
+
+
+
+
+ False
+ False
+ 0
+
+
+
+
+
+
+
+ False
+ True
+ 2
+
+
+
+
+ False
+ True
+
+
+
+
+ True
+ False
+ vertical
+
+
+ True
+ False
+ start
+
+
+
+
+
+
+
+
+
+
+
+ False
+ True
+ 0
+
+
+
+
+ True
+ True
+
+
+
+
+
+ True
+ False
+ page 1
+
+
+ False
+
+
+
+
+
+
+
+ True
+ False
+ page 2
+
+
+ 1
+ False
+
+
+
+
+
+
+
+ True
+ False
+ page 3
+
+
+ 2
+ False
+
+
+
+
+ True
+ True
+ 1
+
+
+
+
+ True
+ False
+
+
+ False
+ 6
+ end
+
+
+
+
+
+
+
+
+
+
+
+ False
+ False
+ 0
+
+
+
+
+ False
+ 16
+
+
+
+
+
+
+
+
+
+
+
+ False
+ False
+ 0
+
+
+
+
+
+
+
+ False
+ True
+ 2
+
+
+
+
+ True
+ True
+
+
+
+
+ True
+ True
+
+
+
+
+ True
+ True
+ 1
+
+
+
+
+
+
+ False
+ True
+ True
+ bottom
+
+
+ True
+ False
+ vertical
+
+
+ True
+ False
+
+
+ gtk-home
+ True
+ True
+ True
+ True
+ True
+
+
+
+ False
+ True
+ 0
+
+
+
+
+ gtk-refresh
+ True
+ True
+ True
+ True
+ True
+
+
+
+ False
+ True
+ 1
+
+
+
+
+ True
+ True
+ edit-find-symbolic
+ False
+ False
+
+
+
+ True
+ True
+ 2
+
+
+
+
+ False
+ True
+ 0
+
+
+
+
+ True
+ True
+
+
+
+
+
+
+ False
+ True
+ 1
+
+
+
+
+
+
diff --git a/src/versions/pyfm-0.0.1/PyFM/old/resources/icons/archive.png b/src/versions/pyfm-0.0.1/PyFM/old/resources/icons/archive.png
new file mode 100644
index 0000000..7943e4e
Binary files /dev/null and b/src/versions/pyfm-0.0.1/PyFM/old/resources/icons/archive.png differ
diff --git a/src/versions/pyfm-0.0.1/PyFM/old/resources/icons/audio.png b/src/versions/pyfm-0.0.1/PyFM/old/resources/icons/audio.png
new file mode 100644
index 0000000..c010134
Binary files /dev/null and b/src/versions/pyfm-0.0.1/PyFM/old/resources/icons/audio.png differ
diff --git a/src/versions/pyfm-0.0.1/PyFM/old/resources/icons/bin.png b/src/versions/pyfm-0.0.1/PyFM/old/resources/icons/bin.png
new file mode 100644
index 0000000..d6954e3
Binary files /dev/null and b/src/versions/pyfm-0.0.1/PyFM/old/resources/icons/bin.png differ
diff --git a/src/versions/pyfm-0.0.1/PyFM/old/resources/icons/dir.png b/src/versions/pyfm-0.0.1/PyFM/old/resources/icons/dir.png
new file mode 100644
index 0000000..a9b5e9f
Binary files /dev/null and b/src/versions/pyfm-0.0.1/PyFM/old/resources/icons/dir.png differ
diff --git a/src/versions/pyfm-0.0.1/PyFM/old/resources/icons/doc.png b/src/versions/pyfm-0.0.1/PyFM/old/resources/icons/doc.png
new file mode 100644
index 0000000..f838826
Binary files /dev/null and b/src/versions/pyfm-0.0.1/PyFM/old/resources/icons/doc.png differ
diff --git a/src/versions/pyfm-0.0.1/PyFM/old/resources/icons/pdf.png b/src/versions/pyfm-0.0.1/PyFM/old/resources/icons/pdf.png
new file mode 100644
index 0000000..9f40122
Binary files /dev/null and b/src/versions/pyfm-0.0.1/PyFM/old/resources/icons/pdf.png differ
diff --git a/src/versions/pyfm-0.0.1/PyFM/old/resources/icons/presentation.png b/src/versions/pyfm-0.0.1/PyFM/old/resources/icons/presentation.png
new file mode 100644
index 0000000..3a339af
Binary files /dev/null and b/src/versions/pyfm-0.0.1/PyFM/old/resources/icons/presentation.png differ
diff --git a/src/versions/pyfm-0.0.1/PyFM/old/resources/icons/spreadsheet.png b/src/versions/pyfm-0.0.1/PyFM/old/resources/icons/spreadsheet.png
new file mode 100644
index 0000000..710efa6
Binary files /dev/null and b/src/versions/pyfm-0.0.1/PyFM/old/resources/icons/spreadsheet.png differ
diff --git a/src/versions/pyfm-0.0.1/PyFM/old/resources/icons/text.png b/src/versions/pyfm-0.0.1/PyFM/old/resources/icons/text.png
new file mode 100644
index 0000000..2546fcd
Binary files /dev/null and b/src/versions/pyfm-0.0.1/PyFM/old/resources/icons/text.png differ
diff --git a/src/versions/pyfm-0.0.1/PyFM/old/resources/icons/video.png b/src/versions/pyfm-0.0.1/PyFM/old/resources/icons/video.png
new file mode 100644
index 0000000..55afa98
Binary files /dev/null and b/src/versions/pyfm-0.0.1/PyFM/old/resources/icons/video.png differ
diff --git a/src/versions/pyfm-0.0.1/PyFM/old/resources/icons/web.png b/src/versions/pyfm-0.0.1/PyFM/old/resources/icons/web.png
new file mode 100644
index 0000000..17017ce
Binary files /dev/null and b/src/versions/pyfm-0.0.1/PyFM/old/resources/icons/web.png differ
diff --git a/src/versions/pyfm-0.0.1/PyFM/old/resources/stylesheet.css b/src/versions/pyfm-0.0.1/PyFM/old/resources/stylesheet.css
new file mode 100644
index 0000000..9addcfa
--- /dev/null
+++ b/src/versions/pyfm-0.0.1/PyFM/old/resources/stylesheet.css
@@ -0,0 +1,88 @@
+viewport,
+treeview,
+treeview > header,
+notebook > stack,
+notebook > header {
+ background-color: rgba(0, 0, 0, 0.24);
+}
+
+
+notebook > header {
+ background-color: rgba(0, 0, 0, 0.24);
+ border-color: rgba(0, 232, 255, 0.64);
+}
+
+box,
+iconview {
+ background-color: rgba(0, 0, 0, 0.2);
+ background: rgba(0, 0, 0, 0.2);
+}
+
+treeview,
+treeview.view {
+ background: rgba(0, 0, 0, 0.2);
+ background-color: rgba(0, 0, 0, 0.2);
+}
+
+cell {
+ margin: 0em;
+ padding: 0em;
+ /* float: left; */
+}
+
+cell:focus {
+ outline-style: solid;
+ outline-color: rgba(0, 232, 255, 0.64);
+}
+
+
+/* Ivonview and children default color */
+.view {
+ background-color: rgba(0, 0, 0, 0.22);
+ color: #ebebeb;
+}
+
+
+/* Hover over color when not selected */
+.view:hover {
+ box-shadow: inset 0 0 0 9999px alpha(rgba(0, 232, 255, 0.64), 0.54);
+}
+
+/* Handles the icon selection hover and selected hover color. */
+.view:selected,
+.view:selected:hover {
+ box-shadow: inset 0 0 0 9999px rgba(15, 134, 13, 0.49);
+}
+
+/* Rubberband coloring */
+.rubberband,
+rubberband,
+flowbox rubberband,
+treeview.view rubberband,
+.content-view rubberband,
+.content-view .rubberband,
+XfdesktopIconView.view .rubberband {
+ border: 1px solid #6c6c6c;
+ background-color: rgba(21, 158, 167, 0.57);
+}
+
+XfdesktopIconView.view:active {
+ background-color: rgba(172, 102, 21, 1);
+}
+
+
+XfdesktopIconView.view {
+ border-radius: 4px;
+ background-color: transparent;
+ color: white;
+ text-shadow: 0 1px 1px rgba(0, 0, 0, 0.12), 0 1px 2px rgba(0, 0, 0, 0.24);
+}
+
+XfdesktopIconView.view:active {
+ box-shadow: none;
+ text-shadow: none;
+}
+
+XfdesktopIconView.view .rubberband {
+ border-radius: 0;
+}
diff --git a/src/versions/pyfm-0.0.1/PyFM/old/utils/Dragging.py b/src/versions/pyfm-0.0.1/PyFM/old/utils/Dragging.py
new file mode 100644
index 0000000..a0b2856
--- /dev/null
+++ b/src/versions/pyfm-0.0.1/PyFM/old/utils/Dragging.py
@@ -0,0 +1,79 @@
+import os, gi
+
+gi.require_version('Gdk', '3.0')
+
+from gi.repository import Gdk
+from gi.repository import GObject
+
+
+class Dragging:
+ def __init__(self):
+ # higher values make movement more performant
+ # lower values make movement smoother
+ self.SENSITIVITY = 1
+ self.desktop = None
+ self.EvMask = Gdk.EventMask.BUTTON_PRESS_MASK | Gdk.EventMask.BUTTON1_MOTION_MASK
+ self.offsetx = 0
+ self.offsety = 0
+ self.px = 0
+ self.py = 0
+ self.maxx = 0
+ self.maxy = 0
+
+ def connectEvents(self, desktop, widget):
+ self.desktop = desktop
+ widget.set_events(self.EvMask)
+ widget.connect("button_press_event", self.press_event)
+ widget.connect("motion_notify_event", self.draggingEvent)
+ widget.show()
+
+ def press_event(self, w, event):
+ if event.button == 1:
+ p = w.get_parent()
+ # offset == distance of parent widget from edge of screen ...
+ self.offsetx, self.offsety = p.get_window().get_position()
+ # plus distance from pointer to edge of widget
+ self.offsetx += event.x
+ self.offsety += event.y
+ # self.maxx, self.maxy both relative to the parent
+ # note that we're rounding down now so that these max values don't get
+ # rounded upward later and push the widget off the edge of its parent.
+ self.maxx = self.RoundDownToMultiple(p.get_allocation().width - w.get_allocation().width, self.SENSITIVITY)
+ self.maxy = self.RoundDownToMultiple(p.get_allocation().height - w.get_allocation().height, self.SENSITIVITY)
+
+
+ def draggingEvent(self, widget, event):
+ # x_root,x_root relative to screen
+ # x,y relative to parent (fixed widget)
+ # self.px,self.py stores previous values of x,y
+
+ # get starting values for x,y
+ x = event.x_root - self.offsetx
+ y = event.y_root - self.offsety
+ # make sure the potential coordinates x,y:
+ # 1) will not push any part of the widget outside of its parent container
+ # 2) is a multiple of self.SENSITIVITY
+ x = self.RoundToNearestMultiple(self.Max(self.Min(x, self.maxx), 0), self.SENSITIVITY)
+ y = self.RoundToNearestMultiple(self.Max(self.Min(y, self.maxy), 0), self.SENSITIVITY)
+ if x != self.px or y != self.py:
+ self.px = x
+ self.py = y
+ self.desktop.move(widget, x, y)
+
+ def Min(self, a, b):
+ if b < a:
+ return b
+ return a
+
+ def Max(self, a, b):
+ if b > a:
+ return b
+ return a
+
+ def RoundDownToMultiple(self, i, m):
+ return i/m*m
+
+ def RoundToNearestMultiple(self, i, m):
+ if i % m > m / 2:
+ return (i/m+1)*m
+ return i/m*m
diff --git a/src/versions/pyfm-0.0.1/PyFM/old/utils/Events.py b/src/versions/pyfm-0.0.1/PyFM/old/utils/Events.py
new file mode 100644
index 0000000..fbd3962
--- /dev/null
+++ b/src/versions/pyfm-0.0.1/PyFM/old/utils/Events.py
@@ -0,0 +1,72 @@
+
+# Gtk Imports
+
+# Python imports
+from .Grid import Grid
+from .Dragging import Dragging
+
+class Events:
+ def __init__(self, settings):
+ self.settings = settings
+ self.builder = self.settings.returnBuilder()
+ self.desktop = self.builder.get_object("Desktop")
+ self.webview = self.builder.get_object("webview")
+ self.desktopPath = self.settings.returnDesktopPath()
+
+ self.settings.setDefaultWebviewSettings(self.webview, self.webview.get_settings())
+ self.webview.load_uri(self.settings.returnWebHome())
+
+ # Add filter to allow only folders to be selected
+ selectedDirDialog = self.builder.get_object("selectedDirDialog")
+ filefilter = self.builder.get_object("Folders")
+ selectedDirDialog.add_filter(filefilter)
+ selectedDirDialog.set_filename(self.desktopPath)
+
+ self.grid = None
+ self.setIconViewDir(selectedDirDialog)
+
+ def setIconViewDir(self, widget, data=None):
+ newPath = widget.get_filename()
+ Grid(self.desktop, self.settings, newPath)
+
+
+
+ # File control events
+ def createFile(self):
+ pass
+
+ def updateFile(self, widget, data=None):
+ newName = widget.get_text().strip()
+ if data and data.keyval == 65293: # Enter key event
+ self.grid.updateFile(newName)
+ elif data == None: # Save button 'event'
+ self.grid.updateFile(newName)
+
+ def deleteFile(self, widget, data=None):
+ self.grid.deleteFile()
+
+ def copyFile(self):
+ pass
+
+ def cutFile(self):
+ pass
+
+ def pasteFile(self):
+ pass
+
+ # Webview events
+ def showWebview(self, widget):
+ self.builder.get_object("webViewer").popup()
+
+ def loadHome(self, widget):
+ self.webview.load_uri(self.settings.returnWebHome())
+
+ def runSearchWebview(self, widget, data=None):
+ if data.keyval == 65293:
+ self.webview.load_uri(widget.get_text().strip())
+
+ def refreshPage(self, widget, data=None):
+ self.webview.load_uri(self.webview.get_uri())
+
+ def setUrlBar(self, widget, data=None):
+ self.builder.get_object("webviewSearch").set_text(widget.get_uri())
diff --git a/src/versions/pyfm-0.0.1/PyFM/old/utils/FileHandler.py b/src/versions/pyfm-0.0.1/PyFM/old/utils/FileHandler.py
new file mode 100644
index 0000000..c4dfa3b
--- /dev/null
+++ b/src/versions/pyfm-0.0.1/PyFM/old/utils/FileHandler.py
@@ -0,0 +1,93 @@
+
+import os, shutil, subprocess, threading
+
+
+def threaded(fn):
+ def wrapper(*args, **kwargs):
+ threading.Thread(target=fn, args=args, kwargs=kwargs).start()
+ return wrapper
+
+class FileHandler:
+ def __init__(self):
+ # 'Filters'
+ self.office = ('.doc', '.docx', '.xls', '.xlsx', '.xlt', '.xltx' '.xlm', '.ppt', 'pptx', '.pps', '.ppsx', '.odt', '.rtf')
+ self.vids = ('.mkv', '.avi', '.flv', '.mov', '.m4v', '.mpg', '.wmv', '.mpeg', '.mp4', '.webm')
+ self.txt = ('.txt', '.text', '.sh', '.cfg', '.conf')
+ self.music = ('.psf', '.mp3', '.ogg' , '.flac')
+ self.images = ('.png', '.jpg', '.jpeg', '.gif')
+ self.pdf = ('.pdf')
+
+ # Args
+ self.MEDIAPLAYER = "mpv";
+ self.IMGVIEWER = "mirage";
+ self.MUSICPLAYER = "/opt/deadbeef/bin/deadbeef";
+ self.OFFICEPROG = "libreoffice";
+ self.TEXTVIEWER = "leafpad";
+ self.PDFVIEWER = "evince";
+ self.FILEMANAGER = "spacefm";
+ self.MPLAYER_WH = " -xy 1600 -geometry 50%:50% ";
+ self.MPV_WH = " -geometry 50%:50% ";
+
+ @threaded
+ def openFile(self, file):
+ print("Opening: " + file)
+ if file.lower().endswith(self.vids):
+ subprocess.Popen([self.MEDIAPLAYER, self.MPV_WH, file])
+ elif file.lower().endswith(self.music):
+ subprocess.Popen([self.MUSICPLAYER, file])
+ elif file.lower().endswith(self.images):
+ subprocess.Popen([self.IMGVIEWER, file])
+ elif file.lower().endswith(self.txt):
+ subprocess.Popen([self.TEXTVIEWER, file])
+ elif file.lower().endswith(self.pdf):
+ subprocess.Popen([self.PDFVIEWER, file])
+ elif file.lower().endswith(self.office):
+ subprocess.Popen([self.OFFICEPROG, file])
+ else:
+ subprocess.Popen(['xdg-open', file])
+
+
+ def createFile(self, newFileName):
+ pass
+
+ def updateFile(self, oldFileName, newFileName):
+ try:
+ print("Renaming...")
+ print(oldFileName + " --> " + newFileName)
+ os.rename(oldFileName, newFileName)
+ return 0
+ except Exception as e:
+ print("An error occured renaming the file:")
+ print(e)
+ return 1
+
+ def deleteFile(self, toDeleteFile):
+ try:
+ print("Deleting...")
+ print(toDeleteFile)
+ if os.path.exists(toDeleteFile):
+ if os.path.isfile(toDeleteFile):
+ os.remove(toDeleteFile)
+ elif os.path.isdir(toDeleteFile):
+ shutil.rmtree(toDeleteFile)
+ else:
+ print("An error occured deleting the file:")
+ return 1
+ else:
+ print("The folder/file does not exist")
+ return 1
+ except Exception as e:
+ print("An error occured deleting the file:")
+ print(e)
+ return 1
+
+ return 0
+
+ def copyFile(self):
+ pass
+
+ def cutFile(self):
+ pass
+
+ def pasteFile(self):
+ pass
diff --git a/src/versions/pyfm-0.0.1/PyFM/old/utils/Grid.py b/src/versions/pyfm-0.0.1/PyFM/old/utils/Grid.py
new file mode 100644
index 0000000..392de55
--- /dev/null
+++ b/src/versions/pyfm-0.0.1/PyFM/old/utils/Grid.py
@@ -0,0 +1,214 @@
+
+
+# Gtk Imports
+import gi
+gi.require_version('Gtk', '3.0')
+gi.require_version('Gdk', '3.0')
+
+from gi.repository import Gtk as gtk
+from gi.repository import Gdk as gdk
+from gi.repository import GLib as glib
+from gi.repository import GdkPixbuf
+
+# Python imports
+import os, threading, time
+from os.path import isdir, isfile, join
+from os import listdir
+from .Icon import Icon
+from .FileHandler import FileHandler
+
+
+def threaded(fn):
+ def wrapper(*args, **kwargs):
+ threading.Thread(target=fn, args=args, kwargs=kwargs).start()
+ return wrapper
+
+class Grid:
+ def __init__(self, desktop, settings, newPath):
+ self.desktop = desktop
+ self.settings = settings
+ self.filehandler = FileHandler()
+
+ self.store = gtk.ListStore(GdkPixbuf.Pixbuf, str)
+ self.usrHome = settings.returnUserHome()
+ self.builder = settings.returnBuilder()
+ self.ColumnSize = settings.returnColumnSize()
+ self.currentPath = ""
+ self.selectedFile = ""
+
+ self.desktop.set_model(self.store)
+ self.desktop.set_pixbuf_column(0)
+ self.desktop.set_text_column(1)
+ self.desktop.connect("item-activated", self.iconLeftClickEventManager)
+ self.desktop.connect("button_press_event", self.iconRightClickEventManager, (self.desktop,))
+ self.desktop.connect("selection-changed", self.setIconSelectionArray, (self.desktop,))
+
+ self.vidsList = settings.returnVidsExtensionList()
+ self.imagesList = settings.returnImagesExtensionList()
+ self.gtkLock = False # Thread checks for gtkLock
+ self.threadLock = False # Gtk checks for thread lock
+ self.helperThread = None # Helper thread object
+ self.toWorkPool = [] # Thread fills pool and gtk empties it
+ self.copyCutArry = []
+
+ self.setIconViewDir(newPath)
+
+ def setIconViewDir(self, path):
+ self.store.clear()
+
+ self.currentPath = path
+ dirPaths = ['.', '..']
+ vids = []
+ images = []
+ desktop = []
+ files = []
+
+ for f in listdir(path):
+ file = join(path, f)
+ if self.settings.isHideHiddenFiles():
+ if f.startswith('.'):
+ continue
+ if isfile(file):
+ if file.lower().endswith(self.vidsList):
+ vids.append(f)
+ elif file.lower().endswith(self.imagesList):
+ images.append(f)
+ elif file.lower().endswith((".desktop",)):
+ desktop.append(f)
+ else:
+ files.append(f)
+ else:
+ dirPaths.append(f)
+
+ dirPaths.sort()
+ vids.sort()
+ images.sort()
+ desktop.sort()
+ files.sort()
+ files = dirPaths + vids + images + desktop + files
+
+ if self.helperThread:
+ self.helperThread.terminate()
+ self.helperThread = None
+
+ # Run helper thread...
+ self.threadLock = True
+ self.helperThread = threading.Thread(target=self.generateDirectoryGridIcon, args=(path, files)).start()
+ glib.idle_add(self.addToGrid, (file,)) # This must stay in the main thread b/c
+ # gtk isn't thread safe/aware So, we
+ # make a sad lil thread hot potato 'game'
+ # out of this process.
+
+
+ # @threaded
+ def generateDirectoryGridIcon(self, dirPath, files):
+ # NOTE: We'll be passing pixbuf after retreval to keep Icon.py file more
+ # universaly usable. We can just remove get_pixbuf to get a gtk.Image type
+ for file in files:
+ image = Icon(self.settings).createIcon(dirPath, file)
+ self.toWorkPool.append([image.get_pixbuf(), file])
+ self.threadLock = False
+ self.gtkLock = True
+
+
+ def addToGrid(self, args):
+ # NOTE: Returning true tells gtk to check again in the future when idle.
+ # False ends checks and "continues normal flow"
+ files = args[0]
+
+ if len(self.toWorkPool) > 0:
+ for dataSet in self.toWorkPool:
+ self.store.append(dataSet)
+
+ if len(self.store) == len(files): # Confirm processed all files and cleanup
+ self.gtkLock = False
+ self.threadLock = False
+ self.toWorkPool.clear()
+ return False
+ # Check again when idle; If nothing else is updating, this function
+ # gets called immediatly. So, we play hot potato by passing lock to Thread
+ else:
+ self.toWorkPool.clear()
+ self.gtkLock = False
+ self.threadLock = True
+ time.sleep(.005) # Fixes refresh and up icon not being added.
+ return True
+
+ def setIconSelectionArray(self, widget, data=None):
+ pass
+ # os.system('cls||clear')
+ # print(data)
+
+ def iconLeftClickEventManager(self, widget, item):
+ try:
+ model = widget.get_model()
+ fileName = model[item][1]
+ dir = self.currentPath
+ file = dir + "/" + fileName
+
+ if fileName == ".":
+ self.setIconViewDir(dir)
+ elif fileName == "..":
+ parentDir = os.path.abspath(os.path.join(dir, os.pardir))
+ self.currentPath = parentDir
+ self.setIconViewDir(parentDir)
+ elif isdir(file):
+ self.currentPath = file
+ self.setIconViewDir(self.currentPath)
+ elif isfile(file):
+ self.filehandler.openFile(file)
+ except Exception as e:
+ print(e)
+
+ def iconRightClickEventManager(self, widget, eve, params):
+ try:
+ if eve.type == gdk.EventType.BUTTON_PRESS and eve.button == 3:
+ popover = self.builder.get_object("iconControlsWindow")
+ popover.show_all()
+ popover.popup()
+ # # NOTE: Need to change name of listview box...
+ # children = widget.get_children()[0].get_children()
+ # fileName = children[1].get_text()
+ # dir = self.currentPath
+ # file = dir + "/" + fileName
+ #
+ # input = self.builder.get_object("iconRenameInput")
+ # popover = self.builder.get_object("iconControlsWindow")
+ # self.selectedFile = file # Used for return to caller
+ #
+ # input.set_text(fileName)
+ # popover.set_relative_to(widget)
+ # popover.set_position(gtk.PositionType.RIGHT)
+ # popover.show_all()
+ # popover.popup()
+ except Exception as e:
+ print(e)
+
+
+ # Passthrough file control events
+ def createFile(arg):
+ pass
+
+ def updateFile(self, file):
+ newName = self.currentPath + "/" + file
+ status = self.filehandler.updateFile(self.selectedFile, newName)
+
+ if status == 0:
+ self.selectedFile = newName
+ self.setIconViewDir(self.currentPath)
+
+ def deleteFile(self):
+ status = self.filehandler.deleteFile(self.selectedFile)
+
+ if status == 0:
+ self.selectedFile = ""
+ self.setIconViewDir(self.currentPath)
+
+ def copyFile(self):
+ pass
+
+ def cutFile(self):
+ pass
+
+ def pasteFile(self):
+ pass
diff --git a/src/versions/pyfm-0.0.1/PyFM/old/utils/Icon.py b/src/versions/pyfm-0.0.1/PyFM/old/utils/Icon.py
new file mode 100644
index 0000000..826e408
--- /dev/null
+++ b/src/versions/pyfm-0.0.1/PyFM/old/utils/Icon.py
@@ -0,0 +1,167 @@
+
+# Gtk Imports
+import gi
+gi.require_version('Gtk', '3.0')
+gi.require_version('Gdk', '3.0')
+
+from gi.repository import Gtk as gtk
+from gi.repository import Gio as gio
+from gi.repository import GdkPixbuf
+from xdg.DesktopEntry import DesktopEntry
+
+# Python Imports
+import os, subprocess, hashlib, threading
+
+from os.path import isdir, isfile, join
+
+
+
+def threaded(fn):
+ def wrapper(*args, **kwargs):
+ threading.Thread(target=fn, args=args, kwargs=kwargs).start()
+ return wrapper
+
+class Icon:
+ def __init__(self, settings):
+ self.settings = settings
+ self.thubnailGen = settings.getThumbnailGenerator()
+ self.vidsList = settings.returnVidsExtensionList()
+ self.imagesList = settings.returnImagesExtensionList()
+ self.GTK_ORIENTATION = settings.returnIconImagePos()
+ self.usrHome = settings.returnUserHome()
+ self.iconContainerWH = settings.returnContainerWH()
+ self.systemIconImageWH = settings.returnSystemIconImageWH()
+ self.viIconWH = settings.returnVIIconWH()
+
+
+ def createIcon(self, dir, file):
+ fullPath = dir + "/" + file
+ return self.getIconImage(file, fullPath)
+
+
+ def getIconImage(self, file, fullPath):
+ try:
+ thumbnl = None
+
+ # Video thumbnail
+ if file.lower().endswith(self.vidsList):
+ fileHash = hashlib.sha256(str.encode(fullPath)).hexdigest()
+ hashImgPth = self.usrHome + "/.thumbnails/normal/" + fileHash + ".png"
+
+ if isfile(hashImgPth) == False:
+ self.generateVideoThumbnail(fullPath, hashImgPth)
+
+ thumbnl = self.createIconImageBuffer(hashImgPth, self.viIconWH)
+ # Image Icon
+ elif file.lower().endswith(self.imagesList):
+ thumbnl = self.createIconImageBuffer(fullPath, self.viIconWH)
+ # .desktop file parsing
+ elif fullPath.lower().endswith( ('.desktop',) ):
+ thumbnl = self.parseDesktopFiles(fullPath)
+ # System icons
+ else:
+ thumbnl = self.getSystemThumbnail(fullPath, self.systemIconImageWH[0])
+
+ if thumbnl == None: # If no icon, try stock file icon...
+ thumbnl = gtk.Image.new_from_icon_name("gtk-file", gtk.IconSize.LARGE_TOOLBAR)
+
+ if thumbnl == None: # If no icon whatsoever, return internal default
+ thumbnl = gtk.Image.new_from_file("resources/icons/bin.png")
+
+ return thumbnl
+ except Exception as e:
+ print(e)
+ return gtk.Image.new_from_file("resources/icons/bin.png")
+
+
+ def parseDesktopFiles(self, fullPath):
+ try:
+ xdgObj = DesktopEntry(fullPath)
+ icon = xdgObj.getIcon()
+ iconsDirs = "/usr/share/icons"
+ altIconPath = ""
+
+ if "steam" in icon:
+ steamIconsDir = self.usrHome + "/.thumbnails/steam_icons/"
+ name = xdgObj.getName()
+ fileHash = hashlib.sha256(str.encode(name)).hexdigest()
+
+ if isdir(steamIconsDir) == False:
+ os.mkdir(steamIconsDir)
+
+ hashImgPth = steamIconsDir + fileHash + ".jpg"
+ if isfile(hashImgPth) == True:
+ # Use video sizes since headers are bigger
+ return self.createIconImageBuffer(hashImgPth, self.viIconWH)
+
+ execStr = xdgObj.getExec()
+ parts = execStr.split("steam://rungameid/")
+ id = parts[len(parts) - 1]
+
+ # NOTE: Can try this logic instead...
+ # if command exists use it instead of header image
+ # if "steamcmd app_info_print id":
+ # proc = subprocess.Popen(["steamcmd", "app_info_print", id])
+ # proc.wait()
+ # else:
+ # use the bottom logic
+
+ imageLink = "https://steamcdn-a.akamaihd.net/steam/apps/" + id + "/header.jpg"
+ proc = subprocess.Popen(["wget", "-O", hashImgPth, imageLink])
+ proc.wait()
+
+ # Use video sizes since headers are bigger
+ return self.createIconImageBuffer(hashImgPth, self.viIconWH)
+ elif os.path.exists(icon):
+ return self.createIconImageBuffer(icon, self.systemIconImageWH)
+ else:
+ for (dirpath, dirnames, filenames) in os.walk(iconsDirs):
+ for file in filenames:
+ appNM = "application-x-" + icon
+ if appNM in file:
+ altIconPath = dirpath + "/" + file
+ break
+
+ return self.createIconImageBuffer(altIconPath, self.systemIconImageWH)
+ except Exception as e:
+ print(e)
+ return None
+
+
+ def getSystemThumbnail(self, filename, size):
+ try:
+ iconPath = None
+ if os.path.exists(filename):
+ file = gio.File.new_for_path(filename)
+ info = file.query_info('standard::icon' , 0 , gio.Cancellable())
+ icon = info.get_icon().get_names()[0]
+ iconTheme = gtk.IconTheme.get_default()
+ iconFile = iconTheme.lookup_icon(icon , size , 0)
+
+ if iconFile != None:
+ iconPath = iconFile.get_filename()
+ return self.createIconImageBuffer(iconPath, self.systemIconImageWH)
+ else:
+ return None
+ else:
+ return None
+ except Exception as e:
+ print(e)
+ return None
+
+
+ def createIconImageBuffer(self, path, wxh):
+ try:
+ pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_scale(path, wxh[0], wxh[1], False)
+ except Exception as e:
+ return None
+
+ return gtk.Image.new_from_pixbuf(pixbuf)
+
+
+ def generateVideoThumbnail(self, fullPath, hashImgPth):
+ try:
+ proc = subprocess.Popen([self.thubnailGen, "-t", "65%", "-s", "300", "-c", "jpg", "-i", fullPath, "-o", hashImgPth])
+ proc.wait()
+ except Exception as e:
+ print(e)
diff --git a/src/versions/pyfm-0.0.1/PyFM/old/utils/Settings.py b/src/versions/pyfm-0.0.1/PyFM/old/utils/Settings.py
new file mode 100644
index 0000000..4e72f11
--- /dev/null
+++ b/src/versions/pyfm-0.0.1/PyFM/old/utils/Settings.py
@@ -0,0 +1,139 @@
+
+# Gtk Imports
+import gi, cairo, os
+gi.require_version('Gtk', '3.0')
+gi.require_version('Gdk', '3.0')
+
+from gi.repository import Gtk as gtk
+from gi.repository import Gdk as gdk
+
+class Settings:
+ def __init__(self):
+ self.builder = None
+ self.hideHiddenFiles = True
+
+
+ self.GTK_ORIENTATION = 1 # HORIZONTAL (0) VERTICAL (1)
+ self.THUMB_GENERATOR = "ffmpegthumbnailer"
+ self.DEFAULTCOLOR = gdk.RGBA(0.0, 0.0, 0.0, 0.0) # ~#00000000
+ self.MOUSEOVERCOLOR = gdk.RGBA(0.0, 0.9, 1.0, 0.64) # ~#00e8ff
+ self.SELECTEDCOLOR = gdk.RGBA(0.4, 0.5, 0.1, 0.84)
+
+ self.ColumnSize = 8
+ self.usrHome = os.path.expanduser('~')
+ self.desktopPath = self.usrHome + "/Desktop"
+ self.webHome = 'http://webfm.com/'
+ self.iconContainerWxH = [128, 128]
+ self.systemIconImageWxH = [72, 72]
+ self.viIconWxH = [256, 128]
+ self.vidsExtensionList = ('.mkv', '.avi', '.flv', '.mov', '.m4v', '.mpg', '.wmv', '.mpeg', '.mp4', '.webm')
+ self.imagesExtensionList = ('.png', '.jpg', '.jpeg', '.gif', '.ico', '.tga')
+
+
+ def attachBuilder(self, builder):
+ self.builder = builder
+ self.builder.add_from_file("resources/PyFM.glade")
+
+ def createWindow(self):
+ # Get window and connect signals
+ window = self.builder.get_object("Window")
+ window.connect("delete-event", gtk.main_quit)
+ self.setWindowData(window)
+ return window
+
+ def setWindowData(self, window):
+ screen = window.get_screen()
+ visual = screen.get_rgba_visual()
+ if visual != None and screen.is_composited():
+ window.set_visual(visual)
+
+ # bind css file
+ cssProvider = gtk.CssProvider()
+ cssProvider.load_from_path('resources/stylesheet.css')
+ screen = gdk.Screen.get_default()
+ styleContext = gtk.StyleContext()
+ styleContext.add_provider_for_screen(screen, cssProvider, gtk.STYLE_PROVIDER_PRIORITY_USER)
+
+ window.set_app_paintable(True)
+ monitors = self.getMonitorData(screen)
+ window.resize(monitors[0].width, monitors[0].height)
+
+ def getMonitorData(self, screen):
+ monitors = []
+ for m in range(screen.get_n_monitors()):
+ monitors.append(screen.get_monitor_geometry(m))
+
+ for monitor in monitors:
+ print(str(monitor.width) + "x" + str(monitor.height) + "+" + str(monitor.x) + "+" + str(monitor.y))
+
+ return monitors
+
+
+ def returnBuilder(self): return self.builder
+ def returnUserHome(self): return self.usrHome
+ def returnDesktopPath(self): return self.usrHome + "/Desktop"
+ def returnIconImagePos(self): return self.GTK_ORIENTATION
+ def getThumbnailGenerator(self): return self.THUMB_GENERATOR
+ def returnColumnSize(self): return self.ColumnSize
+ def returnContainerWH(self): return self.iconContainerWxH
+ def returnSystemIconImageWH(self): return self.systemIconImageWxH
+ def returnVIIconWH(self): return self.viIconWxH
+ def returnWebHome(self): return self.webHome
+ def isHideHiddenFiles(self): return self.hideHiddenFiles
+ def returnVidsExtensionList(self): return self.vidsExtensionList
+ def returnImagesExtensionList(self): return self.imagesExtensionList
+
+ def setDefaultWebviewSettings(self, widget, settings=None):
+ # Usability
+ settings.set_property('enable-fullscreen', True)
+ settings.set_property('print-backgrounds', True)
+ settings.set_property('enable-frame-flattening', False)
+ settings.set_property('enable-plugins', True)
+ settings.set_property('enable-java', False)
+ settings.set_property('enable-resizable-text-areas', True)
+ settings.set_property('zoom-text-only', False)
+ settings.set_property('enable-smooth-scrolling', True)
+ settings.set_property('enable-back-forward-navigation-gestures', False)
+ settings.set_property('media-playback-requires-user-gesture', False)
+ settings.set_property('enable-tabs-to-links', True)
+ settings.set_property('enable-caret-browsing', False)
+
+ # Security
+ settings.set_property('user-agent','Mozilla/5.0 (X11; Generic; Linux x86-64) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/11.0 Safari/605.1.15')
+ settings.set_property('enable-private-browsing', False)
+ settings.set_property('enable-xss-auditor', True)
+ settings.set_property('enable-hyperlink-auditing', False)
+ settings.set_property('enable-site-specific-quirks', True)
+ settings.set_property('enable-offline-web-application-cache', True)
+ settings.set_property('enable-page-cache', True)
+ settings.set_property('allow-modal-dialogs', False)
+ settings.set_property('enable-html5-local-storage', True)
+ settings.set_property('enable-html5-database', True)
+ settings.set_property('allow-file-access-from-file-urls', False)
+ settings.set_property('allow-universal-access-from-file-urls', False)
+ settings.set_property('enable-dns-prefetching', False)
+
+ # Media stuff
+ # settings.set_property('hardware-acceleration-policy', 'on-demand')
+ settings.set_property('enable-webgl', False)
+ settings.set_property('enable-webaudio', True)
+ settings.set_property('enable-accelerated-2d-canvas', True)
+ settings.set_property('auto-load-images', True)
+ settings.set_property('enable-media-capabilities', True)
+ settings.set_property('enable-media-stream', True)
+ settings.set_property('enable-mediasource', True)
+ settings.set_property('enable-encrypted-media', True)
+ settings.set_property('media-playback-allows-inline', True)
+
+ # JS
+ settings.set_property('enable-javascript', True)
+ settings.set_property('enable-javascript-markup', True)
+ settings.set_property('javascript-can-access-clipboard', False)
+ settings.set_property('javascript-can-open-windows-automatically', False)
+
+ # Debugging
+ settings.set_property('enable-developer-extras', False)
+ settings.set_property('enable-write-console-messages-to-stdout', False)
+ settings.set_property('draw-compositing-indicators', False)
+ settings.set_property('enable-mock-capture-devices', False)
+ settings.set_property('enable-spatial-navigation', False)
diff --git a/src/versions/pyfm-0.0.1/PyFM/old/utils/__init__.py b/src/versions/pyfm-0.0.1/PyFM/old/utils/__init__.py
new file mode 100644
index 0000000..e291f0f
--- /dev/null
+++ b/src/versions/pyfm-0.0.1/PyFM/old/utils/__init__.py
@@ -0,0 +1,6 @@
+from utils.Dragging import Dragging
+from utils.Settings import Settings
+from utils.Events import Events
+from utils.Grid import Grid
+from utils.Icon import Icon
+from utils.FileHandler import FileHandler
diff --git a/src/versions/pyfm-0.0.1/PyFM_exec_bin.cpp b/src/versions/pyfm-0.0.1/PyFM_exec_bin.cpp
new file mode 100644
index 0000000..93af3d7
--- /dev/null
+++ b/src/versions/pyfm-0.0.1/PyFM_exec_bin.cpp
@@ -0,0 +1,10 @@
+#include
+#include
+#include
+using namespace std;
+
+int main() {
+ chdir("/opt/Pytop/");
+ system("python3 PyTop.py");
+return 0;
+}
diff --git a/src/versions/pyfm-0.0.1/clear_pycache_dirs.sh b/src/versions/pyfm-0.0.1/clear_pycache_dirs.sh
new file mode 100755
index 0000000..7987a3d
--- /dev/null
+++ b/src/versions/pyfm-0.0.1/clear_pycache_dirs.sh
@@ -0,0 +1,12 @@
+#!/bin/bash
+
+# set -o xtrace ## To debug scripts
+# set -o errexit ## To exit on error
+# set -o errunset ## To exit if a variable is referenced but not set
+
+
+function main() {
+ find . -name "__pycache__" -exec rm -rf $1 {} \;
+ find . -name "*.pyc" -exec rm -rf $1 {} \;
+}
+main
diff --git a/src/versions/pyfm-0.0.1/compileBin.sh b/src/versions/pyfm-0.0.1/compileBin.sh
new file mode 100755
index 0000000..a5cbb77
--- /dev/null
+++ b/src/versions/pyfm-0.0.1/compileBin.sh
@@ -0,0 +1,6 @@
+#!/bin/bash
+
+function main() {
+ gcc -no-pie -s Pytop_exec_bin.cpp -o pytop
+}
+main;