diff --git a/README.md b/README.md
index b27e603..ac9688d 100644
--- a/README.md
+++ b/README.md
@@ -1,15 +1,22 @@
-# PyFM
+# SolarFM
-# PyFM
-PyFM is a Gtk + Python file manager.
+# SolarFM
+SolarFM is a Gtk + Python file manager.
# Notes
```sudo apt-get install python3 wget steamcmd```
# TODO
+- Add prompt guards for actions.
+- Add path bar search dropdown.
+- Add "execute" and "execute in terminal" context options.
+- Add "go to trash" and "delete all trash" options.
+- Add save button for "show errors" dropdown.
+- Add simpleish plugin system to run bash/python scripts.
+- Add DnD context awareness for over folder drop.
# Images
-![1 PyFM showing different directories. ](images/pic1.png)
-![1 PyFM themed dark and made transparent. ](images/pic2.png)
+![1 SolarFM showing different directories. ](images/pic1.png)
+![1 SolarFM themed dark and made transparent. ](images/pic2.png)
diff --git a/bin/REMOVE.txt b/bin/REMOVE.txt
new file mode 100644
index 0000000..f95d44d
--- /dev/null
+++ b/bin/REMOVE.txt
@@ -0,0 +1 @@
+Remove me...
diff --git a/bin/pytop-0-0-1-x64.deb b/bin/pytop-0-0-1-x64.deb
deleted file mode 100644
index e6ad1ca..0000000
Binary files a/bin/pytop-0-0-1-x64.deb and /dev/null differ
diff --git a/src/debs/pyFfm-0-0-1-x64/DEBIAN/postrm b/src/debs/pyFfm-0-0-1-x64/DEBIAN/postrm
deleted file mode 100755
index 4962c4c..0000000
--- a/src/debs/pyFfm-0-0-1-x64/DEBIAN/postrm
+++ /dev/null
@@ -1,11 +0,0 @@
-#!/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
deleted file mode 100755
index 9f3608f..0000000
Binary files a/src/debs/pyFfm-0-0-1-x64/bin/pytop and /dev/null 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
deleted file mode 100644
index 99795ce..0000000
--- a/src/debs/pyFfm-0-0-1-x64/opt/PyFM/PyFM.py
+++ /dev/null
@@ -1,36 +0,0 @@
-#!/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/resources/PyTop.glade b/src/debs/pyFfm-0-0-1-x64/opt/PyFM/resources/PyTop.glade
deleted file mode 100644
index d2d2d35..0000000
--- a/src/debs/pyFfm-0-0-1-x64/opt/PyFM/resources/PyTop.glade
+++ /dev/null
@@ -1,305 +0,0 @@
-
-
-
-
-
-
-
-
-
- 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/utils/Settings.py b/src/debs/pyFfm-0-0-1-x64/opt/PyFM/utils/Settings.py
deleted file mode 100644
index 6c612f5..0000000
--- a/src/debs/pyFfm-0-0-1-x64/opt/PyFM/utils/Settings.py
+++ /dev/null
@@ -1,139 +0,0 @@
-
-# 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/DEBIAN/control b/src/debs/solarfm-0-0-1-x64/DEBIAN/control
similarity index 77%
rename from src/debs/pyFfm-0-0-1-x64/DEBIAN/control
rename to src/debs/solarfm-0-0-1-x64/DEBIAN/control
index e22da34..6be221d 100644
--- a/src/debs/pyFfm-0-0-1-x64/DEBIAN/control
+++ b/src/debs/solarfm-0-0-1-x64/DEBIAN/control
@@ -5,4 +5,4 @@ Priority: optional
Architecture: amd64
Depends: ffmpegthumbnailer (>= 2.0.10-0.1)
Maintainer: Maxim Stewart <1itdominator@gmail.com>
-Description: Pytop is a custom desktop GUI.
+Description: SolarFM is a Gtk + Python file manager.
diff --git a/src/debs/solarfm-0-0-1-x64/DEBIAN/postrm b/src/debs/solarfm-0-0-1-x64/DEBIAN/postrm
new file mode 100755
index 0000000..eb94f1c
--- /dev/null
+++ b/src/debs/solarfm-0-0-1-x64/DEBIAN/postrm
@@ -0,0 +1,11 @@
+#!/bin/bash
+#postrm (script executed after uninstalling the package)
+#set -e
+
+if [ -f /bin/solarfm ]; then
+ rm /bin/solarfm
+fi
+
+if [ -d /opt/SolarFM ]; then
+ rm -rf /opt/SolarFM
+fi
diff --git a/src/debs/solarfm-0-0-1-x64/bin/solarfm b/src/debs/solarfm-0-0-1-x64/bin/solarfm
new file mode 100755
index 0000000..53fc165
Binary files /dev/null and b/src/debs/solarfm-0-0-1-x64/bin/solarfm differ
diff --git a/src/versions/pyfm-0.0.1/PyFM/new/pyfm/__builtins__.py b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/__builtins__.py
similarity index 100%
rename from src/versions/pyfm-0.0.1/PyFM/new/pyfm/__builtins__.py
rename to src/debs/solarfm-0-0-1-x64/opt/SolarFM/__builtins__.py
diff --git a/src/versions/pyfm-0.0.1/PyFM/new/pyfm/__init__.py b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/__init__.py
similarity index 100%
rename from src/versions/pyfm-0.0.1/PyFM/new/pyfm/__init__.py
rename to src/debs/solarfm-0-0-1-x64/opt/SolarFM/__init__.py
diff --git a/src/versions/pyfm-0.0.1/PyFM/new/pyfm/__main__.py b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/__main__.py
similarity index 96%
rename from src/versions/pyfm-0.0.1/PyFM/new/pyfm/__main__.py
rename to src/debs/solarfm-0-0-1-x64/opt/SolarFM/__main__.py
index b808f3e..91e7ad6 100644
--- a/src/versions/pyfm-0.0.1/PyFM/new/pyfm/__main__.py
+++ b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/__main__.py
@@ -20,7 +20,7 @@ from __init__ import Main
if __name__ == "__main__":
try:
- setproctitle('PyFM')
+ setproctitle('solarfm')
faulthandler.enable() # For better debug info
parser = argparse.ArgumentParser()
# Add long and short arguments
diff --git a/src/versions/pyfm-0.0.1/PyFM/new/pyfm/resources/Main_Window.glade b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/resources/Main_Window.glade
similarity index 99%
rename from src/versions/pyfm-0.0.1/PyFM/new/pyfm/resources/Main_Window.glade
rename to src/debs/solarfm-0-0-1-x64/opt/SolarFM/resources/Main_Window.glade
index 55386e8..a146017 100644
--- a/src/versions/pyfm-0.0.1/PyFM/new/pyfm/resources/Main_Window.glade
+++ b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/resources/Main_Window.glade
@@ -6,15 +6,15 @@
False
5
center-on-parent
- pyfm.png
+ solarfm.png
dialog
center
- PyFM
+ SolarFM
0.0.1
Copyright (C) 2021 GPL2
by ITDominator
- https://code.itdominator.com/itdominator/PyFM
- PyFM - Copyright (C) 2021 ITDominator GPL2
+ https://code.itdominator.com/itdominator/SolarFM
+ SolarFM - Copyright (C) 2021 ITDominator GPL2
GNU GENERAL PUBLIC LICENSE
@@ -361,10 +361,9 @@ Public License instead of this License.
ITDominator <1itdominator@gmail.com>
-PyFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspection.
-
+SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspection.
translator-credits
- pyfm-64x64.png
+ solarfm-64x64.png
True
custom
@@ -798,7 +797,7 @@ PyFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspecti
center
1670
830
- pyfm.png
+ solarfm.png
center
diff --git a/src/versions/pyfm-0.0.1/PyFM/new/pyfm/resources/pyfm-64x64.png b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/resources/solarfm-64x64.png
similarity index 100%
rename from src/versions/pyfm-0.0.1/PyFM/new/pyfm/resources/pyfm-64x64.png
rename to src/debs/solarfm-0-0-1-x64/opt/SolarFM/resources/solarfm-64x64.png
diff --git a/src/versions/pyfm-0.0.1/PyFM/new/pyfm/resources/pyfm.png b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/resources/solarfm.png
similarity index 100%
rename from src/versions/pyfm-0.0.1/PyFM/new/pyfm/resources/pyfm.png
rename to src/debs/solarfm-0-0-1-x64/opt/SolarFM/resources/solarfm.png
diff --git a/src/versions/pyfm-0.0.1/PyFM/new/pyfm/resources/stylesheet.css b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/resources/stylesheet.css
similarity index 100%
rename from src/versions/pyfm-0.0.1/PyFM/new/pyfm/resources/stylesheet.css
rename to src/debs/solarfm-0-0-1-x64/opt/SolarFM/resources/stylesheet.css
diff --git a/src/versions/pyfm-0.0.1/PyFM/new/pyfm/shellfm/__init__.py b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/__init__.py
similarity index 100%
rename from src/versions/pyfm-0.0.1/PyFM/new/pyfm/shellfm/__init__.py
rename to src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/__init__.py
diff --git a/src/versions/pyfm-0.0.1/PyFM/new/pyfm/shellfm/windows/Window.py b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/Window.py
similarity index 100%
rename from src/versions/pyfm-0.0.1/PyFM/new/pyfm/shellfm/windows/Window.py
rename to src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/Window.py
diff --git a/src/versions/pyfm-0.0.1/PyFM/new/pyfm/shellfm/windows/WindowController.py b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/WindowController.py
similarity index 98%
rename from src/versions/pyfm-0.0.1/PyFM/new/pyfm/shellfm/windows/WindowController.py
rename to src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/WindowController.py
index f1c8e26..0cd271e 100644
--- a/src/versions/pyfm-0.0.1/PyFM/new/pyfm/shellfm/windows/WindowController.py
+++ b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/WindowController.py
@@ -17,7 +17,7 @@ def threaded(fn):
class WindowController:
def __init__(self):
USER_HOME = path.expanduser('~')
- CONFIG_PATH = USER_HOME + "/.config/pyfm"
+ CONFIG_PATH = USER_HOME + "/.config/solarfm"
self.session_file = CONFIG_PATH + "/session.json"
self._event_sleep_time = 1
diff --git a/src/versions/pyfm-0.0.1/PyFM/new/pyfm/shellfm/windows/__init__.py b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/__init__.py
similarity index 100%
rename from src/versions/pyfm-0.0.1/PyFM/new/pyfm/shellfm/windows/__init__.py
rename to src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/__init__.py
diff --git a/src/versions/pyfm-0.0.1/PyFM/new/pyfm/shellfm/windows/view/Path.py b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/view/Path.py
similarity index 100%
rename from src/versions/pyfm-0.0.1/PyFM/new/pyfm/shellfm/windows/view/Path.py
rename to src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/view/Path.py
diff --git a/src/versions/pyfm-0.0.1/PyFM/new/pyfm/shellfm/windows/view/View.py b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/view/View.py
similarity index 100%
rename from src/versions/pyfm-0.0.1/PyFM/new/pyfm/shellfm/windows/view/View.py
rename to src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/view/View.py
diff --git a/src/versions/pyfm-0.0.1/PyFM/new/pyfm/shellfm/windows/view/__init__.py b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/view/__init__.py
similarity index 100%
rename from src/versions/pyfm-0.0.1/PyFM/new/pyfm/shellfm/windows/view/__init__.py
rename to src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/view/__init__.py
diff --git a/src/versions/pyfm-0.0.1/PyFM/new/pyfm/shellfm/windows/view/icons/Icon.py b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/view/icons/Icon.py
similarity index 100%
rename from src/versions/pyfm-0.0.1/PyFM/new/pyfm/shellfm/windows/view/icons/Icon.py
rename to src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/view/icons/Icon.py
diff --git a/src/versions/pyfm-0.0.1/PyFM/new/pyfm/shellfm/windows/view/icons/__init__.py b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/view/icons/__init__.py
similarity index 100%
rename from src/versions/pyfm-0.0.1/PyFM/new/pyfm/shellfm/windows/view/icons/__init__.py
rename to src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/view/icons/__init__.py
diff --git a/src/versions/pyfm-0.0.1/PyFM/new/pyfm/shellfm/windows/view/icons/mixins/DesktopIconMixin.py b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/view/icons/mixins/DesktopIconMixin.py
similarity index 100%
rename from src/versions/pyfm-0.0.1/PyFM/new/pyfm/shellfm/windows/view/icons/mixins/DesktopIconMixin.py
rename to src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/view/icons/mixins/DesktopIconMixin.py
diff --git a/src/versions/pyfm-0.0.1/PyFM/new/pyfm/shellfm/windows/view/icons/mixins/VideoIconMixin.py b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/view/icons/mixins/VideoIconMixin.py
similarity index 100%
rename from src/versions/pyfm-0.0.1/PyFM/new/pyfm/shellfm/windows/view/icons/mixins/VideoIconMixin.py
rename to src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/view/icons/mixins/VideoIconMixin.py
diff --git a/src/versions/pyfm-0.0.1/PyFM/new/pyfm/shellfm/windows/view/icons/mixins/__init__.py b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/view/icons/mixins/__init__.py
similarity index 100%
rename from src/versions/pyfm-0.0.1/PyFM/new/pyfm/shellfm/windows/view/icons/mixins/__init__.py
rename to src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/view/icons/mixins/__init__.py
diff --git a/src/versions/pyfm-0.0.1/PyFM/new/pyfm/shellfm/windows/view/icons/mixins/xdg/BaseDirectory.py b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/view/icons/mixins/xdg/BaseDirectory.py
similarity index 100%
rename from src/versions/pyfm-0.0.1/PyFM/new/pyfm/shellfm/windows/view/icons/mixins/xdg/BaseDirectory.py
rename to src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/view/icons/mixins/xdg/BaseDirectory.py
diff --git a/src/versions/pyfm-0.0.1/PyFM/new/pyfm/shellfm/windows/view/icons/mixins/xdg/Config.py b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/view/icons/mixins/xdg/Config.py
similarity index 100%
rename from src/versions/pyfm-0.0.1/PyFM/new/pyfm/shellfm/windows/view/icons/mixins/xdg/Config.py
rename to src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/view/icons/mixins/xdg/Config.py
diff --git a/src/versions/pyfm-0.0.1/PyFM/new/pyfm/shellfm/windows/view/icons/mixins/xdg/DesktopEntry.py b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/view/icons/mixins/xdg/DesktopEntry.py
similarity index 100%
rename from src/versions/pyfm-0.0.1/PyFM/new/pyfm/shellfm/windows/view/icons/mixins/xdg/DesktopEntry.py
rename to src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/view/icons/mixins/xdg/DesktopEntry.py
diff --git a/src/versions/pyfm-0.0.1/PyFM/new/pyfm/shellfm/windows/view/icons/mixins/xdg/Exceptions.py b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/view/icons/mixins/xdg/Exceptions.py
similarity index 100%
rename from src/versions/pyfm-0.0.1/PyFM/new/pyfm/shellfm/windows/view/icons/mixins/xdg/Exceptions.py
rename to src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/view/icons/mixins/xdg/Exceptions.py
diff --git a/src/versions/pyfm-0.0.1/PyFM/new/pyfm/shellfm/windows/view/icons/mixins/xdg/IconTheme.py b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/view/icons/mixins/xdg/IconTheme.py
similarity index 100%
rename from src/versions/pyfm-0.0.1/PyFM/new/pyfm/shellfm/windows/view/icons/mixins/xdg/IconTheme.py
rename to src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/view/icons/mixins/xdg/IconTheme.py
diff --git a/src/versions/pyfm-0.0.1/PyFM/new/pyfm/shellfm/windows/view/icons/mixins/xdg/IniFile.py b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/view/icons/mixins/xdg/IniFile.py
similarity index 100%
rename from src/versions/pyfm-0.0.1/PyFM/new/pyfm/shellfm/windows/view/icons/mixins/xdg/IniFile.py
rename to src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/view/icons/mixins/xdg/IniFile.py
diff --git a/src/versions/pyfm-0.0.1/PyFM/new/pyfm/shellfm/windows/view/icons/mixins/xdg/Locale.py b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/view/icons/mixins/xdg/Locale.py
similarity index 100%
rename from src/versions/pyfm-0.0.1/PyFM/new/pyfm/shellfm/windows/view/icons/mixins/xdg/Locale.py
rename to src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/view/icons/mixins/xdg/Locale.py
diff --git a/src/versions/pyfm-0.0.1/PyFM/new/pyfm/shellfm/windows/view/icons/mixins/xdg/Menu.py b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/view/icons/mixins/xdg/Menu.py
similarity index 100%
rename from src/versions/pyfm-0.0.1/PyFM/new/pyfm/shellfm/windows/view/icons/mixins/xdg/Menu.py
rename to src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/view/icons/mixins/xdg/Menu.py
diff --git a/src/versions/pyfm-0.0.1/PyFM/new/pyfm/shellfm/windows/view/icons/mixins/xdg/MenuEditor.py b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/view/icons/mixins/xdg/MenuEditor.py
similarity index 100%
rename from src/versions/pyfm-0.0.1/PyFM/new/pyfm/shellfm/windows/view/icons/mixins/xdg/MenuEditor.py
rename to src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/view/icons/mixins/xdg/MenuEditor.py
diff --git a/src/versions/pyfm-0.0.1/PyFM/new/pyfm/shellfm/windows/view/icons/mixins/xdg/Mime.py b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/view/icons/mixins/xdg/Mime.py
similarity index 100%
rename from src/versions/pyfm-0.0.1/PyFM/new/pyfm/shellfm/windows/view/icons/mixins/xdg/Mime.py
rename to src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/view/icons/mixins/xdg/Mime.py
diff --git a/src/versions/pyfm-0.0.1/PyFM/new/pyfm/shellfm/windows/view/icons/mixins/xdg/RecentFiles.py b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/view/icons/mixins/xdg/RecentFiles.py
similarity index 100%
rename from src/versions/pyfm-0.0.1/PyFM/new/pyfm/shellfm/windows/view/icons/mixins/xdg/RecentFiles.py
rename to src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/view/icons/mixins/xdg/RecentFiles.py
diff --git a/src/versions/pyfm-0.0.1/PyFM/new/pyfm/shellfm/windows/view/icons/mixins/xdg/__init__.py b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/view/icons/mixins/xdg/__init__.py
similarity index 100%
rename from src/versions/pyfm-0.0.1/PyFM/new/pyfm/shellfm/windows/view/icons/mixins/xdg/__init__.py
rename to src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/view/icons/mixins/xdg/__init__.py
diff --git a/src/versions/pyfm-0.0.1/PyFM/new/pyfm/shellfm/windows/view/icons/mixins/xdg/util.py b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/view/icons/mixins/xdg/util.py
similarity index 100%
rename from src/versions/pyfm-0.0.1/PyFM/new/pyfm/shellfm/windows/view/icons/mixins/xdg/util.py
rename to src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/view/icons/mixins/xdg/util.py
diff --git a/src/versions/pyfm-0.0.1/PyFM/new/pyfm/shellfm/windows/view/utils/FileHandler.py b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/view/utils/FileHandler.py
similarity index 100%
rename from src/versions/pyfm-0.0.1/PyFM/new/pyfm/shellfm/windows/view/utils/FileHandler.py
rename to src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/view/utils/FileHandler.py
diff --git a/src/versions/pyfm-0.0.1/PyFM/new/pyfm/shellfm/windows/view/utils/Launcher.py b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/view/utils/Launcher.py
similarity index 100%
rename from src/versions/pyfm-0.0.1/PyFM/new/pyfm/shellfm/windows/view/utils/Launcher.py
rename to src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/view/utils/Launcher.py
diff --git a/src/versions/pyfm-0.0.1/PyFM/new/pyfm/shellfm/windows/view/utils/Settings.py b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/view/utils/Settings.py
similarity index 98%
rename from src/versions/pyfm-0.0.1/PyFM/new/pyfm/shellfm/windows/view/utils/Settings.py
rename to src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/view/utils/Settings.py
index 7dac2b9..408bc42 100644
--- a/src/versions/pyfm-0.0.1/PyFM/new/pyfm/shellfm/windows/view/utils/Settings.py
+++ b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/view/utils/Settings.py
@@ -14,7 +14,7 @@ class Settings:
logger = None
USER_HOME = path.expanduser('~')
- CONFIG_PATH = USER_HOME + "/.config/pyfm"
+ CONFIG_PATH = USER_HOME + "/.config/solarfm"
CONFIG_FILE = CONFIG_PATH + "/settings.json"
HIDE_HIDDEN_FILES = True
diff --git a/src/versions/pyfm-0.0.1/PyFM/new/pyfm/shellfm/windows/view/utils/__init__.py b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/view/utils/__init__.py
similarity index 100%
rename from src/versions/pyfm-0.0.1/PyFM/new/pyfm/shellfm/windows/view/utils/__init__.py
rename to src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/view/utils/__init__.py
diff --git a/src/versions/pyfm-0.0.1/PyFM/new/pyfm/signal_classes/Controller.py b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/signal_classes/Controller.py
similarity index 100%
rename from src/versions/pyfm-0.0.1/PyFM/new/pyfm/signal_classes/Controller.py
rename to src/debs/solarfm-0-0-1-x64/opt/SolarFM/signal_classes/Controller.py
diff --git a/src/versions/pyfm-0.0.1/PyFM/new/pyfm/signal_classes/Controller_Data.py b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/signal_classes/Controller_Data.py
similarity index 100%
rename from src/versions/pyfm-0.0.1/PyFM/new/pyfm/signal_classes/Controller_Data.py
rename to src/debs/solarfm-0-0-1-x64/opt/SolarFM/signal_classes/Controller_Data.py
diff --git a/src/versions/pyfm-0.0.1/PyFM/new/pyfm/signal_classes/DBusControllerMixin.py b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/signal_classes/DBusControllerMixin.py
similarity index 95%
rename from src/versions/pyfm-0.0.1/PyFM/new/pyfm/signal_classes/DBusControllerMixin.py
rename to src/debs/solarfm-0-0-1-x64/opt/SolarFM/signal_classes/DBusControllerMixin.py
index 2f8f955..ce49ee7 100644
--- a/src/versions/pyfm-0.0.1/PyFM/new/pyfm/signal_classes/DBusControllerMixin.py
+++ b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/signal_classes/DBusControllerMixin.py
@@ -17,7 +17,7 @@ class DBusControllerMixin:
@threaded
def create_ipc_server(self):
- listener = Listener(('127.0.0.1', 4848), authkey=b'pyfm-ipc')
+ listener = Listener(('127.0.0.1', 4848), authkey=b'solarfm-ipc')
self.is_ipc_alive = True
while event_system.keep_ipc_alive:
conn = listener.accept()
@@ -56,7 +56,7 @@ class DBusControllerMixin:
def send_ipc_message(self, message="Empty Data..."):
try:
- conn = Client(('127.0.0.1', 4848), authkey=b'pyfm-ipc')
+ conn = Client(('127.0.0.1', 4848), authkey=b'solar-ipc')
conn.send(message)
conn.send('close connection')
except Exception as e:
diff --git a/src/versions/pyfm-0.0.1/PyFM/new/pyfm/signal_classes/KeyboardSignalsMixin.py b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/signal_classes/KeyboardSignalsMixin.py
similarity index 97%
rename from src/versions/pyfm-0.0.1/PyFM/new/pyfm/signal_classes/KeyboardSignalsMixin.py
rename to src/debs/solarfm-0-0-1-x64/opt/SolarFM/signal_classes/KeyboardSignalsMixin.py
index 181f636..657753b 100644
--- a/src/versions/pyfm-0.0.1/PyFM/new/pyfm/signal_classes/KeyboardSignalsMixin.py
+++ b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/signal_classes/KeyboardSignalsMixin.py
@@ -40,7 +40,7 @@ class KeyboardSignalsMixin:
self.tear_down()
if (self.ctrlDown and keyname == "slash") or keyname == "home":
self.builder.get_object("go_home").released()
- if self.ctrlDown and keyname == "r":
+ if (self.ctrlDown and keyname == "r") or keyname == "f5":
self.builder.get_object("refresh_view").released()
if (self.ctrlDown and keyname == "up") or (self.ctrlDown and keyname == "u"):
self.builder.get_object("go_up").released()
diff --git a/src/versions/pyfm-0.0.1/PyFM/new/pyfm/signal_classes/ShowHideMixin.py b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/signal_classes/ShowHideMixin.py
similarity index 100%
rename from src/versions/pyfm-0.0.1/PyFM/new/pyfm/signal_classes/ShowHideMixin.py
rename to src/debs/solarfm-0-0-1-x64/opt/SolarFM/signal_classes/ShowHideMixin.py
diff --git a/src/versions/pyfm-0.0.1/PyFM/new/pyfm/signal_classes/__init__.py b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/signal_classes/__init__.py
similarity index 100%
rename from src/versions/pyfm-0.0.1/PyFM/new/pyfm/signal_classes/__init__.py
rename to src/debs/solarfm-0-0-1-x64/opt/SolarFM/signal_classes/__init__.py
diff --git a/src/versions/pyfm-0.0.1/PyFM/new/pyfm/signal_classes/mixins/PaneMixin.py b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/signal_classes/mixins/PaneMixin.py
similarity index 100%
rename from src/versions/pyfm-0.0.1/PyFM/new/pyfm/signal_classes/mixins/PaneMixin.py
rename to src/debs/solarfm-0-0-1-x64/opt/SolarFM/signal_classes/mixins/PaneMixin.py
diff --git a/src/versions/pyfm-0.0.1/PyFM/new/pyfm/signal_classes/mixins/TabMixin.py b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/signal_classes/mixins/TabMixin.py
similarity index 100%
rename from src/versions/pyfm-0.0.1/PyFM/new/pyfm/signal_classes/mixins/TabMixin.py
rename to src/debs/solarfm-0-0-1-x64/opt/SolarFM/signal_classes/mixins/TabMixin.py
diff --git a/src/versions/pyfm-0.0.1/PyFM/new/pyfm/signal_classes/mixins/WidgetFileActionMixin.py b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/signal_classes/mixins/WidgetFileActionMixin.py
similarity index 100%
rename from src/versions/pyfm-0.0.1/PyFM/new/pyfm/signal_classes/mixins/WidgetFileActionMixin.py
rename to src/debs/solarfm-0-0-1-x64/opt/SolarFM/signal_classes/mixins/WidgetFileActionMixin.py
diff --git a/src/versions/pyfm-0.0.1/PyFM/new/pyfm/signal_classes/mixins/WidgetMixin.py b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/signal_classes/mixins/WidgetMixin.py
similarity index 100%
rename from src/versions/pyfm-0.0.1/PyFM/new/pyfm/signal_classes/mixins/WidgetMixin.py
rename to src/debs/solarfm-0-0-1-x64/opt/SolarFM/signal_classes/mixins/WidgetMixin.py
diff --git a/src/versions/pyfm-0.0.1/PyFM/new/pyfm/signal_classes/mixins/WindowMixin.py b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/signal_classes/mixins/WindowMixin.py
similarity index 99%
rename from src/versions/pyfm-0.0.1/PyFM/new/pyfm/signal_classes/mixins/WindowMixin.py
rename to src/debs/solarfm-0-0-1-x64/opt/SolarFM/signal_classes/mixins/WindowMixin.py
index 2441b46..9aab631 100644
--- a/src/versions/pyfm-0.0.1/PyFM/new/pyfm/signal_classes/mixins/WindowMixin.py
+++ b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/signal_classes/mixins/WindowMixin.py
@@ -63,7 +63,7 @@ class WindowMixin(TabMixin):
ctx = notebook.get_style_context()
ctx.add_class("notebook-selected-focus")
- self.window.set_title("PyFM ~ " + dir)
+ self.window.set_title("SolarFM ~ " + dir)
self.set_bottom_labels(view)
def set_path_text(self, wid, tid):
diff --git a/src/versions/pyfm-0.0.1/PyFM/new/pyfm/signal_classes/mixins/__init__.py b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/signal_classes/mixins/__init__.py
similarity index 100%
rename from src/versions/pyfm-0.0.1/PyFM/new/pyfm/signal_classes/mixins/__init__.py
rename to src/debs/solarfm-0-0-1-x64/opt/SolarFM/signal_classes/mixins/__init__.py
diff --git a/src/versions/pyfm-0.0.1/PyFM/new/pyfm/pyfm b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/solarfm
similarity index 94%
rename from src/versions/pyfm-0.0.1/PyFM/new/pyfm/pyfm
rename to src/debs/solarfm-0-0-1-x64/opt/SolarFM/solarfm
index fa3604b..40cd1fd 100755
--- a/src/versions/pyfm-0.0.1/PyFM/new/pyfm/pyfm
+++ b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/solarfm
@@ -13,6 +13,6 @@ function main() {
echo "Working Dir: " $(pwd)
source "/home/abaddon/Portable_Apps/py-venvs/flask-apps-venv/venv/bin/activate"
- python ../pyfm "$@"
+ python ../solarfm "$@"
}
main "$@";
diff --git a/src/versions/pyfm-0.0.1/PyFM/new/pyfm/utils/Logger.py b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/utils/Logger.py
similarity index 100%
rename from src/versions/pyfm-0.0.1/PyFM/new/pyfm/utils/Logger.py
rename to src/debs/solarfm-0-0-1-x64/opt/SolarFM/utils/Logger.py
diff --git a/src/versions/pyfm-0.0.1/PyFM/new/pyfm/utils/Settings.py b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/utils/Settings.py
similarity index 100%
rename from src/versions/pyfm-0.0.1/PyFM/new/pyfm/utils/Settings.py
rename to src/debs/solarfm-0-0-1-x64/opt/SolarFM/utils/Settings.py
diff --git a/src/versions/pyfm-0.0.1/PyFM/new/pyfm/utils/__init__.py b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/utils/__init__.py
similarity index 100%
rename from src/versions/pyfm-0.0.1/PyFM/new/pyfm/utils/__init__.py
rename to src/debs/solarfm-0-0-1-x64/opt/SolarFM/utils/__init__.py
diff --git a/src/debs/pyFfm-0-0-1-x64/usr/share/doc/pytop/copyright b/src/debs/solarfm-0-0-1-x64/usr/share/doc/solarfm/copyright
similarity index 87%
rename from src/debs/pyFfm-0-0-1-x64/usr/share/doc/pytop/copyright
rename to src/debs/solarfm-0-0-1-x64/usr/share/doc/solarfm/copyright
index 04b188e..b293cfe 100644
--- a/src/debs/pyFfm-0-0-1-x64/usr/share/doc/pytop/copyright
+++ b/src/debs/solarfm-0-0-1-x64/usr/share/doc/solarfm/copyright
@@ -1,7 +1,7 @@
-Pytop is copyright 2019 Maxim Stewart.
-Pytop is currently developed by ITDominator <1itdominator@gmail.com>.
+SolarFM is copyright 2021 Maxim Stewart.
+SolarFM is currently developed by ITDominator <1itdominator@gmail.com>.
-License: GPLv2+
+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
diff --git a/src/versions/pyfm-0.0.1/PyFM/new/pyfm/resources/appchooserdlg.glade b/src/versions/pyfm-0.0.1/PyFM/new/pyfm/resources/appchooserdlg.glade
deleted file mode 100644
index 8d596cb..0000000
--- a/src/versions/pyfm-0.0.1/PyFM/new/pyfm/resources/appchooserdlg.glade
+++ /dev/null
@@ -1,331 +0,0 @@
-
-
-
-
-
- False
- 6
- Choose Application
- 420
- dialog
-
-
- True
- False
- 12
-
-
- True
- False
- end
-
-
- gtk-cancel
- True
- True
- True
- False
- True
-
-
- True
- True
- 0
-
-
-
-
- gtk-ok
- True
- True
- True
- True
- False
- True
-
-
- True
- True
- 1
-
-
-
-
- False
- False
- end
- 0
-
-
-
-
- True
- False
- 4
- 4
- Please choose an application or enter a command:
- True
- 0
-
-
- False
- False
- 1
- 1
-
-
-
-
- True
- False
- 6
-
-
- True
- False
- 4
- 0
- File Type:
- 0
-
-
- False
- False
- 0
-
-
-
-
- True
- False
- True
-
-
- False
- False
- 2
-
-
-
-
- False
- True
- 2
- 3
-
-
-
-
- True
- True
-
-
-
- True
- True
- in
-
-
- True
- True
- True
- False
-
-
-
-
-
-
-
-
-
- True
- False
- _Associated Apps
- True
-
-
- False
-
-
-
-
- True
- True
- in
-
-
- True
- True
- False
-
-
-
-
-
-
-
- 1
-
-
-
-
- True
- False
- A_ll Apps
- True
-
-
- 1
- False
-
-
-
-
- False
- True
- 4
-
-
-
-
- True
- False
- 4
- 12
-
-
- True
- False
- _Command:
- True
- cmdline
-
-
- False
- False
- 0
-
-
-
-
- True
- True
- True
-
-
- True
- True
- 1
-
-
-
-
- True
- True
- False
-
-
-
- True
- False
- 0
- 0
-
-
- True
- False
- 2
-
-
- True
- False
- gtk-open
-
-
- False
- False
- 0
-
-
-
-
- True
- False
- _Browse
- True
-
-
- False
- False
- 1
-
-
-
-
-
-
-
-
- False
- False
- 2
-
-
-
-
- False
- True
- 5
-
-
-
-
- True
- False
- 6
-
-
- Opened in Terminal
- False
- True
- False
- True
- True
-
-
- False
- False
- 0
-
-
-
-
- _Set as default application for this file type
- True
- True
- False
- True
- True
-
-
- False
- False
- 1
-
-
-
-
- False
- True
- 6
-
-
-
-
-
- cancelbutton
- okbutton
-
-
-
diff --git a/src/versions/pyfm-0.0.1/PyFM/new/pyfm/resources/file_properties.glade b/src/versions/pyfm-0.0.1/PyFM/new/pyfm/resources/file_properties.glade
deleted file mode 100644
index aa8e891..0000000
--- a/src/versions/pyfm-0.0.1/PyFM/new/pyfm/resources/file_properties.glade
+++ /dev/null
@@ -1,858 +0,0 @@
-
-
-
-
-
- False
- 6
- File Properties
- center-on-parent
- 360
- dialog
- center
-
-
-
- True
- False
- 12
-
-
- True
- False
- end
-
-
- gtk-cancel
- True
- True
- True
- False
- True
-
-
-
- True
- True
- 0
-
-
-
-
- gtk-ok
- True
- True
- True
- False
- True
-
-
-
- True
- True
- 1
-
-
-
-
- False
- False
- end
- 0
-
-
-
-
- True
- True
- 6
-
-
- True
- False
- 6
- 6
- 12
-
-
- True
- False
- 4
- 9
- 2
- 12
- 6
-
-
- True
- False
- <b>File _Name:</b>
- True
- True
- file_name
- 0
-
-
- GTK_FILL
-
-
-
-
-
- True
- True
-
-
- 1
- 2
-
-
-
-
-
- True
- False
- <b>_Location:</b>
- True
- True
- location
- 0
-
-
- 1
- 2
- GTK_FILL
-
-
-
-
-
- True
- True
-
-
- 1
- 2
- 1
- 2
- GTK_FILL
-
-
-
-
-
- False
- <b>Link _Target:</b>
- True
- True
- target
- 0
-
-
- 2
- 3
- GTK_FILL
-
-
-
-
-
- True
-
-
- 1
- 2
- 2
- 3
- GTK_FILL
-
-
-
-
-
- True
- False
- <b>Type:</b>
- True
- True
- 0
- 0
-
-
- 3
- 4
- GTK_FILL
- GTK_FILL
-
-
-
-
- True
- True
- True
- end
- 0
- 0
-
-
- 1
- 2
- 3
- 4
- GTK_FILL
-
-
-
-
-
- True
- False
- <b>Opens _With:</b>
- True
- True
- open_with
- 0
-
-
- 4
- 5
- GTK_FILL
-
-
-
-
-
- True
- False
-
-
- 1
- 2
- 4
- 5
- GTK_FILL
- GTK_FILL
-
-
-
-
- True
- False
- <b>Total Size:</b>
- True
- True
- 0
-
-
- 5
- 6
- GTK_FILL
-
-
-
-
-
- True
- True
- True
- 0
-
-
- 1
- 2
- 5
- 6
- GTK_FILL
-
-
-
-
-
- True
- False
- <b>Size On Disk:</b>
- True
- True
- 0
-
-
- 6
- 7
- GTK_FILL
-
-
-
-
-
- True
- True
- True
- 0
-
-
- 1
- 2
- 6
- 7
- GTK_FILL
-
-
-
-
-
- True
- False
- <b>Count:</b>
- True
- True
- 0
-
-
- 7
- 8
- GTK_FILL
-
-
-
-
-
- True
- True
- True
- 0
-
-
- 1
- 2
- 7
- 8
- GTK_FILL
-
-
-
-
-
- True
- False
- <b>_Modified:</b>
- True
- True
- mtime
- 0
-
-
- 8
- 9
- GTK_FILL
-
-
-
-
-
- True
- True
-
-
- 1
- 2
- 8
- 9
- GTK_FILL
-
-
-
-
-
- True
- False
- <b>_Accessed:</b>
- True
- True
- atime
- 0
-
-
- 9
- 10
- GTK_FILL
-
-
-
-
-
- True
- True
-
-
- 1
- 2
- 9
- 10
- GTK_FILL
-
-
-
-
-
-
-
-
-
- True
- False
- _Info
- True
-
-
- False
-
-
-
-
- True
- False
- 6
- 6
- 12
-
-
- True
- False
- 6
-
-
- True
- False
- 2
- 2
- 2
- 12
- 6
-
-
- True
- False
- <b>_Owner:</b>
- True
- True
- owner
- 0
-
-
- GTK_FILL
-
-
-
-
-
- True
- False
- <b>_Group:</b>
- True
- True
- group
- 0
-
-
- 1
- 2
- GTK_FILL
-
-
-
-
-
- True
- True
-
-
- 1
- 2
-
-
-
-
-
- True
- True
-
-
- 1
- 2
- 1
- 2
-
-
-
-
-
- False
- False
- 0
-
-
-
-
- True
- False
-
-
- False
- False
- 1
-
-
-
-
- True
- False
- 4
- 3
- 6
- 12
- 6
-
-
- True
- False
- <b>Owner:</b>
- True
- True
- 0
-
-
- GTK_FILL
-
-
-
-
-
- True
- False
- <b>Group:</b>
- True
- True
- 0
-
-
- 1
- 2
- GTK_FILL
-
-
-
-
-
- True
- False
- <b>Other:</b>
- True
- True
- 0
-
-
- 2
- 3
- GTK_FILL
-
-
-
-
-
- Read
- True
- True
- False
- 2
- True
- True
-
-
- 1
- 2
- GTK_FILL
-
-
-
-
-
- Read
- True
- True
- False
- 2
- True
- True
-
-
- 1
- 2
- 1
- 2
- GTK_FILL
-
-
-
-
-
- Read
- True
- True
- False
- 2
- True
- True
-
-
- 1
- 2
- 2
- 3
- GTK_FILL
-
-
-
-
-
- Write
- True
- True
- False
- 2
- True
- True
-
-
- 2
- 3
- GTK_FILL
-
-
-
-
-
- Write
- True
- True
- False
- 2
- True
- True
-
-
- 2
- 3
- 1
- 2
- GTK_FILL
-
-
-
-
-
- Write
- True
- True
- False
- 2
- True
- True
-
-
- 2
- 3
- 2
- 3
- GTK_FILL
-
-
-
-
-
- Execute
- True
- True
- False
- 2
- True
- True
-
-
- 3
- 4
- GTK_FILL
-
-
-
-
-
- Execute
- True
- True
- False
- 2
- True
- True
-
-
- 3
- 4
- 1
- 2
- GTK_FILL
-
-
-
-
-
- Execute
- True
- True
- False
- 2
- True
- True
-
-
- 3
- 4
- 2
- 3
- GTK_FILL
-
-
-
-
-
- Set UID
- True
- True
- False
- 2
- True
- True
-
-
- 5
- 6
- GTK_FILL
-
-
-
-
-
- Set GID
- True
- True
- False
- 2
- True
- True
-
-
- 5
- 6
- 1
- 2
- GTK_FILL
-
-
-
-
-
- Sticky
- True
- True
- False
- 2
- True
- True
-
-
- 5
- 6
- 2
- 3
- GTK_FILL
-
-
-
-
-
- True
- False
-
-
- 4
- 5
- 3
- GTK_FILL
- GTK_FILL
-
-
-
-
- False
- True
- 2
-
-
-
-
- True
- False
-
-
- False
- False
- 3
-
-
-
-
- Recursive (apply changes to folders and their contents)
- True
- True
- False
- True
-
-
- False
- True
- 4
-
-
-
-
-
-
- 1
-
-
-
-
- True
- False
- _Permissions
- True
-
-
- 1
- False
-
-
-
-
- False
- True
- 2
-
-
-
-
-
- cancel_button
- ok_button
-
-
-
diff --git a/src/versions/pyfm-0.0.1/PyFM/old/PyFM.sh b/src/versions/pyfm-0.0.1/PyFM/old/PyFM.sh
deleted file mode 100755
index de59af8..0000000
--- a/src/versions/pyfm-0.0.1/PyFM/old/PyFM.sh
+++ /dev/null
@@ -1,12 +0,0 @@
-#!/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/stylesheet.css b/src/versions/pyfm-0.0.1/PyFM/old/resources/stylesheet.css
deleted file mode 100644
index 9addcfa..0000000
--- a/src/versions/pyfm-0.0.1/PyFM/old/resources/stylesheet.css
+++ /dev/null
@@ -1,88 +0,0 @@
-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
deleted file mode 100644
index a0b2856..0000000
--- a/src/versions/pyfm-0.0.1/PyFM/old/utils/Dragging.py
+++ /dev/null
@@ -1,79 +0,0 @@
-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
deleted file mode 100644
index fbd3962..0000000
--- a/src/versions/pyfm-0.0.1/PyFM/old/utils/Events.py
+++ /dev/null
@@ -1,72 +0,0 @@
-
-# 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
deleted file mode 100644
index c4dfa3b..0000000
--- a/src/versions/pyfm-0.0.1/PyFM/old/utils/FileHandler.py
+++ /dev/null
@@ -1,93 +0,0 @@
-
-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
deleted file mode 100644
index 392de55..0000000
--- a/src/versions/pyfm-0.0.1/PyFM/old/utils/Grid.py
+++ /dev/null
@@ -1,214 +0,0 @@
-
-
-# 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
deleted file mode 100644
index 826e408..0000000
--- a/src/versions/pyfm-0.0.1/PyFM/old/utils/Icon.py
+++ /dev/null
@@ -1,167 +0,0 @@
-
-# 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/__init__.py b/src/versions/pyfm-0.0.1/PyFM/old/utils/__init__.py
deleted file mode 100644
index e291f0f..0000000
--- a/src/versions/pyfm-0.0.1/PyFM/old/utils/__init__.py
+++ /dev/null
@@ -1,6 +0,0 @@
-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/compileBin.sh b/src/versions/pyfm-0.0.1/compileBin.sh
deleted file mode 100755
index 7b0e7b2..0000000
--- a/src/versions/pyfm-0.0.1/compileBin.sh
+++ /dev/null
@@ -1,6 +0,0 @@
-#!/bin/bash
-
-function main() {
- gcc -no-pie -s PyFM_exec_bin.cpp -o pyfm
-}
-main;
diff --git a/src/versions/pyfm-0.0.1/PyFM/new/setup.py b/src/versions/solarfm-0.0.1/SolarFM/new/setup.py
similarity index 74%
rename from src/versions/pyfm-0.0.1/PyFM/new/setup.py
rename to src/versions/solarfm-0.0.1/SolarFM/new/setup.py
index 5cd2885..17117f6 100644
--- a/src/versions/pyfm-0.0.1/PyFM/new/setup.py
+++ b/src/versions/solarfm-0.0.1/SolarFM/new/setup.py
@@ -1,9 +1,9 @@
from setuptools import setup
setup(
- name='pyfm',
+ name='SolarFM',
version='0.0.1',
- packages=['pyfm'],
+ packages=['solarfm'],
install_requires=[
'setproctitle',
'PyGobject',
diff --git a/src/versions/pyfm-0.0.1/PyFM/new/pyfm.sh b/src/versions/solarfm-0.0.1/SolarFM/new/solarfm.sh
similarity index 95%
rename from src/versions/pyfm-0.0.1/PyFM/new/pyfm.sh
rename to src/versions/solarfm-0.0.1/SolarFM/new/solarfm.sh
index a527c21..67de002 100755
--- a/src/versions/pyfm-0.0.1/PyFM/new/pyfm.sh
+++ b/src/versions/solarfm-0.0.1/SolarFM/new/solarfm.sh
@@ -13,6 +13,6 @@ function main() {
echo "Working Dir: " $(pwd)
source "/home/abaddon/Portable_Apps/py-venvs/flask-apps-venv/venv/bin/activate"
- python ./pyfm
+ python ./solarfm
}
main "$@";
diff --git a/src/versions/pyfm-0.0.1/PyFM/new/pyfm.toml b/src/versions/solarfm-0.0.1/SolarFM/new/solarfm.toml
similarity index 100%
rename from src/versions/pyfm-0.0.1/PyFM/new/pyfm.toml
rename to src/versions/solarfm-0.0.1/SolarFM/new/solarfm.toml
diff --git a/src/versions/solarfm-0.0.1/SolarFM/new/solarfm/__builtins__.py b/src/versions/solarfm-0.0.1/SolarFM/new/solarfm/__builtins__.py
new file mode 100644
index 0000000..ef1affd
--- /dev/null
+++ b/src/versions/solarfm-0.0.1/SolarFM/new/solarfm/__builtins__.py
@@ -0,0 +1,66 @@
+# Python imports
+import builtins
+
+# Gtk imports
+
+# Application imports
+from signal_classes.DBusControllerMixin import DBusControllerMixin
+
+
+class Builtins(DBusControllerMixin):
+ """Docstring for __builtins__ extender"""
+
+ def __init__(self):
+ # NOTE: The format used is list of [type, target, data]
+ # Where data may be any kind of data
+ self._gui_events = []
+ self._fm_events = []
+ self.monitor_events = True
+ self.keep_ipc_alive = True
+ self.is_ipc_alive = False
+
+ # Makeshift fake "events" type system FIFO
+ def _pop_gui_event(self):
+ if len(self._gui_events) > 0:
+ return self._gui_events.pop(0)
+ return None
+
+ def _pop_fm_event(self):
+ if len(self._fm_events) > 0:
+ return self._fm_events.pop(0)
+ return None
+
+
+ def push_gui_event(self, event):
+ if len(event) == 3:
+ self._gui_events.append(event)
+ return None
+
+ raise Exception("Invald event format! Please do: [type, target, data]")
+
+ def push_fm_event(self, event):
+ if len(event) == 3:
+ self._fm_events.append(event)
+ return None
+
+ raise Exception("Invald event format! Please do: [type, target, data]")
+
+ def read_gui_event(self):
+ return self._gui_events[0]
+
+ def read_fm_event(self):
+ return self._fm_events[0]
+
+ def consume_gui_event(self):
+ return self._pop_gui_event()
+
+ def consume_fm_event(self):
+ return self._pop_fm_event()
+
+
+
+# NOTE: Just reminding myself we can add to builtins two different ways...
+# __builtins__.update({"event_system": Builtins()})
+builtins.event_system = Builtins()
+builtins.event_sleep_time = 0.5
+builtins.debug = False
diff --git a/src/versions/solarfm-0.0.1/SolarFM/new/solarfm/__init__.py b/src/versions/solarfm-0.0.1/SolarFM/new/solarfm/__init__.py
new file mode 100644
index 0000000..14e08ac
--- /dev/null
+++ b/src/versions/solarfm-0.0.1/SolarFM/new/solarfm/__init__.py
@@ -0,0 +1,49 @@
+# Python imports
+import os, inspect, time
+
+# Gtk imports
+
+# Application imports
+from utils import Settings
+from signal_classes import Controller
+from __builtins__ import Builtins
+
+
+class Main(Builtins):
+ def __init__(self, args, unknownargs):
+ event_system.create_ipc_server()
+ time.sleep(0.5)
+ if not event_system.is_ipc_alive:
+ if unknownargs:
+ for arg in unknownargs:
+ if os.path.isdir(arg):
+ message = f"FILE|{arg}"
+ event_system.send_ipc_message(message)
+
+ if args.new_tab and os.path.isdir(args.new_tab):
+ message = f"FILE|{args.new_tab}"
+ event_system.send_ipc_message(message)
+
+ raise Exception("IPC Server Exists: Will send path(s) to it and close...")
+
+
+ settings = Settings()
+ settings.createWindow()
+
+ controller = Controller(args, unknownargs, settings)
+ if not controller:
+ raise Exception("Controller exited and doesn't exist...")
+
+ # Gets the methods from the classes and sets to handler.
+ # Then, builder connects to any signals it needs.
+ classes = [controller]
+ 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)
diff --git a/src/versions/solarfm-0.0.1/SolarFM/new/solarfm/__main__.py b/src/versions/solarfm-0.0.1/SolarFM/new/solarfm/__main__.py
new file mode 100644
index 0000000..91e7ad6
--- /dev/null
+++ b/src/versions/solarfm-0.0.1/SolarFM/new/solarfm/__main__.py
@@ -0,0 +1,39 @@
+#!/usr/bin/python3
+
+
+# Python imports
+import argparse
+from setproctitle import setproctitle
+
+import tracemalloc
+tracemalloc.start()
+
+
+# Gtk imports
+import gi, faulthandler, traceback
+gi.require_version('Gtk', '3.0')
+from gi.repository import Gtk
+
+# Application imports
+from __init__ import Main
+
+
+if __name__ == "__main__":
+ try:
+ setproctitle('solarfm')
+ faulthandler.enable() # For better debug info
+ parser = argparse.ArgumentParser()
+ # Add long and short arguments
+ parser.add_argument("--new-tab", "-t", default="", help="Open a file into new tab.")
+ parser.add_argument("--new-window", "-w", default="", help="Open a file into a new window.")
+
+ # Read arguments (If any...)
+ args, unknownargs = parser.parse_known_args()
+
+ Main(args, unknownargs)
+ Gtk.main()
+ except Exception as e:
+ print(repr(e))
+ event_system.keep_ipc_alive = False
+ if debug:
+ traceback.print_exc()
diff --git a/src/versions/solarfm-0.0.1/SolarFM/new/solarfm/resources/Main_Window.glade b/src/versions/solarfm-0.0.1/SolarFM/new/solarfm/resources/Main_Window.glade
new file mode 100644
index 0000000..a146017
--- /dev/null
+++ b/src/versions/solarfm-0.0.1/SolarFM/new/solarfm/resources/Main_Window.glade
@@ -0,0 +1,1653 @@
+
+
+
+
+
+ False
+ 5
+ center-on-parent
+ solarfm.png
+ dialog
+ center
+ SolarFM
+ 0.0.1
+ Copyright (C) 2021 GPL2
+ by ITDominator
+ https://code.itdominator.com/itdominator/SolarFM
+ SolarFM - Copyright (C) 2021 ITDominator GPL2
+
+
+ GNU GENERAL PUBLIC LICENSE
+ Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users. This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it. (Some other Free Software Foundation software is covered by
+the GNU Lesser General Public License instead.) You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+ To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have. You must make sure that they, too, receive or can get the
+source code. And you must show them these terms so they know their
+rights.
+
+ We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+ Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software. If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+ Finally, any free program is threatened constantly by software
+patents. We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary. To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ GNU GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License. The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language. (Hereinafter, translation is included without limitation in
+the term "modification".) Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+ 1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+ 2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+ a) You must cause the modified files to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ b) You must cause any work that you distribute or publish, that in
+ whole or in part contains or is derived from the Program or any
+ part thereof, to be licensed as a whole at no charge to all third
+ parties under the terms of this License.
+
+ c) If the modified program normally reads commands interactively
+ when run, you must cause it, when started running for such
+ interactive use in the most ordinary way, to print or display an
+ announcement including an appropriate copyright notice and a
+ notice that there is no warranty (or else, saying that you provide
+ a warranty) and that users may redistribute the program under
+ these conditions, and telling the user how to view a copy of this
+ License. (Exception: if the Program itself is interactive but
+ does not normally print such an announcement, your work based on
+ the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works. But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+ 3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+ a) Accompany it with the complete corresponding machine-readable
+ source code, which must be distributed under the terms of Sections
+ 1 and 2 above on a medium customarily used for software interchange; or,
+
+ b) Accompany it with a written offer, valid for at least three
+ years, to give any third party, for a charge no more than your
+ cost of physically performing source distribution, a complete
+ machine-readable copy of the corresponding source code, to be
+ distributed under the terms of Sections 1 and 2 above on a medium
+ customarily used for software interchange; or,
+
+ c) Accompany it with the information you received as to the offer
+ to distribute corresponding source code. (This alternative is
+ allowed only for noncommercial distribution and only if you
+ received the program in object code or executable form with such
+ an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it. For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable. However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+ 4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License. Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+ 5. You are not required to accept this License, since you have not
+signed it. However, nothing else grants you permission to modify or
+distribute the Program or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+ 6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions. You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+ 7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all. For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices. Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+ 8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded. In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+ 9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation. If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+ 10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission. For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this. Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+ NO WARRANTY
+
+ 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+ 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+ <one line to give the program's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ 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 Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+ Gnomovision version 69, Copyright (C) year name of author
+ Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary. Here is a sample; alter the names:
+
+ Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+ `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+ <signature of Ty Coon>, 1 April 1989
+ Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs. If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library. If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.
+
+ Lead Developer:
+ ITDominator <1itdominator@gmail.com>
+
+
+SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspection.
+ translator-credits
+ solarfm-64x64.png
+ True
+ custom
+
+
+ False
+
+
+ False
+
+
+ False
+ False
+ 0
+
+
+
+
+
+
+
+ True
+ False
+ gtk-new
+
+
+
+ True
+ False
+ gtk-justify-center
+
+
+
+ True
+ False
+ gtk-open
+
+
+ True
+ False
+ gtk-edit
+ 3
+
+
+ True
+ False
+ gtk-edit
+
+
+ True
+ False
+ gtk-media-forward
+
+
+
+ True
+ False
+ gtk-apply
+ 3
+
+
+ True
+ False
+ gtk-apply
+ 3
+
+
+ True
+ False
+ gtk-apply
+ 3
+
+
+ True
+ False
+ gtk-apply
+ 3
+
+
+ 800
+ 600
+ False
+ center
+ 1670
+ 830
+ solarfm.png
+ center
+
+
+
+
+ True
+ False
+ vertical
+ top
+
+
+ True
+ False
+
+
+
+ True
+ True
+ 0
+
+
+
+
+ True
+ False
+ 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
+
+
+
+
+
+ True
+ False
+ False
+ False
+ False
+ False
+
+
+ True
+ True
+ 2
+
+
+
+
+ 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
+
+
+
+
+ path_entry
+ 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
+ True
+ 5
+ 5
+ 5
+ 5
+ False
+ True
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ True
+ True
+
+
+
+
+ notebook2
+ True
+ True
+ 5
+ 5
+ 5
+ 5
+ False
+ True
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ True
+ True
+
+
+
+
+ True
+ True
+
+
+
+
+ True
+ True
+ 5
+ True
+ True
+ True
+
+
+ notebook3
+ True
+ True
+ 5
+ 5
+ 5
+ 5
+ False
+ True
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ True
+ True
+
+
+
+
+ notebook4
+ True
+ True
+ 5
+ 5
+ 5
+ False
+ True
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ True
+ True
+
+
+
+
+ True
+ True
+
+
+
+
+ True
+ True
+ 2
+
+
+
+
+ True
+ False
+ 10
+ 10
+ 10
+ 10
+ 6
+ 6
+ 15
+ top
+
+
+ True
+ False
+
+
+ False
+ True
+ 0
+
+
+
+
+ True
+ False
+
+
+ False
+ True
+ 1
+
+
+
+
+ True
+ False
+
+
+ False
+ True
+ 2
+
+
+
+
+ False
+ True
+ 3
+
+
+
+
+
+
+ 320
+ False
+ True
+ controll_box
+ bottom
+
+
+ 600
+ True
+ True
+ True
+ in
+ False
+
+
+ message_view
+ True
+ True
+ True
+ False
+ False
+ message_buffer
+
+
+
+
+
+
+ True
+ False
+ user-trash
+
+
+
diff --git a/src/versions/solarfm-0.0.1/SolarFM/new/solarfm/resources/solarfm-64x64.png b/src/versions/solarfm-0.0.1/SolarFM/new/solarfm/resources/solarfm-64x64.png
new file mode 100644
index 0000000..1a403ae
Binary files /dev/null and b/src/versions/solarfm-0.0.1/SolarFM/new/solarfm/resources/solarfm-64x64.png differ
diff --git a/src/versions/solarfm-0.0.1/SolarFM/new/solarfm/resources/solarfm.png b/src/versions/solarfm-0.0.1/SolarFM/new/solarfm/resources/solarfm.png
new file mode 100644
index 0000000..83974e1
Binary files /dev/null and b/src/versions/solarfm-0.0.1/SolarFM/new/solarfm/resources/solarfm.png differ
diff --git a/src/versions/solarfm-0.0.1/SolarFM/new/solarfm/resources/stylesheet.css b/src/versions/solarfm-0.0.1/SolarFM/new/solarfm/resources/stylesheet.css
new file mode 100644
index 0000000..44b70dc
--- /dev/null
+++ b/src/versions/solarfm-0.0.1/SolarFM/new/solarfm/resources/stylesheet.css
@@ -0,0 +1,67 @@
+/* Set fm to have transparent window */
+box,
+iconview,
+notebook,
+paned,
+stack,
+scrolledwindow,
+treeview.view,
+.content-view,
+.view {
+ background: rgba(19, 21, 25, 0.14);
+ color: rgba(255, 255, 255, 1);
+}
+
+notebook > header > tabs > tab:checked {
+ /* Neon Blue 00e8ff */
+ background-color: rgba(0, 232, 255, 0.25);
+ /* Dark Bergundy */
+ /* background-color: rgba(116, 0, 0, 0.25); */
+
+ color: rgba(255, 255, 255, 0.5);
+}
+
+#message_view {
+ font: 16px "Monospace";
+}
+
+.notebook-selected-focus {
+ /* Neon Blue 00e8ff border */
+ border: 2px solid rgba(0, 232, 255, 0.25);
+ /* Dark Bergundy */
+ /* border: 2px solid rgba(116, 0, 0, 0.64); */
+}
+
+
+.view:selected,
+.view:selected:hover {
+ box-shadow: inset 0 0 0 9999px rgba(21, 158, 167, 0.57);
+ color: rgba(255, 255, 255, 0.5);;
+}
+
+
+/* * {
+ background: rgba(0, 0, 0, 0.14);
+ color: rgba(255, 255, 255, 1);
+} */
+
+/* * selection {
+ background-color: rgba(116, 0, 0, 0.65);
+ 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/solarfm-0.0.1/SolarFM/new/solarfm/shellfm/__init__.py b/src/versions/solarfm-0.0.1/SolarFM/new/solarfm/shellfm/__init__.py
new file mode 100644
index 0000000..0c8b591
--- /dev/null
+++ b/src/versions/solarfm-0.0.1/SolarFM/new/solarfm/shellfm/__init__.py
@@ -0,0 +1 @@
+from .windows import WindowController
diff --git a/src/versions/solarfm-0.0.1/SolarFM/new/solarfm/shellfm/windows/Window.py b/src/versions/solarfm-0.0.1/SolarFM/new/solarfm/shellfm/windows/Window.py
new file mode 100644
index 0000000..78c5241
--- /dev/null
+++ b/src/versions/solarfm-0.0.1/SolarFM/new/solarfm/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/solarfm-0.0.1/SolarFM/new/solarfm/shellfm/windows/WindowController.py b/src/versions/solarfm-0.0.1/SolarFM/new/solarfm/shellfm/windows/WindowController.py
new file mode 100644
index 0000000..0cd271e
--- /dev/null
+++ b/src/versions/solarfm-0.0.1/SolarFM/new/solarfm/shellfm/windows/WindowController.py
@@ -0,0 +1,179 @@
+# Python imports
+import threading, subprocess, time, json
+from os import path
+
+# Lib imports
+
+# Application imports
+from . import Window
+
+
+def threaded(fn):
+ def wrapper(*args, **kwargs):
+ threading.Thread(target=fn, args=args, kwargs=kwargs).start()
+ return wrapper
+
+
+class WindowController:
+ def __init__(self):
+ USER_HOME = path.expanduser('~')
+ CONFIG_PATH = USER_HOME + "/.config/solarfm"
+ self.session_file = CONFIG_PATH + "/session.json"
+
+ self._event_sleep_time = 1
+ self.active_window_id = ""
+ self.active_tab_id = ""
+ self.windows = []
+ self.fm_event_observer()
+
+ @threaded
+ def fm_event_observer(self):
+ while event_system.monitor_events:
+ time.sleep(event_sleep_time)
+ event = event_system.consume_fm_event()
+ if event:
+ print(event)
+
+ 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/solarfm-0.0.1/SolarFM/new/solarfm/shellfm/windows/__init__.py b/src/versions/solarfm-0.0.1/SolarFM/new/solarfm/shellfm/windows/__init__.py
new file mode 100644
index 0000000..cd9f6ce
--- /dev/null
+++ b/src/versions/solarfm-0.0.1/SolarFM/new/solarfm/shellfm/windows/__init__.py
@@ -0,0 +1,2 @@
+from .Window import Window
+from .WindowController import WindowController
diff --git a/src/versions/solarfm-0.0.1/SolarFM/new/solarfm/shellfm/windows/view/Path.py b/src/versions/solarfm-0.0.1/SolarFM/new/solarfm/shellfm/windows/view/Path.py
new file mode 100644
index 0000000..91787b0
--- /dev/null
+++ b/src/versions/solarfm-0.0.1/SolarFM/new/solarfm/shellfm/windows/view/Path.py
@@ -0,0 +1,59 @@
+# 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):
+ if path == self.get_path():
+ return
+
+ if os.path.isdir(path):
+ self.path = list( filter(None, path.replace("\\", "/").split('/')) )
+ self.load_directory()
+ return True
+
+ return False
+
+ def set_path_with_sub_path(self, sub_path):
+ path = os.path.join(self.get_home(), sub_path)
+ if path == self.get_path():
+ return False
+
+ if os.path.isdir(path):
+ self.path = list( filter(None, path.replace("\\", "/").split('/')) )
+ self.load_directory()
+ return True
+
+ return False
+
+ 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/solarfm-0.0.1/SolarFM/new/solarfm/shellfm/windows/view/View.py b/src/versions/solarfm-0.0.1/SolarFM/new/solarfm/shellfm/windows/view/View.py
new file mode 100644
index 0000000..a4f1d73
--- /dev/null
+++ b/src/versions/solarfm-0.0.1/SolarFM/new/solarfm/shellfm/windows/view/View.py
@@ -0,0 +1,227 @@
+# 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, FileHandler
+from .icons import Icon
+from . import Path
+
+
+class View(Settings, FileHandler, Launcher, Icon, Path):
+ def __init__(self):
+ self. logger = None
+ self.id_length = 10
+
+ self.id = ""
+ self.wid = None
+ self.dir_watcher = None
+ self.hide_hidden = self.HIDE_HIDDEN_FILES
+ self.files = []
+ self.dirs = []
+ self.vids = []
+ self.images = []
+ self.desktop = []
+ self.ungrouped = []
+ self.hidden = []
+
+ 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 set_wid(self, _wid):
+ self.wid = _wid
+
+ def get_wid(self):
+ return self.wid
+
+ def set_dir_watcher(self, watcher):
+ self.dir_watcher = watcher
+
+ def get_dir_watcher(self):
+ return self.dir_watcher
+
+ def load_directory(self):
+ path = self.get_path()
+ self.dirs = []
+ self.vids = []
+ self.images = []
+ self.desktop = []
+ self.ungrouped = []
+ self.hidden = []
+ self.files = []
+
+ if not isdir(path):
+ self.set_to_home()
+ return ""
+
+ for f in listdir(path):
+ file = join(path, f)
+ if self.hide_hidden:
+ if f.startswith('.'):
+ self.hidden.append(f)
+ 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_not_hidden_count(self):
+ return len(self.files) + \
+ len(self.dirs) + \
+ len(self.vids) + \
+ len(self.images) + \
+ len(self.desktop) + \
+ len(self.ungrouped)
+
+ def get_hidden_count(self):
+ return len(self.hidden)
+
+ def get_files_count(self):
+ return len(self.files)
+
+ 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/solarfm-0.0.1/SolarFM/new/solarfm/shellfm/windows/view/__init__.py b/src/versions/solarfm-0.0.1/SolarFM/new/solarfm/shellfm/windows/view/__init__.py
new file mode 100644
index 0000000..07d9ad7
--- /dev/null
+++ b/src/versions/solarfm-0.0.1/SolarFM/new/solarfm/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/solarfm-0.0.1/SolarFM/new/solarfm/shellfm/windows/view/icons/Icon.py b/src/versions/solarfm-0.0.1/SolarFM/new/solarfm/shellfm/windows/view/icons/Icon.py
new file mode 100644
index 0000000..f551ee6
--- /dev/null
+++ b/src/versions/solarfm-0.0.1/SolarFM/new/solarfm/shellfm/windows/view/icons/Icon.py
@@ -0,0 +1,76 @@
+# Python Imports
+import os, subprocess, threading, hashlib
+from os.path import isfile
+
+# Gtk imports
+from gi.repository import GdkPixbuf
+
+# 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)
+
+ return thumbnl
+ except Exception as e:
+ return None
+
+ 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 = GdkPixbuf.Pixbuf.new_from_file(self.DEFAULT_ICONS + "/video.png")
+
+ return thumbnl
+ except Exception as e:
+ print("Thumbnail generation issue:")
+ print( repr(e) )
+ return GdkPixbuf.Pixbuf.new_from_file(self.DEFAULT_ICONS + "/video.png")
+
+
+ def create_scaled_image(self, path, wxh):
+ try:
+ pixbuf = GdkPixbuf.Pixbuf.new_from_file(path)
+ scaled_pixbuf = pixbuf.scale_simple(wxh[0], wxh[1], 2) # 2 = BILINEAR and is best by default
+ return scaled_pixbuf
+ except Exception as e:
+ print("Image Scaling Issue:")
+ print( repr(e) )
+ return None
+
+ def create_from_file(self, path):
+ try:
+ return GdkPixbuf.Pixbuf.new_from_file(path)
+ except Exception as e:
+ print("Image from file Issue:")
+ print( repr(e) )
+ return None
+
+ def return_generic_icon(self):
+ return GdkPixbuf.Pixbuf.new_from_file(self.DEFAULT_ICON)
diff --git a/src/versions/solarfm-0.0.1/SolarFM/new/solarfm/shellfm/windows/view/icons/__init__.py b/src/versions/solarfm-0.0.1/SolarFM/new/solarfm/shellfm/windows/view/icons/__init__.py
new file mode 100644
index 0000000..b946d44
--- /dev/null
+++ b/src/versions/solarfm-0.0.1/SolarFM/new/solarfm/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/solarfm-0.0.1/SolarFM/new/solarfm/shellfm/windows/view/icons/mixins/DesktopIconMixin.py b/src/versions/solarfm-0.0.1/SolarFM/new/solarfm/shellfm/windows/view/icons/mixins/DesktopIconMixin.py
new file mode 100644
index 0000000..2d3c30b
--- /dev/null
+++ b/src/versions/solarfm-0.0.1/SolarFM/new/solarfm/shellfm/windows/view/icons/mixins/DesktopIconMixin.py
@@ -0,0 +1,65 @@
+# 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 Gtk
+
+# Application imports
+from .xdg.DesktopEntry import DesktopEntry
+
+
+class DesktopIconMixin:
+ 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/solarfm-0.0.1/SolarFM/new/solarfm/shellfm/windows/view/icons/mixins/VideoIconMixin.py b/src/versions/solarfm-0.0.1/SolarFM/new/solarfm/shellfm/windows/view/icons/mixins/VideoIconMixin.py
new file mode 100644
index 0000000..fc35e9d
--- /dev/null
+++ b/src/versions/solarfm-0.0.1/SolarFM/new/solarfm/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/solarfm-0.0.1/SolarFM/new/solarfm/shellfm/windows/view/icons/mixins/__init__.py b/src/versions/solarfm-0.0.1/SolarFM/new/solarfm/shellfm/windows/view/icons/mixins/__init__.py
new file mode 100644
index 0000000..54bfe39
--- /dev/null
+++ b/src/versions/solarfm-0.0.1/SolarFM/new/solarfm/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/solarfm-0.0.1/SolarFM/new/solarfm/shellfm/windows/view/icons/mixins/xdg/BaseDirectory.py b/src/versions/solarfm-0.0.1/SolarFM/new/solarfm/shellfm/windows/view/icons/mixins/xdg/BaseDirectory.py
new file mode 100644
index 0000000..a7c31b1
--- /dev/null
+++ b/src/versions/solarfm-0.0.1/SolarFM/new/solarfm/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/solarfm-0.0.1/SolarFM/new/solarfm/shellfm/windows/view/icons/mixins/xdg/Config.py b/src/versions/solarfm-0.0.1/SolarFM/new/solarfm/shellfm/windows/view/icons/mixins/xdg/Config.py
new file mode 100644
index 0000000..3f5d654
--- /dev/null
+++ b/src/versions/solarfm-0.0.1/SolarFM/new/solarfm/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/solarfm-0.0.1/SolarFM/new/solarfm/shellfm/windows/view/icons/mixins/xdg/DesktopEntry.py b/src/versions/solarfm-0.0.1/SolarFM/new/solarfm/shellfm/windows/view/icons/mixins/xdg/DesktopEntry.py
new file mode 100644
index 0000000..803993e
--- /dev/null
+++ b/src/versions/solarfm-0.0.1/SolarFM/new/solarfm/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/solarfm-0.0.1/SolarFM/new/solarfm/shellfm/windows/view/icons/mixins/xdg/Exceptions.py b/src/versions/solarfm-0.0.1/SolarFM/new/solarfm/shellfm/windows/view/icons/mixins/xdg/Exceptions.py
new file mode 100644
index 0000000..7096b61
--- /dev/null
+++ b/src/versions/solarfm-0.0.1/SolarFM/new/solarfm/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/solarfm-0.0.1/SolarFM/new/solarfm/shellfm/windows/view/icons/mixins/xdg/IconTheme.py b/src/versions/solarfm-0.0.1/SolarFM/new/solarfm/shellfm/windows/view/icons/mixins/xdg/IconTheme.py
new file mode 100644
index 0000000..2ff3c05
--- /dev/null
+++ b/src/versions/solarfm-0.0.1/SolarFM/new/solarfm/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/solarfm-0.0.1/SolarFM/new/solarfm/shellfm/windows/view/icons/mixins/xdg/IniFile.py b/src/versions/solarfm-0.0.1/SolarFM/new/solarfm/shellfm/windows/view/icons/mixins/xdg/IniFile.py
new file mode 100644
index 0000000..74ab858
--- /dev/null
+++ b/src/versions/solarfm-0.0.1/SolarFM/new/solarfm/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/solarfm-0.0.1/SolarFM/new/solarfm/shellfm/windows/view/icons/mixins/xdg/Locale.py b/src/versions/solarfm-0.0.1/SolarFM/new/solarfm/shellfm/windows/view/icons/mixins/xdg/Locale.py
new file mode 100644
index 0000000..d0a70d2
--- /dev/null
+++ b/src/versions/solarfm-0.0.1/SolarFM/new/solarfm/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/solarfm-0.0.1/SolarFM/new/solarfm/shellfm/windows/view/icons/mixins/xdg/Menu.py b/src/versions/solarfm-0.0.1/SolarFM/new/solarfm/shellfm/windows/view/icons/mixins/xdg/Menu.py
new file mode 100644
index 0000000..fcf1ac1
--- /dev/null
+++ b/src/versions/solarfm-0.0.1/SolarFM/new/solarfm/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/solarfm-0.0.1/SolarFM/new/solarfm/shellfm/windows/view/icons/mixins/xdg/MenuEditor.py b/src/versions/solarfm-0.0.1/SolarFM/new/solarfm/shellfm/windows/view/icons/mixins/xdg/MenuEditor.py
new file mode 100644
index 0000000..2c68515
--- /dev/null
+++ b/src/versions/solarfm-0.0.1/SolarFM/new/solarfm/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/solarfm-0.0.1/SolarFM/new/solarfm/shellfm/windows/view/icons/mixins/xdg/Mime.py b/src/versions/solarfm-0.0.1/SolarFM/new/solarfm/shellfm/windows/view/icons/mixins/xdg/Mime.py
new file mode 100644
index 0000000..60c4efd
--- /dev/null
+++ b/src/versions/solarfm-0.0.1/SolarFM/new/solarfm/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/solarfm-0.0.1/SolarFM/new/solarfm/shellfm/windows/view/icons/mixins/xdg/RecentFiles.py b/src/versions/solarfm-0.0.1/SolarFM/new/solarfm/shellfm/windows/view/icons/mixins/xdg/RecentFiles.py
new file mode 100644
index 0000000..fbe608c
--- /dev/null
+++ b/src/versions/solarfm-0.0.1/SolarFM/new/solarfm/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/solarfm-0.0.1/SolarFM/new/solarfm/shellfm/windows/view/icons/mixins/xdg/__init__.py b/src/versions/solarfm-0.0.1/SolarFM/new/solarfm/shellfm/windows/view/icons/mixins/xdg/__init__.py
new file mode 100644
index 0000000..b5a117e
--- /dev/null
+++ b/src/versions/solarfm-0.0.1/SolarFM/new/solarfm/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/solarfm-0.0.1/SolarFM/new/solarfm/shellfm/windows/view/icons/mixins/xdg/util.py b/src/versions/solarfm-0.0.1/SolarFM/new/solarfm/shellfm/windows/view/icons/mixins/xdg/util.py
new file mode 100644
index 0000000..1637aa5
--- /dev/null
+++ b/src/versions/solarfm-0.0.1/SolarFM/new/solarfm/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/solarfm-0.0.1/SolarFM/new/solarfm/shellfm/windows/view/utils/FileHandler.py b/src/versions/solarfm-0.0.1/SolarFM/new/solarfm/shellfm/windows/view/utils/FileHandler.py
new file mode 100644
index 0000000..57c69e3
--- /dev/null
+++ b/src/versions/solarfm-0.0.1/SolarFM/new/solarfm/shellfm/windows/view/utils/FileHandler.py
@@ -0,0 +1,80 @@
+
+import os, shutil, subprocess, threading
+
+
+class FileHandler:
+ def create_file(self, nFile, type):
+ try:
+ if TYPE == "dir":
+ os.mkdir(nFile)
+ elif TYPE == "file":
+ open(nFile, 'a').close()
+ except Exception as e:
+ print("An error occured creating the file/dir:")
+ print(repr(e))
+ return False
+
+ return True
+
+ def update_file(self, oFile, nFile):
+ try:
+ print(f"Renaming: {oFile} --> {nFile}")
+ os.rename(oFile, nFile)
+ except Exception as e:
+ print("An error occured renaming the file:")
+ print(repr(e))
+ return False
+
+ return True
+
+ def delete_file(self, toDeleteFile):
+ try:
+ print(f"Deleting: {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 False
+ else:
+ print("The folder/file does not exist")
+ return False
+ except Exception as e:
+ print("An error occured deleting the file:")
+ print(repr(e))
+ return False
+
+ return True
+
+ def move_file(self, fFile, tFile):
+ try:
+ print(f"Moving: {fFile} --> {tFile}")
+ if os.path.exists(fFile) and os.path.exists(tFile):
+ if not tFile.endswith("/"):
+ tFile += "/"
+
+ shutil.move(fFile, tFile)
+ else:
+ print("The folder/file does not exist")
+ return False
+ except Exception as e:
+ print("An error occured moving the file:")
+ print(repr(e))
+ return False
+
+ return True
+
+ def copy_file(self,fFile, tFile, symlinks=False, ignore=None):
+ try:
+ if os.path.isdir(fFile):
+ shutil.copytree(fFile, tFile, symlinks, ignore)
+ else:
+ shutil.copy2(fFile, tFile)
+ except Exception as e:
+ print("An error occured copying the file:")
+ print(repr(e))
+ return False
+
+ return True
diff --git a/src/versions/solarfm-0.0.1/SolarFM/new/solarfm/shellfm/windows/view/utils/Launcher.py b/src/versions/solarfm-0.0.1/SolarFM/new/solarfm/shellfm/windows/view/utils/Launcher.py
new file mode 100644
index 0000000..92a690e
--- /dev/null
+++ b/src/versions/solarfm-0.0.1/SolarFM/new/solarfm/shellfm/windows/view/utils/Launcher.py
@@ -0,0 +1,95 @@
+# 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]
+ elif lowerName.endswith("placeholder-until-i-can-get-a-use-pref-fm-flag"):
+ command = [self.file_manager_app, file]
+ else:
+ command = ["xdg-open", 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/solarfm-0.0.1/SolarFM/new/solarfm/shellfm/windows/view/utils/Settings.py b/src/versions/solarfm-0.0.1/SolarFM/new/solarfm/shellfm/windows/view/utils/Settings.py
new file mode 100644
index 0000000..408bc42
--- /dev/null
+++ b/src/versions/solarfm-0.0.1/SolarFM/new/solarfm/shellfm/windows/view/utils/Settings.py
@@ -0,0 +1,94 @@
+# 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/solarfm"
+ 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
+ FFMPG_THUMBNLR = FFMPG_THUMBNLR if settings["thumbnailer_path"] == "" else settings["thumbnailer_path"]
+ 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')
+
+
+ # Dir 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/solarfm-0.0.1/SolarFM/new/solarfm/shellfm/windows/view/utils/__init__.py b/src/versions/solarfm-0.0.1/SolarFM/new/solarfm/shellfm/windows/view/utils/__init__.py
new file mode 100644
index 0000000..3efd664
--- /dev/null
+++ b/src/versions/solarfm-0.0.1/SolarFM/new/solarfm/shellfm/windows/view/utils/__init__.py
@@ -0,0 +1,3 @@
+from .Settings import Settings
+from .Launcher import Launcher
+from .FileHandler import FileHandler
diff --git a/src/versions/solarfm-0.0.1/SolarFM/new/solarfm/signal_classes/Controller.py b/src/versions/solarfm-0.0.1/SolarFM/new/solarfm/signal_classes/Controller.py
new file mode 100644
index 0000000..acf2edc
--- /dev/null
+++ b/src/versions/solarfm-0.0.1/SolarFM/new/solarfm/signal_classes/Controller.py
@@ -0,0 +1,150 @@
+# Python imports
+import sys, traceback, threading, subprocess, signal, inspect, os, time
+
+# Gtk imports
+import gi
+gi.require_version('Gtk', '3.0')
+from gi.repository import Gtk
+from gi.repository import GLib
+
+# Application imports
+from .mixins import *
+from . import ShowHideMixin, KeyboardSignalsMixin, Controller_Data
+
+
+def threaded(fn):
+ def wrapper(*args, **kwargs):
+ threading.Thread(target=fn, args=args, kwargs=kwargs).start()
+ return wrapper
+
+
+class Controller(Controller_Data, ShowHideMixin, KeyboardSignalsMixin, WidgetFileActionMixin, \
+ PaneMixin, WindowMixin):
+ def __init__(self, args, unknownargs, _settings):
+ sys.excepthook = self.my_except_hook
+ self.settings = _settings
+ self.setup_controller_data()
+
+ self.window.show()
+ self.generate_windows(self.state)
+
+ self.window.connect("delete-event", self.tear_down)
+ GLib.unix_signal_add(GLib.PRIORITY_DEFAULT, signal.SIGINT, self.tear_down)
+ self.gui_event_observer()
+
+ if unknownargs:
+ for arg in unknownargs:
+ if os.path.isdir(arg):
+ message = f"FILE|{arg}"
+ event_system.send_ipc_message(message)
+
+ if args.new_tab and os.path.isdir(args.new_tab):
+ message = f"FILE|{args.new_tab}"
+ event_system.send_ipc_message(message)
+
+
+ def tear_down(self, widget=None, eve=None):
+ event_system.monitor_events = False
+ event_system.send_ipc_message("close server")
+ self.window_controller.save_state()
+ time.sleep(event_sleep_time)
+ Gtk.main_quit()
+
+ @threaded
+ def gui_event_observer(self):
+ while event_system.monitor_events:
+ time.sleep(event_sleep_time)
+ event = event_system.consume_gui_event()
+ if event:
+ try:
+ type, target, data = event
+ method = getattr(self.__class__, type)
+ GLib.idle_add(method, (self, data,))
+ except Exception as e:
+ print(repr(e))
+
+
+ def my_except_hook(self, exctype, value, _traceback):
+ trace = ''.join(traceback.format_tb(_traceback))
+ data = f"Exectype: {exctype} <--> Value: {value}\n\n{trace}\n\n\n\n"
+ start_itr = self.message_buffer.get_start_iter()
+ self.message_buffer.place_cursor(start_itr)
+ self.display_message(self.error, data)
+
+ def display_message(self, type, text, seconds=None):
+ self.message_buffer.insert_at_cursor(text)
+ self.message_widget.popup()
+ if seconds:
+ self.hide_message_timeout(seconds)
+
+ @threaded
+ def hide_message_timeout(self, seconds=3):
+ time.sleep(seconds)
+ GLib.idle_add(self.message_widget.popdown)
+
+
+
+ def do_edit_files(self, widget=None, eve=None):
+ self.to_rename_files = self.selected_files
+ self.rename_files()
+
+ def execute(self, option, start_dir=os.getenv("HOME")):
+ DEVNULL = open(os.devnull, 'w')
+ command = option.split()
+ subprocess.Popen(command, cwd=start_dir, start_new_session=True, stdout=DEVNULL, stderr=DEVNULL)
+
+
+ def do_action_from_menu_controls(self, imagemenuitem, eventbutton):
+ action = imagemenuitem.get_name()
+ self.ctrlDown = True
+ self.hide_context_menu()
+ self.hide_new_file_menu()
+ self.hide_edit_file_menu()
+
+ if action == "create":
+ self.create_file()
+ self.hide_new_file_menu()
+ if action == "open":
+ self.open_files()
+ if action == "rename":
+ self.to_rename_files = self.selected_files
+ self.rename_files()
+ if action == "cut":
+ self.to_copy_files.clear()
+ self.cut_files()
+ if action == "copy":
+ self.to_cut_files.clear()
+ self.copy_files()
+ if action == "paste":
+ self.paste_files()
+ if action == "delete":
+ # self.delete_files()
+ self.trash_files()
+ if action == "trash":
+ self.trash_files()
+
+ self.ctrlDown = False
+
+
+
+
+ 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()
+ object.set_active(True)
+
+ for view in views:
+ self.create_new_view_notebook(None, i, view)
+
+ 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, i, None)
diff --git a/src/versions/solarfm-0.0.1/SolarFM/new/solarfm/signal_classes/Controller_Data.py b/src/versions/solarfm-0.0.1/SolarFM/new/solarfm/signal_classes/Controller_Data.py
new file mode 100644
index 0000000..8220192
--- /dev/null
+++ b/src/versions/solarfm-0.0.1/SolarFM/new/solarfm/signal_classes/Controller_Data.py
@@ -0,0 +1,53 @@
+# Python imports
+
+# Gtk imports
+
+# Application imports
+from shellfm import WindowController
+
+
+class Controller_Data:
+ def has_method(self, o, name):
+ return callable(getattr(o, name, None))
+
+ def setup_controller_data(self):
+ self.window_controller = WindowController()
+ self.state = self.window_controller.load_state()
+
+ self.builder = self.settings.builder
+ self.logger = self.settings.logger
+
+ self.window = self.settings.getMainWindow()
+ self.window1 = self.builder.get_object("window_1")
+ self.window2 = self.builder.get_object("window_2")
+ self.window3 = self.builder.get_object("window_3")
+ self.window4 = self.builder.get_object("window_4")
+ self.message_widget = self.builder.get_object("message_widget")
+ self.message_view = self.builder.get_object("message_view")
+ self.message_buffer = self.builder.get_object("message_buffer")
+
+ self.bottom_size_label = self.builder.get_object("bottom_size_label")
+ self.bottom_file_count_label = self.builder.get_object("bottom_file_count_label")
+ self.bottom_path_label = self.builder.get_object("bottom_path_label")
+
+ self.notebooks = [self.window1, self.window2, self.window3, self.window4]
+ self.selected_files = []
+ self.to_rename_files = []
+ self.to_copy_files = []
+ self.to_cut_files = []
+
+ 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.skip_edit = False
+ self.cancel_edit = False
+ self.ctrlDown = False
+ self.shiftDown = False
+ self.altDown = False
+
+ self.success = "#88cc27"
+ self.warning = "#ffa800"
+ self.error = "#ff0000"
diff --git a/src/versions/solarfm-0.0.1/SolarFM/new/solarfm/signal_classes/DBusControllerMixin.py b/src/versions/solarfm-0.0.1/SolarFM/new/solarfm/signal_classes/DBusControllerMixin.py
new file mode 100644
index 0000000..ce49ee7
--- /dev/null
+++ b/src/versions/solarfm-0.0.1/SolarFM/new/solarfm/signal_classes/DBusControllerMixin.py
@@ -0,0 +1,63 @@
+# Python imports
+import threading, socket, time
+from multiprocessing.connection import Listener, Client
+
+# Gtk imports
+
+# Application imports
+
+
+def threaded(fn):
+ def wrapper(*args, **kwargs):
+ threading.Thread(target=fn, args=args, kwargs=kwargs).start()
+ return wrapper
+
+
+class DBusControllerMixin:
+
+ @threaded
+ def create_ipc_server(self):
+ listener = Listener(('127.0.0.1', 4848), authkey=b'solarfm-ipc')
+ self.is_ipc_alive = True
+ while event_system.keep_ipc_alive:
+ conn = listener.accept()
+ start_time = time.time()
+
+ print(f"New Connection: {listener.last_accepted}")
+ while True:
+ msg = conn.recv()
+ if debug:
+ print(msg)
+
+ if "FILE|" in msg:
+ file = msg.split("FILE|")[1].strip()
+ if file:
+ event_system.push_gui_event(["create_tab_from_ipc", None, file])
+
+ conn.close()
+ break
+
+
+ if msg == 'close connection':
+ conn.close()
+ break
+ if msg == 'close server':
+ conn.close()
+ event_system.keep_ipc_alive = False
+ break
+
+ # NOTE: Not perfect but insures we don't lockup the connection for too long.
+ end_time = time.time()
+ if (end - start) > 15.0:
+ conn.close()
+
+ listener.close()
+
+
+ def send_ipc_message(self, message="Empty Data..."):
+ try:
+ conn = Client(('127.0.0.1', 4848), authkey=b'solar-ipc')
+ conn.send(message)
+ conn.send('close connection')
+ except Exception as e:
+ print(repr(e))
diff --git a/src/versions/solarfm-0.0.1/SolarFM/new/solarfm/signal_classes/KeyboardSignalsMixin.py b/src/versions/solarfm-0.0.1/SolarFM/new/solarfm/signal_classes/KeyboardSignalsMixin.py
new file mode 100644
index 0000000..657753b
--- /dev/null
+++ b/src/versions/solarfm-0.0.1/SolarFM/new/solarfm/signal_classes/KeyboardSignalsMixin.py
@@ -0,0 +1,78 @@
+# Python imports
+
+# Gtk imports
+import gi
+gi.require_version('Gtk', '3.0')
+gi.require_version('Gdk', '3.0')
+from gi.repository import Gtk
+from gi.repository import Gdk
+
+# Application imports
+
+
+class KeyboardSignalsMixin:
+ def global_key_press_controller(self, eve, user_data):
+ keyname = Gdk.keyval_name(user_data.keyval).lower()
+ if "control" in keyname or "alt" in keyname or "shift" in keyname:
+ if "control" in keyname:
+ self.ctrlDown = True
+ if "shift" in keyname:
+ self.shiftDown = True
+ if "alt" in keyname:
+ self.altDown = True
+
+ # NOTE: Yes, this should actually be mapped to some key controller setting
+ # file or something. Sue me.
+ def global_key_release_controller(self, eve, user_data):
+ keyname = Gdk.keyval_name(user_data.keyval).lower()
+ if debug:
+ print(f"global_key_release_controller > key > {keyname}")
+
+ if "control" in keyname or "alt" in keyname or "shift" in keyname:
+ if "control" in keyname:
+ self.ctrlDown = False
+ if "shift" in keyname:
+ self.shiftDown = False
+ if "alt" in keyname:
+ self.altDown = False
+
+ if self.ctrlDown and keyname == "q":
+ self.tear_down()
+ if (self.ctrlDown and keyname == "slash") or keyname == "home":
+ self.builder.get_object("go_home").released()
+ if (self.ctrlDown and keyname == "r") or keyname == "f5":
+ self.builder.get_object("refresh_view").released()
+ if (self.ctrlDown and keyname == "up") or (self.ctrlDown and keyname == "u"):
+ self.builder.get_object("go_up").released()
+ if self.ctrlDown and keyname == "l":
+ self.builder.get_object("path_entry").grab_focus()
+ if self.ctrlDown and keyname == "t":
+ self.builder.get_object("create_tab").released()
+ if self.ctrlDown and keyname == "o":
+ self.open_files()
+ if self.ctrlDown and keyname == "w":
+ self.keyboard_close_tab()
+ if self.ctrlDown and keyname == "h":
+ self.show_hide_hidden_files()
+ if (self.ctrlDown and keyname == "e"):
+ self.edit_files()
+ if self.ctrlDown and keyname == "c":
+ self.to_cut_files.clear()
+ self.copy_files()
+ if self.ctrlDown and keyname == "x":
+ self.to_copy_files.clear()
+ self.cut_files()
+ if self.ctrlDown and keyname == "v":
+ self.paste_files()
+ if self.ctrlDown and keyname == "n":
+ self.show_new_file_menu()
+
+ if keyname == "delete":
+ self.trash_files()
+ if keyname == "f2":
+ self.do_edit_files()
+ if keyname == "f4":
+ wid, tid = self.window_controller.get_active_data()
+ view = self.get_fm_window(wid).get_view_by_id(tid)
+ dir = view.get_current_directory()
+ self.execute("terminator", dir)
diff --git a/src/versions/solarfm-0.0.1/SolarFM/new/solarfm/signal_classes/ShowHideMixin.py b/src/versions/solarfm-0.0.1/SolarFM/new/solarfm/signal_classes/ShowHideMixin.py
new file mode 100644
index 0000000..886aa08
--- /dev/null
+++ b/src/versions/solarfm-0.0.1/SolarFM/new/solarfm/signal_classes/ShowHideMixin.py
@@ -0,0 +1,65 @@
+# Python imports
+
+# Gtk imports
+import gi
+gi.require_version('Gtk', '3.0')
+from gi.repository import Gtk
+
+# Application imports
+
+
+class ShowHideMixin:
+ def show_messages_popup(self, type, text, seconds=None):
+ self.message_widget.popup()
+
+ def show_about_page(self, widget=None, eve=None):
+ about_page = self.builder.get_object("about_page")
+ response = about_page.run()
+ if response == -4:
+ self.hide_about_page()
+
+ def hide_about_page(self, widget=None, eve=None):
+ about_page = self.builder.get_object("about_page").hide()
+
+ def show_appchooser_menu(self, widget=None, eve=None):
+ appchooser_menu = self.builder.get_object("appchooser_menu")
+ appchooser_widget = self.builder.get_object("appchooser_widget")
+
+ resp = appchooser_menu.run()
+ if resp == Gtk.ResponseType.CANCEL:
+ self.hide_appchooser_menu()
+ if resp == Gtk.ResponseType.OK:
+ self.open_with_files(appchooser_widget)
+ self.hide_appchooser_menu()
+
+ def hide_appchooser_menu(self, widget=None, eve=None):
+ self.builder.get_object("appchooser_menu").hide()
+
+ def run_appchooser_launch(self, widget=None, eve=None):
+ self.builder.get_object("appchooser_select_btn").pressed()
+
+ def show_context_menu(self, widget=None, eve=None):
+ self.builder.get_object("context_menu").run()
+
+ def hide_context_menu(self, widget=None, eve=None):
+ self.builder.get_object("context_menu").hide()
+
+ def show_new_file_menu(self, widget=None, eve=None):
+ self.builder.get_object("new_file_menu").run()
+
+ def hide_new_file_menu(self, widget=None, eve=None):
+ self.builder.get_object("new_file_menu").hide()
+
+ def show_edit_file_menu(self, widget=None, eve=None):
+ self.builder.get_object("edit_file_menu").run()
+
+ def hide_edit_file_menu(self, widget=None, eve=None):
+ self.builder.get_object("edit_file_menu").hide()
+
+ def hide_edit_file_menu_skip(self, widget=None, eve=None):
+ self.skip_edit = True
+ self.builder.get_object("edit_file_menu").hide()
+
+ def hide_edit_file_menu_cancel(self, widget=None, eve=None):
+ self.cancel_edit = True
+ self.builder.get_object("edit_file_menu").hide()
diff --git a/src/versions/solarfm-0.0.1/SolarFM/new/solarfm/signal_classes/__init__.py b/src/versions/solarfm-0.0.1/SolarFM/new/solarfm/signal_classes/__init__.py
new file mode 100644
index 0000000..f34b90c
--- /dev/null
+++ b/src/versions/solarfm-0.0.1/SolarFM/new/solarfm/signal_classes/__init__.py
@@ -0,0 +1,9 @@
+"""
+ Gtk Bound Signal Module
+"""
+from .mixins import *
+from .DBusControllerMixin import DBusControllerMixin
+from .KeyboardSignalsMixin import KeyboardSignalsMixin
+from .ShowHideMixin import ShowHideMixin
+from .Controller_Data import Controller_Data
+from .Controller import Controller
diff --git a/src/versions/solarfm-0.0.1/SolarFM/new/solarfm/signal_classes/mixins/PaneMixin.py b/src/versions/solarfm-0.0.1/SolarFM/new/solarfm/signal_classes/mixins/PaneMixin.py
new file mode 100644
index 0000000..0f9c316
--- /dev/null
+++ b/src/versions/solarfm-0.0.1/SolarFM/new/solarfm/signal_classes/mixins/PaneMixin.py
@@ -0,0 +1,59 @@
+
+
+
+# # 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):
+ tggl_button = self.builder.get_object(f"tggl_notebook_{pane_index}")
+ if pane_index == 1:
+ self.is_pane1_hidden = not self.is_pane1_hidden
+ tggl_button.set_active(not self.is_pane1_hidden)
+ return self.is_pane1_hidden
+ elif pane_index == 2:
+ self.is_pane2_hidden = not self.is_pane2_hidden
+ tggl_button.set_active(not self.is_pane2_hidden)
+ return self.is_pane2_hidden
+ elif pane_index == 3:
+ self.is_pane3_hidden = not self.is_pane3_hidden
+ tggl_button.set_active(not self.is_pane3_hidden)
+ return self.is_pane3_hidden
+ elif pane_index == 4:
+ self.is_pane4_hidden = not self.is_pane4_hidden
+ tggl_button.set_active(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/solarfm-0.0.1/SolarFM/new/solarfm/signal_classes/mixins/TabMixin.py b/src/versions/solarfm-0.0.1/SolarFM/new/solarfm/signal_classes/mixins/TabMixin.py
new file mode 100644
index 0000000..c0079ea
--- /dev/null
+++ b/src/versions/solarfm-0.0.1/SolarFM/new/solarfm/signal_classes/mixins/TabMixin.py
@@ -0,0 +1,178 @@
+# Python imports
+
+# Lib imports
+
+# Application imports
+from . import WidgetMixin
+
+
+class TabMixin(WidgetMixin):
+ """docstring for TabMixin"""
+
+ def create_tab_from_ipc(data):
+ self, path = data
+ wid, tid = self.window_controller.get_active_data()
+ notebook = self.builder.get_object(f"window_{wid}")
+ if notebook.is_visible():
+ self.create_tab(wid, path)
+ return
+
+ if not self.is_pane4_hidden:
+ self.create_tab(4, path)
+ elif not self.is_pane3_hidden:
+ self.create_tab(3, path)
+ elif not self.is_pane2_hidden:
+ self.create_tab(2, path)
+ elif not self.is_pane1_hidden:
+ self.create_tab(1, path)
+
+
+ def create_tab(self, wid, path=None):
+ 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
+
+ view.set_wid(wid)
+ if path: view.set_path(path)
+
+ tab = self.create_tab_widget(view)
+ scroll, store = self.create_grid_iconview_widget(view, wid)
+ # scroll, store = self.create_grid_treeview_widget(view, wid)
+ index = notebook.append_page(scroll, tab)
+
+ 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)
+
+ notebook.set_tab_reorderable(scroll, True)
+ self.load_store(view, store)
+ self.set_window_title()
+ self.set_file_watcher(view)
+
+
+
+
+ def close_tab(self, button, eve=None):
+ notebook = button.get_parent().get_parent()
+ tid = self.get_tab_id_from_tab_box(button.get_parent())
+ wid = int(notebook.get_name()[-1])
+ scroll = self.builder.get_object(f"{wid}|{tid}")
+ page = notebook.page_num(scroll)
+ view = self.get_fm_window(wid).get_view_by_id(tid)
+ watcher = view.get_dir_watcher()
+
+ watcher.cancel()
+ self.get_fm_window(wid).delete_view_by_id(tid)
+ notebook.remove_page(page)
+ self.window_controller.save_state()
+ self.set_window_title()
+
+ def on_tab_reorder(self, child, page_num, new_index):
+ wid, tid = page_num.get_name().split("|")
+ window = self.get_fm_window(wid)
+ view = None
+
+ for i, view in enumerate(window.views):
+ if view.id == tid:
+ _view = window.get_view_by_id(tid)
+ watcher = _view.get_dir_watcher()
+ watcher.cancel()
+ window.views.insert(new_index, window.views.pop(i))
+
+ view = window.get_view_by_id(tid)
+ self.set_file_watcher(view)
+ self.window_controller.save_state()
+
+ def on_tab_switch_update(self, notebook, content=None, index=None):
+ self.selected_files.clear()
+ wid, tid = content.get_children()[0].get_name().split("|")
+ self.window_controller.set_active_data(wid, tid)
+ self.set_path_text(wid, tid)
+ self.set_window_title()
+
+ def get_tab_id_from_tab_box(self, tab_box):
+ tid = tab_box.get_children()[2]
+ return tid.get_text()
+
+ def get_tab_label(self, notebook, iconview):
+ return notebook.get_tab_label(iconview.get_parent()).get_children()[0]
+
+ def get_tab_close(self, notebook, iconview):
+ return notebook.get_tab_label(iconview.get_parent()).get_children()[1]
+
+ def get_tab_iconview_from_notebook(self, notebook):
+ return notebook.get_children()[1].get_children()[0]
+
+ def refresh_tab(data=None):
+ self, ids = data
+ wid, tid = ids.split("|")
+ notebook = self.builder.get_object(f"window_{wid}")
+ store, tab_label = self.get_store_and_label_from_notebook(notebook, f"{wid}|{tid}")
+ view = self.get_fm_window(wid).get_view_by_id(tid)
+
+ view.load_directory()
+ self.load_store(view, store)
+
+ 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}")
+ store, tab_label = self.get_store_and_label_from_notebook(notebook, f"{wid}|{tid}")
+ view = self.get_fm_window(wid).get_view_by_id(tid)
+
+ 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)
+ self.window_controller.save_state()
+ return
+ if action == "path_entry":
+ path = widget.get_text()
+ dir = view.get_current_directory() + "/"
+ if path == dir :
+ return
+
+ traversed = view.set_path(path)
+ if not traversed:
+ return
+
+
+ self.load_store(view, store)
+ self.set_path_text(wid, tid)
+
+ char_width = len(view.get_end_of_path())
+ tab_label.set_width_chars(char_width)
+ tab_label.set_label(view.get_end_of_path())
+ self.set_window_title()
+ self.set_file_watcher(view)
+ self.window_controller.save_state()
+
+
+ def keyboard_close_tab(self):
+ wid, tid = self.window_controller.get_active_data()
+ notebook = self.builder.get_object(f"window_{wid}")
+ scroll = self.builder.get_object(f"{wid}|{tid}")
+ page = notebook.page_num(scroll)
+ view = self.get_fm_window(wid).get_view_by_id(tid)
+ watcher = view.get_dir_watcher()
+ watcher.cancel()
+
+ self.get_fm_window(wid).delete_view_by_id(tid)
+ notebook.remove_page(page)
+ self.window_controller.save_state()
+ self.set_window_title()
+
+ # File control events
+ def show_hide_hidden_files(self):
+ wid, tid = self.window_controller.get_active_data()
+ view = self.get_fm_window(wid).get_view_by_id(tid)
+ view.hide_hidden = not view.hide_hidden
+ view.load_directory()
+ self.builder.get_object("refresh_view").released()
diff --git a/src/versions/solarfm-0.0.1/SolarFM/new/solarfm/signal_classes/mixins/WidgetFileActionMixin.py b/src/versions/solarfm-0.0.1/SolarFM/new/solarfm/signal_classes/mixins/WidgetFileActionMixin.py
new file mode 100644
index 0000000..5d9e2f7
--- /dev/null
+++ b/src/versions/solarfm-0.0.1/SolarFM/new/solarfm/signal_classes/mixins/WidgetFileActionMixin.py
@@ -0,0 +1,258 @@
+# Python imports
+import os
+
+# Lib imports
+from gi.repository import GObject, Gio
+
+# Application imports
+
+
+
+class WidgetFileActionMixin:
+ def set_file_watcher(self, view):
+ if view.get_dir_watcher():
+ watcher = view.get_dir_watcher()
+ watcher.cancel()
+ if debug:
+ print(f"Watcher Is Cancelled: {watcher.is_cancelled()}")
+
+ dir_watcher = Gio.File.new_for_path(view.get_current_directory()) \
+ .monitor_directory(Gio.FileMonitorFlags.WATCH_MOVES,
+ Gio.Cancellable()
+ )
+
+ wid = view.get_wid()
+ tid = view.get_tab_id()
+ dir_watcher.connect("changed", self.dir_watch_updates, (f"{wid}|{tid}",))
+ view.set_dir_watcher(dir_watcher)
+
+ def dir_watch_updates(self, file_monitor, file, other_file=None, eve_type=None, data=None):
+ if eve_type == Gio.FileMonitorEvent.CREATED or \
+ eve_type == Gio.FileMonitorEvent.DELETED or \
+ eve_type == Gio.FileMonitorEvent.RENAMED or \
+ eve_type == Gio.FileMonitorEvent.MOVED_IN or \
+ eve_type == Gio.FileMonitorEvent.MOVED_OUT:
+ wid, tid = data[0].split("|")
+ notebook = self.builder.get_object(f"window_{wid}")
+ view = self.get_fm_window(wid).get_view_by_id(tid)
+ iconview = self.builder.get_object(f"{wid}|{tid}|iconview")
+ store = iconview.get_model()
+ _store, tab_label = self.get_store_and_label_from_notebook(notebook, f"{wid}|{tid}")
+
+ view.load_directory()
+ self.load_store(view, store)
+ tab_label.set_label(view.get_end_of_path())
+ self.set_bottom_labels(view)
+
+
+
+
+ def create_file(self):
+ fname_field = self.builder.get_object("context_menu_fname")
+ file_name = fname_field.get_text().strip()
+ type = self.builder.get_object("context_menu_type_toggle").get_state()
+
+ wid, tid = self.window_controller.get_active_data()
+ view = self.get_fm_window(wid).get_view_by_id(tid)
+ target = f"{view.get_current_directory()}"
+
+ if file_name != "":
+ file_name = "file://" + target + "/" + file_name
+ if type == True: # Create File
+ self.handle_file([file_name], "create_file", target)
+ else: # Create Folder
+ self.handle_file([file_name], "create_dir")
+
+ fname_field.set_text("")
+
+ def open_files(self):
+ wid, tid = self.window_controller.get_active_data()
+ view = self.get_fm_window(wid).get_view_by_id(tid)
+ iconview = self.builder.get_object(f"{wid}|{tid}|iconview")
+ store = iconview.get_model()
+ uris = self.format_to_uris(store, wid, tid, self.selected_files, True)
+
+ for file in uris:
+ view.open_file_locally(file)
+
+ def open_with_files(self, appchooser_widget):
+ wid, tid = self.window_controller.get_active_data()
+ view = self.get_fm_window(wid).get_view_by_id(tid)
+ iconview = self.builder.get_object(f"{wid}|{tid}|iconview")
+ store = iconview.get_model()
+ uris = self.format_to_uris(store, wid, tid, self.selected_files)
+
+ f = Gio.File.new_for_uri(uris[0])
+ app_info = appchooser_widget.get_app_info()
+ app_info.launch([f], None)
+
+ def edit_files(self):
+ pass
+
+ def rename_files(self):
+ rename_label = self.builder.get_object("file_to_rename_label")
+ rename_input = self.builder.get_object("new_rename_fname")
+ wid, tid = self.window_controller.get_active_data()
+ view = self.get_fm_window(wid).get_view_by_id(tid)
+ iconview = self.builder.get_object(f"{wid}|{tid}|iconview")
+ store = iconview.get_model()
+ uris = self.format_to_uris(store, wid, tid, self.to_rename_files)
+
+ # The rename button hides the rename dialog box which lets the loop continue.
+ # Weirdly, the show at the end is needed to flow through all the list properly
+ # than auto chosing the first rename entry you do.
+ for uri in uris:
+ entry = uri.split("/")[-1]
+ rename_label.set_label(entry)
+ rename_input.set_text(entry)
+ if self.skip_edit:
+ self.skip_edit = False
+ self.show_edit_file_menu()
+
+ # Yes...this step is required even with the above... =/
+ self.show_edit_file_menu()
+
+ if self.skip_edit:
+ continue
+ if self.cancel_edit:
+ break
+
+ rname_to = rename_input.get_text().strip()
+ target = f"file://{view.get_current_directory()}/{rname_to}"
+ self.handle_file([uri], "edit", target)
+
+ self.show_edit_file_menu()
+
+
+ self.skip_edit = False
+ self.cancel_edit = False
+ self.hide_new_file_menu()
+ self.to_rename_files.clear()
+
+
+
+
+ def cut_files(self):
+ wid, tid = self.window_controller.get_active_data()
+ iconview = self.builder.get_object(f"{wid}|{tid}|iconview")
+ store = iconview.get_model()
+ uris = self.format_to_uris(store, wid, tid, self.selected_files)
+ self.to_cut_files = uris
+
+ def copy_files(self):
+ wid, tid = self.window_controller.get_active_data()
+ iconview = self.builder.get_object(f"{wid}|{tid}|iconview")
+ store = iconview.get_model()
+ uris = self.format_to_uris(store, wid, tid, self.selected_files)
+ self.to_copy_files = uris
+
+ def paste_files(self):
+ wid, tid = self.window_controller.get_active_data()
+ view = self.get_fm_window(wid).get_view_by_id(tid)
+ target = f"{view.get_current_directory()}"
+
+ if len(self.to_copy_files) > 0:
+ self.handle_file(self.to_copy_files, "copy", target)
+ elif len(self.to_cut_files) > 0:
+ self.handle_file(self.to_cut_files, "move", target)
+
+
+
+
+ def move_file(self, view, files, target):
+ self.handle_file([files], "move", target)
+
+ def delete_files(self):
+ wid, tid = self.window_controller.get_active_data()
+ iconview = self.builder.get_object(f"{wid}|{tid}|iconview")
+ view = self.get_fm_window(wid).get_view_by_id(tid)
+ store = iconview.get_model()
+ uris = self.format_to_uris(store, wid, tid, self.selected_files)
+ self.handle_file(uris, "delete")
+
+ def trash_files(self):
+ wid, tid = self.window_controller.get_active_data()
+ iconview = self.builder.get_object(f"{wid}|{tid}|iconview")
+ view = self.get_fm_window(wid).get_view_by_id(tid)
+ store = iconview.get_model()
+ uris = self.format_to_uris(store, wid, tid, self.selected_files)
+ self.handle_file(uris, "trash")
+
+
+
+
+ # NOTE: Gio moves files by generating the target file path with name in it
+ # We can't just give a base target directory and run with it.
+ # Also, the display name is UTF-8 safe and meant for displaying in GUIs
+ def handle_file(self, paths, action, _target_path=None):
+ paths = self.preprocess_paths(paths)
+ target = None
+
+ for path in paths:
+ try:
+ f = Gio.File.new_for_uri(path)
+
+ if action == "create_file":
+ f.create(Gio.FileCreateFlags.NONE, cancellable=None)
+ break
+ if action == "create_dir":
+ f.make_directory(cancellable=None)
+ break
+
+
+ if _target_path:
+ if os.path.isdir(_target_path):
+ info = f.query_info("standard::display-name", 0, cancellable=None)
+ _target = f"file://{_target_path}/{info.get_display_name()}"
+ target = Gio.File.new_for_uri(_target)
+ else:
+ target = Gio.File.new_for_uri(_target_path)
+
+ # See if dragging to same directory then break
+ if action not in ["trash", "delete", "edit"] and \
+ (f.get_parent().get_path() == target.get_parent().get_path()):
+ break
+
+ type = f.query_file_type(flags=Gio.FileQueryInfoFlags.NONE, cancellable=None)
+ if not type == Gio.FileType.DIRECTORY:
+ if action == "delete":
+ f.delete(cancellable=None)
+ if action == "trash":
+ f.trash(cancellable=None)
+ if action == "copy":
+ f.copy(target, flags=Gio.FileCopyFlags.BACKUP, cancellable=None)
+ if action == "move" or action == "edit":
+ f.move(target, flags=Gio.FileCopyFlags.BACKUP, cancellable=None)
+ else:
+ # Yes, life is hopeless and there is no God. Blame Gio for this sinful shitshow. =/
+ wid, tid = self.window_controller.get_active_data()
+ view = self.get_fm_window(wid).get_view_by_id(tid)
+ fPath = f.get_path()
+ tPath = None
+ state = True
+
+ if target:
+ tPath = target.get_path()
+
+
+ if action == "delete":
+ state = view.delete_file(fPath)
+ if action == "trash":
+ f.trash(cancellable=None)
+ if action == "copy":
+ state = view.copy_file(fPath, tPath)
+ if action == "move" or action == "edit":
+ tPath = target.get_parent().get_path()
+ state = view.move_file(fPath, tPath)
+
+ if not state:
+ raise GObject.GError("Failed to perform requested dir/file action!")
+ except GObject.GError as e:
+ raise OSError(e)
+
+ def preprocess_paths(self, paths):
+ if not isinstance(paths, list):
+ paths = [paths]
+ # Convert items such as pathlib paths to strings
+ paths = [path.__fspath__() if hasattr(path, "__fspath__") else path for path in paths]
+ return paths
diff --git a/src/versions/solarfm-0.0.1/SolarFM/new/solarfm/signal_classes/mixins/WidgetMixin.py b/src/versions/solarfm-0.0.1/SolarFM/new/solarfm/signal_classes/mixins/WidgetMixin.py
new file mode 100644
index 0000000..922df41
--- /dev/null
+++ b/src/versions/solarfm-0.0.1/SolarFM/new/solarfm/signal_classes/mixins/WidgetMixin.py
@@ -0,0 +1,213 @@
+# Python imports
+import os, threading, subprocess
+
+# Lib imports
+import gi
+
+gi.require_version("Gtk", "3.0")
+gi.require_version('Gdk', '3.0')
+from gi.repository import Gtk
+from gi.repository import Gdk
+from gi.repository import GLib
+from gi.repository import Gio
+from gi.repository import GdkPixbuf
+
+# Application imports
+
+
+def threaded(fn):
+ def wrapper(*args, **kwargs):
+ threading.Thread(target=fn, args=args, kwargs=kwargs).start()
+ return wrapper
+
+
+
+class WidgetMixin:
+
+ def load_store(self, view, store, save_state=False):
+ store.clear()
+ dir = view.get_current_directory()
+ files = view.get_files()
+
+ icon = GdkPixbuf.Pixbuf.new_from_file(view.DEFAULT_ICON)
+ for i, file in enumerate(files):
+ store.append([icon, file[0]])
+ self.create_icon(i, view, store, dir, file[0])
+
+ # NOTE: Not likely called often from here but it could be useful
+ if save_state:
+ self.window_controller.save_state()
+
+ @threaded
+ def create_icon(self, i, view, store, dir, file):
+ icon = view.create_icon(dir, file)
+ fpath = dir + "/" + file
+ GLib.idle_add(self.update_store, (i, store, icon, view, fpath,))
+
+ def update_store(self, item):
+ i, store, icon, view, fpath = item
+ itr = store.get_iter(i)
+
+ if not icon:
+ icon = self.get_system_thumbnail(fpath, view.SYS_ICON_WH[0])
+ if not icon:
+ if fpath.endswith(".gif"):
+ icon = GdkPixbuf.PixbufAnimation.get_static_image(fpath)
+ else:
+ icon = GdkPixbuf.Pixbuf.new_from_file(view.DEFAULT_ICON)
+
+ store.set_value(itr, 0, icon)
+
+
+ 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 GdkPixbuf.Pixbuf.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 create_tab_widget(self, view):
+ tab = Gtk.Box()
+ label = Gtk.Label()
+ tid = Gtk.Label()
+ close = Gtk.Button()
+ icon = Gtk.Image(stock=Gtk.STOCK_CLOSE)
+
+ label.set_label(f"{view.get_end_of_path()}")
+ label.set_width_chars(len(view.get_end_of_path()))
+ label.set_margin_start(5)
+ label.set_margin_end(15)
+ label.set_xalign(0.0)
+ # label.set_ellipsize(2) #PANGO_ELLIPSIZE_MIDDLE
+ tid.set_label(f"{view.id}")
+
+ close.add(icon)
+ tab.add(label)
+ tab.add(close)
+ tab.add(tid)
+
+ close.connect("released", 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(1)
+ grid.set_selection_mode(3)
+ grid.set_item_width(96)
+ grid.set_item_padding(8)
+ grid.set_margin(12)
+ grid.set_row_spacing(18)
+ grid.set_columns(-1)
+ grid.set_spacing(12)
+ grid.set_column_spacing(18)
+
+ grid.connect("button_release_event", self.grid_icon_single_left_click)
+ grid.connect("item-activated", self.grid_icon_double_left_click)
+ grid.connect("selection-changed", self.grid_set_selected_items)
+ grid.connect("drag-data-get", self.grid_on_drag_set)
+ grid.connect("drag-data-received", self.grid_on_drag_data_received)
+ grid.connect("drag-motion", self.grid_on_drag_motion)
+
+ URI_TARGET_TYPE = 80
+ uri_target = Gtk.TargetEntry.new('text/uri-list', Gtk.TargetFlags(0), URI_TARGET_TYPE)
+ targets = [ uri_target ]
+ action = Gdk.DragAction.COPY
+ grid.enable_model_drag_dest(targets, action)
+ grid.enable_model_drag_source(0, targets, action)
+
+ grid.show_all()
+ scroll.add(grid)
+ grid.set_name(f"{wid}|{view.id}")
+ scroll.set_name(f"{wid}|{view.id}")
+ self.builder.expose_object(f"{wid}|{view.id}|iconview", grid)
+ self.builder.expose_object(f"{wid}|{view.id}", scroll)
+ return scroll, store
+
+ def create_grid_treeview_widget(self, view, wid):
+ scroll = Gtk.ScrolledWindow()
+ grid = Gtk.TreeView()
+ store = Gtk.ListStore(GdkPixbuf.Pixbuf, str)
+ # store = Gtk.TreeStore(GdkPixbuf.Pixbuf, str)
+ column = Gtk.TreeViewColumn("Icons")
+ icon = Gtk.CellRendererPixbuf()
+ name = Gtk.CellRendererText()
+ selec = grid.get_selection()
+
+ grid.set_model(store)
+ selec.set_mode(3)
+ 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(False)
+ column.set_sizing(2)
+ column.set_min_width(120)
+ column.set_max_width(74)
+
+ 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.connect("button_release_event", self.grid_icon_single_left_click)
+ grid.connect("row-activated", self.grid_icon_double_left_click)
+ grid.connect("drag-data-get", self.grid_on_drag_set)
+ grid.connect("drag-data-received", self.grid_on_drag_data_received)
+ grid.connect("drag-motion", self.grid_on_drag_motion)
+
+ URI_TARGET_TYPE = 80
+ uri_target = Gtk.TargetEntry.new('text/uri-list', Gtk.TargetFlags(0), URI_TARGET_TYPE)
+ targets = [ uri_target ]
+ action = Gdk.DragAction.COPY
+ grid.enable_model_drag_dest(targets, action)
+ grid.enable_model_drag_source(0, targets, action)
+
+
+ grid.show_all()
+ scroll.add(grid)
+ grid.set_name(f"{wid}|{view.id}")
+ scroll.set_name(f"{wid}|{view.id}")
+ grid.columns_autosize()
+ self.builder.expose_object(f"{wid}|{view.id}", scroll)
+ return scroll, store
+
+
+ def get_store_and_label_from_notebook(self, notebook, _name):
+ icon_view = None
+ tab_label = None
+ store = None
+
+ for obj in notebook.get_children():
+ icon_view = obj.get_children()[0]
+ name = icon_view.get_name()
+ if name == _name:
+ store = icon_view.get_model()
+ tab_label = notebook.get_tab_label(obj).get_children()[0]
+
+ return store, tab_label
diff --git a/src/versions/solarfm-0.0.1/SolarFM/new/solarfm/signal_classes/mixins/WindowMixin.py b/src/versions/solarfm-0.0.1/SolarFM/new/solarfm/signal_classes/mixins/WindowMixin.py
new file mode 100644
index 0000000..9aab631
--- /dev/null
+++ b/src/versions/solarfm-0.0.1/SolarFM/new/solarfm/signal_classes/mixins/WindowMixin.py
@@ -0,0 +1,159 @@
+# Python imports
+import copy
+from os.path import isdir, isfile
+
+
+# Gtk imports
+import gi
+from gi.repository import Gdk
+
+# Application imports
+from . import TabMixin
+from . import WidgetMixin
+
+
+
+class WindowMixin(TabMixin):
+ """docstring for WindowMixin"""
+ def get_fm_window(self, wid):
+ return self.window_controller.get_window_by_nickname(f"window_{wid}")
+
+ def format_to_uris(self, store, wid, tid, treePaths, use_just_path=False):
+ view = self.get_fm_window(wid).get_view_by_id(tid)
+ dir = view.get_current_directory()
+ uris = []
+
+ for path in treePaths:
+ itr = store.get_iter(path)
+ file = store.get(itr, 1)[0]
+ fpath = ""
+
+ if not use_just_path:
+ fpath = f"file://{dir}/{file}"
+ else:
+ fpath = f"{dir}/{file}"
+
+ uris.append(fpath)
+
+ return uris
+
+
+ def set_bottom_labels(self, view):
+ self.bottom_size_label.set_label("TBD")
+ if view.hide_hidden:
+ if view.get_hidden_count() > 0:
+ self.bottom_file_count_label.set_label(f"{view.get_not_hidden_count()} visible ({view.get_hidden_count()} hidden)")
+ else:
+ self.bottom_file_count_label.set_label(f"{view.get_files_count()} items")
+ else:
+ self.bottom_file_count_label.set_label(f"{view.get_files_count()} items")
+ self.bottom_path_label.set_label(view.get_current_directory())
+
+
+ def set_window_title(self):
+ wid, tid = self.window_controller.get_active_data()
+ notebook = self.builder.get_object(f"window_{wid}")
+ view = self.get_fm_window(wid).get_view_by_id(tid)
+ dir = view.get_current_directory()
+
+ for _notebook in self.notebooks:
+ ctx = _notebook.get_style_context()
+ ctx.remove_class("notebook-selected-focus")
+
+ ctx = notebook.get_style_context()
+ ctx.add_class("notebook-selected-focus")
+
+ self.window.set_title("SolarFM ~ " + dir)
+ self.set_bottom_labels(view)
+
+ 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 grid_set_selected_items(self, iconview):
+ self.selected_files = iconview.get_selected_items()
+
+ def grid_icon_single_left_click(self, iconview, eve):
+ try:
+ wid, tid = iconview.get_name().split("|")
+ self.window_controller.set_active_data(wid, tid)
+ self.set_path_text(wid, tid)
+ self.set_window_title()
+
+ if eve.type == Gdk.EventType.BUTTON_RELEASE and eve.button == 1: # l-click
+ if self.single_click_open: # FIXME: need to find a way to pass the model index
+ self.grid_icon_double_left_click(iconview)
+ elif eve.type == Gdk.EventType.BUTTON_RELEASE and eve.button == 3: # r-click
+ self.show_context_menu()
+
+ except Exception as e:
+ print(repr(e))
+ self.display_message(self.error, f"{repr(e)}")
+
+ def grid_icon_double_left_click(self, iconview, item, data=None):
+ 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(notebook, iconview)
+
+ view = self.get_fm_window(wid).get_view_by_id(tid)
+ model = iconview.get_model()
+
+ fileName = model[item][1]
+ dir = view.get_current_directory()
+ file = dir + "/" + fileName
+ refresh = True
+
+ if 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())
+ self.set_file_watcher(view)
+ self.set_bottom_labels(view)
+ except Exception as e:
+ self.display_message(self.error, f"{repr(e)}")
+
+
+
+ def grid_on_drag_set(self, iconview, drag_context, data, info, time):
+ action = iconview.get_name()
+ wid, tid = action.split("|")
+ store = iconview.get_model()
+ treePaths = iconview.get_selected_items()
+ uris = self.format_to_uris(store, wid, tid, treePaths)
+
+ data.set_uris(uris)
+
+ def grid_on_drag_motion(self, iconview, drag_context, x, y, data):
+ wid, tid = iconview.get_name().split("|")
+ self.window_controller.set_active_data(wid, tid)
+
+ def grid_on_drag_data_received(self, widget, drag_context, x, y, data, info, time):
+ if info == 80:
+ wid, tid = self.window_controller.get_active_data()
+ notebook = self.builder.get_object(f"window_{wid}")
+ store, tab_label = self.get_store_and_label_from_notebook(notebook, f"{wid}|{tid}")
+ view = self.get_fm_window(wid).get_view_by_id(tid)
+
+ uris = data.get_uris()
+ dest = view.get_current_directory()
+
+ if len(uris) > 0:
+ if debug:
+ print(f"Target Move Path: {dest}")
+
+ for uri in uris:
+ if debug:
+ print(f"URI: {uri}")
+ self.move_file(view, uri, dest)
+
+ def create_new_view_notebook(self, widget=None, wid=None, path=None):
+ self.create_tab(wid, path)
diff --git a/src/versions/solarfm-0.0.1/SolarFM/new/solarfm/signal_classes/mixins/__init__.py b/src/versions/solarfm-0.0.1/SolarFM/new/solarfm/signal_classes/mixins/__init__.py
new file mode 100644
index 0000000..cd23f8d
--- /dev/null
+++ b/src/versions/solarfm-0.0.1/SolarFM/new/solarfm/signal_classes/mixins/__init__.py
@@ -0,0 +1,5 @@
+from .PaneMixin import PaneMixin
+from .WidgetMixin import WidgetMixin
+from .TabMixin import TabMixin
+from .WindowMixin import WindowMixin
+from .WidgetFileActionMixin import WidgetFileActionMixin
diff --git a/src/versions/solarfm-0.0.1/SolarFM/new/solarfm/solarfm b/src/versions/solarfm-0.0.1/SolarFM/new/solarfm/solarfm
new file mode 100755
index 0000000..40cd1fd
--- /dev/null
+++ b/src/versions/solarfm-0.0.1/SolarFM/new/solarfm/solarfm
@@ -0,0 +1,18 @@
+#!/bin/bash
+
+# . CONFIG.sh
+
+# 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() {
+ SCRIPTPATH="$( cd "$(dirname "")" >/dev/null 2>&1 ; pwd -P )"
+ cd "${SCRIPTPATH}"
+ echo "Working Dir: " $(pwd)
+
+ source "/home/abaddon/Portable_Apps/py-venvs/flask-apps-venv/venv/bin/activate"
+ python ../solarfm "$@"
+}
+main "$@";
diff --git a/src/versions/solarfm-0.0.1/SolarFM/new/solarfm/utils/Logger.py b/src/versions/solarfm-0.0.1/SolarFM/new/solarfm/utils/Logger.py
new file mode 100644
index 0000000..c7f294e
--- /dev/null
+++ b/src/versions/solarfm-0.0.1/SolarFM/new/solarfm/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/solarfm-0.0.1/SolarFM/new/solarfm/utils/Settings.py b/src/versions/solarfm-0.0.1/SolarFM/new/solarfm/utils/Settings.py
new file mode 100644
index 0000000..46296b1
--- /dev/null
+++ b/src/versions/solarfm-0.0.1/SolarFM/new/solarfm/utils/Settings.py
@@ -0,0 +1,69 @@
+# 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.gladefile = self.SCRIPT_PTH + "/../resources/Main_Window.glade"
+ self.cssFile = self.SCRIPT_PTH + '/../resources/stylesheet.css'
+ self.logger = Logger().get_logger()
+
+ self.builder = gtk.Builder()
+ self.builder.add_from_file(self.gladefile)
+ self.mainWindow = None
+
+
+
+ def createWindow(self):
+ # Get window and connect signals
+ self.mainWindow = self.builder.get_object("Main_Window")
+ self.setWindowData()
+
+ def setWindowData(self):
+ screen = self.mainWindow.get_screen()
+ visual = screen.get_rgba_visual()
+
+ if visual != None and screen.is_composited():
+ self.mainWindow.set_visual(visual)
+ self.mainWindow.set_app_paintable(True)
+ self.mainWindow.connect("draw", self.area_draw)
+
+ # bind css file
+ cssProvider = gtk.CssProvider()
+ cssProvider.load_from_path(self.cssFile)
+ screen = gdk.Screen.get_default()
+ styleContext = gtk.StyleContext()
+ styleContext.add_provider_for_screen(screen, cssProvider, gtk.STYLE_PROVIDER_PRIORITY_USER)
+
+ 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)
+
+ def getMainWindow(self): return self.mainWindow
+
+
+ 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
diff --git a/src/versions/solarfm-0.0.1/SolarFM/new/solarfm/utils/__init__.py b/src/versions/solarfm-0.0.1/SolarFM/new/solarfm/utils/__init__.py
new file mode 100644
index 0000000..415301e
--- /dev/null
+++ b/src/versions/solarfm-0.0.1/SolarFM/new/solarfm/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/solarfm-0.0.1/SolarFM/old/PyFM.py
similarity index 100%
rename from src/versions/pyfm-0.0.1/PyFM/old/PyFM.py
rename to src/versions/solarfm-0.0.1/SolarFM/old/PyFM.py
diff --git a/src/debs/pyFfm-0-0-1-x64/opt/PyFM/PyFM.sh b/src/versions/solarfm-0.0.1/SolarFM/old/PyFM.sh
old mode 100644
new mode 100755
similarity index 100%
rename from src/debs/pyFfm-0-0-1-x64/opt/PyFM/PyFM.sh
rename to src/versions/solarfm-0.0.1/SolarFM/old/PyFM.sh
diff --git a/src/versions/pyfm-0.0.1/PyFM/old/resources/PyFM.glade b/src/versions/solarfm-0.0.1/SolarFM/old/resources/PyFM.glade
similarity index 100%
rename from src/versions/pyfm-0.0.1/PyFM/old/resources/PyFM.glade
rename to src/versions/solarfm-0.0.1/SolarFM/old/resources/PyFM.glade
diff --git a/src/debs/pyFfm-0-0-1-x64/opt/PyFM/resources/icons/archive.png b/src/versions/solarfm-0.0.1/SolarFM/old/resources/icons/archive.png
similarity index 100%
rename from src/debs/pyFfm-0-0-1-x64/opt/PyFM/resources/icons/archive.png
rename to src/versions/solarfm-0.0.1/SolarFM/old/resources/icons/archive.png
diff --git a/src/debs/pyFfm-0-0-1-x64/opt/PyFM/resources/icons/audio.png b/src/versions/solarfm-0.0.1/SolarFM/old/resources/icons/audio.png
similarity index 100%
rename from src/debs/pyFfm-0-0-1-x64/opt/PyFM/resources/icons/audio.png
rename to src/versions/solarfm-0.0.1/SolarFM/old/resources/icons/audio.png
diff --git a/src/debs/pyFfm-0-0-1-x64/opt/PyFM/resources/icons/bin.png b/src/versions/solarfm-0.0.1/SolarFM/old/resources/icons/bin.png
similarity index 100%
rename from src/debs/pyFfm-0-0-1-x64/opt/PyFM/resources/icons/bin.png
rename to src/versions/solarfm-0.0.1/SolarFM/old/resources/icons/bin.png
diff --git a/src/debs/pyFfm-0-0-1-x64/opt/PyFM/resources/icons/dir.png b/src/versions/solarfm-0.0.1/SolarFM/old/resources/icons/dir.png
similarity index 100%
rename from src/debs/pyFfm-0-0-1-x64/opt/PyFM/resources/icons/dir.png
rename to src/versions/solarfm-0.0.1/SolarFM/old/resources/icons/dir.png
diff --git a/src/debs/pyFfm-0-0-1-x64/opt/PyFM/resources/icons/doc.png b/src/versions/solarfm-0.0.1/SolarFM/old/resources/icons/doc.png
similarity index 100%
rename from src/debs/pyFfm-0-0-1-x64/opt/PyFM/resources/icons/doc.png
rename to src/versions/solarfm-0.0.1/SolarFM/old/resources/icons/doc.png
diff --git a/src/debs/pyFfm-0-0-1-x64/opt/PyFM/resources/icons/pdf.png b/src/versions/solarfm-0.0.1/SolarFM/old/resources/icons/pdf.png
similarity index 100%
rename from src/debs/pyFfm-0-0-1-x64/opt/PyFM/resources/icons/pdf.png
rename to src/versions/solarfm-0.0.1/SolarFM/old/resources/icons/pdf.png
diff --git a/src/debs/pyFfm-0-0-1-x64/opt/PyFM/resources/icons/presentation.png b/src/versions/solarfm-0.0.1/SolarFM/old/resources/icons/presentation.png
similarity index 100%
rename from src/debs/pyFfm-0-0-1-x64/opt/PyFM/resources/icons/presentation.png
rename to src/versions/solarfm-0.0.1/SolarFM/old/resources/icons/presentation.png
diff --git a/src/debs/pyFfm-0-0-1-x64/opt/PyFM/resources/icons/spreadsheet.png b/src/versions/solarfm-0.0.1/SolarFM/old/resources/icons/spreadsheet.png
similarity index 100%
rename from src/debs/pyFfm-0-0-1-x64/opt/PyFM/resources/icons/spreadsheet.png
rename to src/versions/solarfm-0.0.1/SolarFM/old/resources/icons/spreadsheet.png
diff --git a/src/debs/pyFfm-0-0-1-x64/opt/PyFM/resources/icons/text.png b/src/versions/solarfm-0.0.1/SolarFM/old/resources/icons/text.png
similarity index 100%
rename from src/debs/pyFfm-0-0-1-x64/opt/PyFM/resources/icons/text.png
rename to src/versions/solarfm-0.0.1/SolarFM/old/resources/icons/text.png
diff --git a/src/debs/pyFfm-0-0-1-x64/opt/PyFM/resources/icons/video.png b/src/versions/solarfm-0.0.1/SolarFM/old/resources/icons/video.png
similarity index 100%
rename from src/debs/pyFfm-0-0-1-x64/opt/PyFM/resources/icons/video.png
rename to src/versions/solarfm-0.0.1/SolarFM/old/resources/icons/video.png
diff --git a/src/debs/pyFfm-0-0-1-x64/opt/PyFM/resources/icons/web.png b/src/versions/solarfm-0.0.1/SolarFM/old/resources/icons/web.png
similarity index 100%
rename from src/debs/pyFfm-0-0-1-x64/opt/PyFM/resources/icons/web.png
rename to src/versions/solarfm-0.0.1/SolarFM/old/resources/icons/web.png
diff --git a/src/debs/pyFfm-0-0-1-x64/opt/PyFM/resources/stylesheet.css b/src/versions/solarfm-0.0.1/SolarFM/old/resources/stylesheet.css
similarity index 100%
rename from src/debs/pyFfm-0-0-1-x64/opt/PyFM/resources/stylesheet.css
rename to src/versions/solarfm-0.0.1/SolarFM/old/resources/stylesheet.css
diff --git a/src/debs/pyFfm-0-0-1-x64/opt/PyFM/utils/Dragging.py b/src/versions/solarfm-0.0.1/SolarFM/old/utils/Dragging.py
similarity index 100%
rename from src/debs/pyFfm-0-0-1-x64/opt/PyFM/utils/Dragging.py
rename to src/versions/solarfm-0.0.1/SolarFM/old/utils/Dragging.py
diff --git a/src/debs/pyFfm-0-0-1-x64/opt/PyFM/utils/Events.py b/src/versions/solarfm-0.0.1/SolarFM/old/utils/Events.py
similarity index 100%
rename from src/debs/pyFfm-0-0-1-x64/opt/PyFM/utils/Events.py
rename to src/versions/solarfm-0.0.1/SolarFM/old/utils/Events.py
diff --git a/src/debs/pyFfm-0-0-1-x64/opt/PyFM/utils/FileHandler.py b/src/versions/solarfm-0.0.1/SolarFM/old/utils/FileHandler.py
similarity index 100%
rename from src/debs/pyFfm-0-0-1-x64/opt/PyFM/utils/FileHandler.py
rename to src/versions/solarfm-0.0.1/SolarFM/old/utils/FileHandler.py
diff --git a/src/debs/pyFfm-0-0-1-x64/opt/PyFM/utils/Grid.py b/src/versions/solarfm-0.0.1/SolarFM/old/utils/Grid.py
similarity index 100%
rename from src/debs/pyFfm-0-0-1-x64/opt/PyFM/utils/Grid.py
rename to src/versions/solarfm-0.0.1/SolarFM/old/utils/Grid.py
diff --git a/src/debs/pyFfm-0-0-1-x64/opt/PyFM/utils/Icon.py b/src/versions/solarfm-0.0.1/SolarFM/old/utils/Icon.py
similarity index 100%
rename from src/debs/pyFfm-0-0-1-x64/opt/PyFM/utils/Icon.py
rename to src/versions/solarfm-0.0.1/SolarFM/old/utils/Icon.py
diff --git a/src/versions/pyfm-0.0.1/PyFM/old/utils/Settings.py b/src/versions/solarfm-0.0.1/SolarFM/old/utils/Settings.py
similarity index 100%
rename from src/versions/pyfm-0.0.1/PyFM/old/utils/Settings.py
rename to src/versions/solarfm-0.0.1/SolarFM/old/utils/Settings.py
diff --git a/src/debs/pyFfm-0-0-1-x64/opt/PyFM/utils/__init__.py b/src/versions/solarfm-0.0.1/SolarFM/old/utils/__init__.py
similarity index 100%
rename from src/debs/pyFfm-0-0-1-x64/opt/PyFM/utils/__init__.py
rename to src/versions/solarfm-0.0.1/SolarFM/old/utils/__init__.py
diff --git a/src/versions/pyfm-0.0.1/PyFM_exec_bin.cpp b/src/versions/solarfm-0.0.1/SolarFM_exec_bin.cpp
similarity index 67%
rename from src/versions/pyfm-0.0.1/PyFM_exec_bin.cpp
rename to src/versions/solarfm-0.0.1/SolarFM_exec_bin.cpp
index 6d3f425..f381eb5 100644
--- a/src/versions/pyfm-0.0.1/PyFM_exec_bin.cpp
+++ b/src/versions/solarfm-0.0.1/SolarFM_exec_bin.cpp
@@ -4,7 +4,7 @@
using namespace std;
int main() {
- chdir("/opt/PyFM/");
- system("python3 .");
+ chdir("/opt/SolarFM/");
+ system("python .");
return 0;
}
diff --git a/src/versions/pyfm-0.0.1/clear_pycache_dirs.sh b/src/versions/solarfm-0.0.1/clear_pycache_dirs.sh
similarity index 100%
rename from src/versions/pyfm-0.0.1/clear_pycache_dirs.sh
rename to src/versions/solarfm-0.0.1/clear_pycache_dirs.sh
diff --git a/src/versions/solarfm-0.0.1/compileBin.sh b/src/versions/solarfm-0.0.1/compileBin.sh
new file mode 100755
index 0000000..c6efca6
--- /dev/null
+++ b/src/versions/solarfm-0.0.1/compileBin.sh
@@ -0,0 +1,6 @@
+#!/bin/bash
+
+function main() {
+ gcc -no-pie -s SolarFM_exec_bin.cpp -o solarfm
+}
+main;
diff --git a/src/versions/pyfm-0.0.1/pyfm.desktop b/src/versions/solarfm-0.0.1/solarfm.desktop
similarity index 75%
rename from src/versions/pyfm-0.0.1/pyfm.desktop
rename to src/versions/solarfm-0.0.1/solarfm.desktop
index 72690af..3ad68dc 100755
--- a/src/versions/pyfm-0.0.1/pyfm.desktop
+++ b/src/versions/solarfm-0.0.1/solarfm.desktop
@@ -1,10 +1,10 @@
[Desktop Entry]
-Name=PyFM
+Name=SolarFM
GenericName=File Manager
Comment=A file manager built with Python and GObject introspection.
-Path=/home/abaddon/.local/share/pyfm
-Exec=/home/abaddon/Portable_Apps/py-venvs/flask-apps-venv/venv/bin/python /home/abaddon/.local/share/pyfm %F
-Icon=/home/abaddon/.local/share/pyfm/resources/pyfm-64x64.png
+Path=/home/abaddon/.local/share/solarfm
+Exec=/home/abaddon/Portable_Apps/py-venvs/flask-apps-venv/venv/bin/python /home/abaddon/.local/share/solarfm %F
+Icon=/home/abaddon/.local/share/solarfm/resources/solarfm-64x64.png
Type=Application
StartupNotify=true
Categories=System;FileTools;Utility;Core;GTK;FileManager;
diff --git a/user_config/pyfm/icons/archive.png b/user_config/pyfm/icons/archive.png
deleted file mode 100644
index 7943e4e..0000000
Binary files a/user_config/pyfm/icons/archive.png and /dev/null differ
diff --git a/user_config/pyfm/icons/audio.png b/user_config/pyfm/icons/audio.png
deleted file mode 100644
index c010134..0000000
Binary files a/user_config/pyfm/icons/audio.png and /dev/null differ
diff --git a/user_config/pyfm/icons/bin.png b/user_config/pyfm/icons/bin.png
deleted file mode 100644
index d6954e3..0000000
Binary files a/user_config/pyfm/icons/bin.png and /dev/null differ
diff --git a/user_config/pyfm/icons/dir.png b/user_config/pyfm/icons/dir.png
deleted file mode 100644
index a9b5e9f..0000000
Binary files a/user_config/pyfm/icons/dir.png and /dev/null differ
diff --git a/user_config/pyfm/icons/doc.png b/user_config/pyfm/icons/doc.png
deleted file mode 100644
index f838826..0000000
Binary files a/user_config/pyfm/icons/doc.png and /dev/null differ
diff --git a/user_config/pyfm/icons/pdf.png b/user_config/pyfm/icons/pdf.png
deleted file mode 100644
index 9f40122..0000000
Binary files a/user_config/pyfm/icons/pdf.png and /dev/null differ
diff --git a/user_config/pyfm/icons/presentation.png b/user_config/pyfm/icons/presentation.png
deleted file mode 100644
index 3a339af..0000000
Binary files a/user_config/pyfm/icons/presentation.png and /dev/null differ
diff --git a/user_config/pyfm/icons/spreadsheet.png b/user_config/pyfm/icons/spreadsheet.png
deleted file mode 100644
index 710efa6..0000000
Binary files a/user_config/pyfm/icons/spreadsheet.png and /dev/null differ
diff --git a/user_config/pyfm/icons/text.png b/user_config/pyfm/icons/text.png
deleted file mode 100644
index 2546fcd..0000000
Binary files a/user_config/pyfm/icons/text.png and /dev/null differ
diff --git a/user_config/pyfm/icons/video.png b/user_config/pyfm/icons/video.png
deleted file mode 100644
index 55afa98..0000000
Binary files a/user_config/pyfm/icons/video.png and /dev/null differ
diff --git a/user_config/pyfm/icons/web.png b/user_config/pyfm/icons/web.png
deleted file mode 100644
index 17017ce..0000000
Binary files a/user_config/pyfm/icons/web.png and /dev/null differ
diff --git a/user_config/pyfm/ffmpegthumbnailer b/user_config/solarfm/ffmpegthumbnailer
similarity index 100%
rename from user_config/pyfm/ffmpegthumbnailer
rename to user_config/solarfm/ffmpegthumbnailer
diff --git a/src/versions/pyfm-0.0.1/PyFM/old/resources/icons/archive.png b/user_config/solarfm/icons/archive.png
similarity index 100%
rename from src/versions/pyfm-0.0.1/PyFM/old/resources/icons/archive.png
rename to user_config/solarfm/icons/archive.png
diff --git a/src/versions/pyfm-0.0.1/PyFM/old/resources/icons/audio.png b/user_config/solarfm/icons/audio.png
similarity index 100%
rename from src/versions/pyfm-0.0.1/PyFM/old/resources/icons/audio.png
rename to user_config/solarfm/icons/audio.png
diff --git a/src/versions/pyfm-0.0.1/PyFM/old/resources/icons/bin.png b/user_config/solarfm/icons/bin.png
similarity index 100%
rename from src/versions/pyfm-0.0.1/PyFM/old/resources/icons/bin.png
rename to user_config/solarfm/icons/bin.png
diff --git a/src/versions/pyfm-0.0.1/PyFM/old/resources/icons/dir.png b/user_config/solarfm/icons/dir.png
similarity index 100%
rename from src/versions/pyfm-0.0.1/PyFM/old/resources/icons/dir.png
rename to user_config/solarfm/icons/dir.png
diff --git a/src/versions/pyfm-0.0.1/PyFM/old/resources/icons/doc.png b/user_config/solarfm/icons/doc.png
similarity index 100%
rename from src/versions/pyfm-0.0.1/PyFM/old/resources/icons/doc.png
rename to user_config/solarfm/icons/doc.png
diff --git a/src/versions/pyfm-0.0.1/PyFM/old/resources/icons/pdf.png b/user_config/solarfm/icons/pdf.png
similarity index 100%
rename from src/versions/pyfm-0.0.1/PyFM/old/resources/icons/pdf.png
rename to user_config/solarfm/icons/pdf.png
diff --git a/src/versions/pyfm-0.0.1/PyFM/old/resources/icons/presentation.png b/user_config/solarfm/icons/presentation.png
similarity index 100%
rename from src/versions/pyfm-0.0.1/PyFM/old/resources/icons/presentation.png
rename to user_config/solarfm/icons/presentation.png
diff --git a/src/versions/pyfm-0.0.1/PyFM/old/resources/icons/spreadsheet.png b/user_config/solarfm/icons/spreadsheet.png
similarity index 100%
rename from src/versions/pyfm-0.0.1/PyFM/old/resources/icons/spreadsheet.png
rename to user_config/solarfm/icons/spreadsheet.png
diff --git a/src/versions/pyfm-0.0.1/PyFM/old/resources/icons/text.png b/user_config/solarfm/icons/text.png
similarity index 100%
rename from src/versions/pyfm-0.0.1/PyFM/old/resources/icons/text.png
rename to user_config/solarfm/icons/text.png
diff --git a/user_config/pyfm/icons/trash.png b/user_config/solarfm/icons/trash.png
similarity index 100%
rename from user_config/pyfm/icons/trash.png
rename to user_config/solarfm/icons/trash.png
diff --git a/src/versions/pyfm-0.0.1/PyFM/old/resources/icons/video.png b/user_config/solarfm/icons/video.png
similarity index 100%
rename from src/versions/pyfm-0.0.1/PyFM/old/resources/icons/video.png
rename to user_config/solarfm/icons/video.png
diff --git a/src/versions/pyfm-0.0.1/PyFM/old/resources/icons/web.png b/user_config/solarfm/icons/web.png
similarity index 100%
rename from src/versions/pyfm-0.0.1/PyFM/old/resources/icons/web.png
rename to user_config/solarfm/icons/web.png
diff --git a/user_config/pyfm/settings.json b/user_config/solarfm/settings.json
similarity index 93%
rename from user_config/pyfm/settings.json
rename to user_config/solarfm/settings.json
index 1702af1..a30a757 100644
--- a/user_config/pyfm/settings.json
+++ b/user_config/solarfm/settings.json
@@ -13,7 +13,7 @@
"office_app": "libreoffice",
"pdf_app": "evince",
"text_app": "leafpad",
- "file_manager_app": "spacefm",
+ "file_manager_app": "solarfm",
"remux_folder_max_disk_usage": "8589934592"
}
}