From 4f18844fd2b31b6bb4127e45ad5a9636bf7bae35 Mon Sep 17 00:00:00 2001 From: itdominator <1itdominator@gmail.com> Date: Sun, 28 Nov 2021 16:01:29 -0600 Subject: [PATCH] Renaming to SolarFM --- README.md | 17 +- bin/REMOVE.txt | 1 + bin/pytop-0-0-1-x64.deb | Bin 20764 -> 0 bytes src/debs/pyFfm-0-0-1-x64/DEBIAN/postrm | 11 - src/debs/pyFfm-0-0-1-x64/bin/pytop | Bin 6168 -> 0 bytes src/debs/pyFfm-0-0-1-x64/opt/PyFM/PyFM.py | 36 - .../opt/PyFM/resources/PyTop.glade | 305 --- .../opt/PyFM/utils/Settings.py | 139 -- .../DEBIAN/control | 2 +- src/debs/solarfm-0-0-1-x64/DEBIAN/postrm | 11 + src/debs/solarfm-0-0-1-x64/bin/solarfm | Bin 0 -> 14416 bytes .../opt/SolarFM}/__builtins__.py | 0 .../opt/SolarFM}/__init__.py | 0 .../opt/SolarFM}/__main__.py | 2 +- .../opt/SolarFM}/resources/Main_Window.glade | 15 +- .../opt/SolarFM/resources/solarfm-64x64.png} | Bin .../opt/SolarFM/resources/solarfm.png} | Bin .../opt/SolarFM}/resources/stylesheet.css | 0 .../opt/SolarFM}/shellfm/__init__.py | 0 .../opt/SolarFM}/shellfm/windows/Window.py | 0 .../shellfm/windows/WindowController.py | 2 +- .../opt/SolarFM}/shellfm/windows/__init__.py | 0 .../opt/SolarFM}/shellfm/windows/view/Path.py | 0 .../opt/SolarFM}/shellfm/windows/view/View.py | 0 .../SolarFM}/shellfm/windows/view/__init__.py | 0 .../shellfm/windows/view/icons/Icon.py | 0 .../shellfm/windows/view/icons/__init__.py | 0 .../view/icons/mixins/DesktopIconMixin.py | 0 .../view/icons/mixins/VideoIconMixin.py | 0 .../windows/view/icons/mixins/__init__.py | 0 .../view/icons/mixins/xdg/BaseDirectory.py | 0 .../windows/view/icons/mixins/xdg/Config.py | 0 .../view/icons/mixins/xdg/DesktopEntry.py | 0 .../view/icons/mixins/xdg/Exceptions.py | 0 .../view/icons/mixins/xdg/IconTheme.py | 0 .../windows/view/icons/mixins/xdg/IniFile.py | 0 .../windows/view/icons/mixins/xdg/Locale.py | 0 .../windows/view/icons/mixins/xdg/Menu.py | 0 .../view/icons/mixins/xdg/MenuEditor.py | 0 .../windows/view/icons/mixins/xdg/Mime.py | 0 .../view/icons/mixins/xdg/RecentFiles.py | 0 .../windows/view/icons/mixins/xdg/__init__.py | 0 .../windows/view/icons/mixins/xdg/util.py | 0 .../shellfm/windows/view/utils/FileHandler.py | 0 .../shellfm/windows/view/utils/Launcher.py | 0 .../shellfm/windows/view/utils/Settings.py | 2 +- .../shellfm/windows/view/utils/__init__.py | 0 .../opt/SolarFM}/signal_classes/Controller.py | 0 .../signal_classes/Controller_Data.py | 0 .../signal_classes/DBusControllerMixin.py | 4 +- .../signal_classes/KeyboardSignalsMixin.py | 2 +- .../SolarFM}/signal_classes/ShowHideMixin.py | 0 .../opt/SolarFM}/signal_classes/__init__.py | 0 .../signal_classes/mixins/PaneMixin.py | 0 .../signal_classes/mixins/TabMixin.py | 0 .../mixins/WidgetFileActionMixin.py | 0 .../signal_classes/mixins/WidgetMixin.py | 0 .../signal_classes/mixins/WindowMixin.py | 2 +- .../signal_classes/mixins/__init__.py | 0 .../solarfm-0-0-1-x64/opt/SolarFM/solarfm} | 2 +- .../opt/SolarFM}/utils/Logger.py | 0 .../opt/SolarFM}/utils/Settings.py | 0 .../opt/SolarFM}/utils/__init__.py | 0 .../usr/share/doc/solarfm}/copyright | 6 +- .../new/pyfm/resources/appchooserdlg.glade | 331 ---- .../new/pyfm/resources/file_properties.glade | 858 --------- src/versions/pyfm-0.0.1/PyFM/old/PyFM.sh | 12 - .../PyFM/old/resources/stylesheet.css | 88 - .../pyfm-0.0.1/PyFM/old/utils/Dragging.py | 79 - .../pyfm-0.0.1/PyFM/old/utils/Events.py | 72 - .../pyfm-0.0.1/PyFM/old/utils/FileHandler.py | 93 - .../pyfm-0.0.1/PyFM/old/utils/Grid.py | 214 --- .../pyfm-0.0.1/PyFM/old/utils/Icon.py | 167 -- .../pyfm-0.0.1/PyFM/old/utils/__init__.py | 6 - src/versions/pyfm-0.0.1/compileBin.sh | 6 - .../SolarFM}/new/setup.py | 4 +- .../SolarFM/new/solarfm.sh} | 2 +- .../SolarFM/new/solarfm.toml} | 0 .../SolarFM/new/solarfm/__builtins__.py | 66 + .../SolarFM/new/solarfm/__init__.py | 49 + .../SolarFM/new/solarfm/__main__.py | 39 + .../new/solarfm/resources/Main_Window.glade | 1653 +++++++++++++++++ .../new/solarfm/resources/solarfm-64x64.png | Bin 0 -> 16172 bytes .../SolarFM/new/solarfm/resources/solarfm.png | Bin 0 -> 40783 bytes .../new/solarfm/resources/stylesheet.css | 67 + .../SolarFM/new/solarfm/shellfm/__init__.py | 1 + .../new/solarfm/shellfm/windows/Window.py | 66 + .../shellfm/windows/WindowController.py | 179 ++ .../new/solarfm/shellfm/windows/__init__.py | 2 + .../new/solarfm/shellfm/windows/view/Path.py | 59 + .../new/solarfm/shellfm/windows/view/View.py | 227 +++ .../solarfm/shellfm/windows/view/__init__.py | 5 + .../shellfm/windows/view/icons/Icon.py | 76 + .../shellfm/windows/view/icons/__init__.py | 4 + .../view/icons/mixins/DesktopIconMixin.py | 65 + .../view/icons/mixins/VideoIconMixin.py | 53 + .../windows/view/icons/mixins/__init__.py | 4 + .../view/icons/mixins/xdg/BaseDirectory.py | 160 ++ .../windows/view/icons/mixins/xdg/Config.py | 39 + .../view/icons/mixins/xdg/DesktopEntry.py | 435 +++++ .../view/icons/mixins/xdg/Exceptions.py | 84 + .../view/icons/mixins/xdg/IconTheme.py | 445 +++++ .../windows/view/icons/mixins/xdg/IniFile.py | 419 +++++ .../windows/view/icons/mixins/xdg/Locale.py | 79 + .../windows/view/icons/mixins/xdg/Menu.py | 1125 +++++++++++ .../view/icons/mixins/xdg/MenuEditor.py | 541 ++++++ .../windows/view/icons/mixins/xdg/Mime.py | 780 ++++++++ .../view/icons/mixins/xdg/RecentFiles.py | 181 ++ .../windows/view/icons/mixins/xdg/__init__.py | 3 + .../windows/view/icons/mixins/xdg/util.py | 75 + .../shellfm/windows/view/utils/FileHandler.py | 80 + .../shellfm/windows/view/utils/Launcher.py | 95 + .../shellfm/windows/view/utils/Settings.py | 94 + .../shellfm/windows/view/utils/__init__.py | 3 + .../new/solarfm/signal_classes/Controller.py | 150 ++ .../solarfm/signal_classes/Controller_Data.py | 53 + .../signal_classes/DBusControllerMixin.py | 63 + .../signal_classes/KeyboardSignalsMixin.py | 78 + .../solarfm/signal_classes/ShowHideMixin.py | 65 + .../new/solarfm/signal_classes/__init__.py | 9 + .../signal_classes/mixins/PaneMixin.py | 59 + .../solarfm/signal_classes/mixins/TabMixin.py | 178 ++ .../mixins/WidgetFileActionMixin.py | 258 +++ .../signal_classes/mixins/WidgetMixin.py | 213 +++ .../signal_classes/mixins/WindowMixin.py | 159 ++ .../solarfm/signal_classes/mixins/__init__.py | 5 + .../solarfm-0.0.1/SolarFM/new/solarfm/solarfm | 18 + .../SolarFM/new/solarfm/utils/Logger.py | 56 + .../SolarFM/new/solarfm/utils/Settings.py | 69 + .../SolarFM/new/solarfm/utils/__init__.py | 6 + .../SolarFM}/old/PyFM.py | 0 .../solarfm-0.0.1/SolarFM/old}/PyFM.sh | 0 .../SolarFM}/old/resources/PyFM.glade | 0 .../SolarFM/old}/resources/icons/archive.png | Bin .../SolarFM/old}/resources/icons/audio.png | Bin .../SolarFM/old}/resources/icons/bin.png | Bin .../SolarFM/old}/resources/icons/dir.png | Bin .../SolarFM/old}/resources/icons/doc.png | Bin .../SolarFM/old}/resources/icons/pdf.png | Bin .../old}/resources/icons/presentation.png | Bin .../old}/resources/icons/spreadsheet.png | Bin .../SolarFM/old}/resources/icons/text.png | Bin .../SolarFM/old}/resources/icons/video.png | Bin .../SolarFM/old}/resources/icons/web.png | Bin .../SolarFM/old}/resources/stylesheet.css | 0 .../SolarFM/old}/utils/Dragging.py | 0 .../SolarFM/old}/utils/Events.py | 0 .../SolarFM/old}/utils/FileHandler.py | 0 .../solarfm-0.0.1/SolarFM/old}/utils/Grid.py | 0 .../solarfm-0.0.1/SolarFM/old}/utils/Icon.py | 0 .../SolarFM}/old/utils/Settings.py | 0 .../SolarFM/old}/utils/__init__.py | 0 .../SolarFM_exec_bin.cpp} | 4 +- .../clear_pycache_dirs.sh | 0 src/versions/solarfm-0.0.1/compileBin.sh | 6 + .../solarfm.desktop} | 8 +- user_config/pyfm/icons/archive.png | Bin 1670 -> 0 bytes user_config/pyfm/icons/audio.png | Bin 1544 -> 0 bytes user_config/pyfm/icons/bin.png | Bin 858 -> 0 bytes user_config/pyfm/icons/dir.png | Bin 850 -> 0 bytes user_config/pyfm/icons/doc.png | Bin 702 -> 0 bytes user_config/pyfm/icons/pdf.png | Bin 925 -> 0 bytes user_config/pyfm/icons/presentation.png | Bin 882 -> 0 bytes user_config/pyfm/icons/spreadsheet.png | Bin 707 -> 0 bytes user_config/pyfm/icons/text.png | Bin 798 -> 0 bytes user_config/pyfm/icons/video.png | Bin 1313 -> 0 bytes user_config/pyfm/icons/web.png | Bin 1845 -> 0 bytes .../{pyfm => solarfm}/ffmpegthumbnailer | Bin .../solarfm}/icons/archive.png | Bin .../solarfm}/icons/audio.png | Bin .../solarfm}/icons/bin.png | Bin .../solarfm}/icons/dir.png | Bin .../solarfm}/icons/doc.png | Bin .../solarfm}/icons/pdf.png | Bin .../solarfm}/icons/presentation.png | Bin .../solarfm}/icons/spreadsheet.png | Bin .../solarfm}/icons/text.png | Bin user_config/{pyfm => solarfm}/icons/trash.png | Bin .../solarfm}/icons/video.png | Bin .../solarfm}/icons/web.png | Bin user_config/{pyfm => solarfm}/settings.json | 2 +- 181 files changed, 8751 insertions(+), 2452 deletions(-) create mode 100644 bin/REMOVE.txt delete mode 100644 bin/pytop-0-0-1-x64.deb delete mode 100755 src/debs/pyFfm-0-0-1-x64/DEBIAN/postrm delete mode 100755 src/debs/pyFfm-0-0-1-x64/bin/pytop delete mode 100644 src/debs/pyFfm-0-0-1-x64/opt/PyFM/PyFM.py delete mode 100644 src/debs/pyFfm-0-0-1-x64/opt/PyFM/resources/PyTop.glade delete mode 100644 src/debs/pyFfm-0-0-1-x64/opt/PyFM/utils/Settings.py rename src/debs/{pyFfm-0-0-1-x64 => solarfm-0-0-1-x64}/DEBIAN/control (77%) create mode 100755 src/debs/solarfm-0-0-1-x64/DEBIAN/postrm create mode 100755 src/debs/solarfm-0-0-1-x64/bin/solarfm rename src/{versions/pyfm-0.0.1/PyFM/new/pyfm => debs/solarfm-0-0-1-x64/opt/SolarFM}/__builtins__.py (100%) rename src/{versions/pyfm-0.0.1/PyFM/new/pyfm => debs/solarfm-0-0-1-x64/opt/SolarFM}/__init__.py (100%) rename src/{versions/pyfm-0.0.1/PyFM/new/pyfm => debs/solarfm-0-0-1-x64/opt/SolarFM}/__main__.py (96%) rename src/{versions/pyfm-0.0.1/PyFM/new/pyfm => debs/solarfm-0-0-1-x64/opt/SolarFM}/resources/Main_Window.glade (99%) rename src/{versions/pyfm-0.0.1/PyFM/new/pyfm/resources/pyfm-64x64.png => debs/solarfm-0-0-1-x64/opt/SolarFM/resources/solarfm-64x64.png} (100%) rename src/{versions/pyfm-0.0.1/PyFM/new/pyfm/resources/pyfm.png => debs/solarfm-0-0-1-x64/opt/SolarFM/resources/solarfm.png} (100%) rename src/{versions/pyfm-0.0.1/PyFM/new/pyfm => debs/solarfm-0-0-1-x64/opt/SolarFM}/resources/stylesheet.css (100%) rename src/{versions/pyfm-0.0.1/PyFM/new/pyfm => debs/solarfm-0-0-1-x64/opt/SolarFM}/shellfm/__init__.py (100%) rename src/{versions/pyfm-0.0.1/PyFM/new/pyfm => debs/solarfm-0-0-1-x64/opt/SolarFM}/shellfm/windows/Window.py (100%) rename src/{versions/pyfm-0.0.1/PyFM/new/pyfm => debs/solarfm-0-0-1-x64/opt/SolarFM}/shellfm/windows/WindowController.py (98%) rename src/{versions/pyfm-0.0.1/PyFM/new/pyfm => debs/solarfm-0-0-1-x64/opt/SolarFM}/shellfm/windows/__init__.py (100%) rename src/{versions/pyfm-0.0.1/PyFM/new/pyfm => debs/solarfm-0-0-1-x64/opt/SolarFM}/shellfm/windows/view/Path.py (100%) rename src/{versions/pyfm-0.0.1/PyFM/new/pyfm => debs/solarfm-0-0-1-x64/opt/SolarFM}/shellfm/windows/view/View.py (100%) rename src/{versions/pyfm-0.0.1/PyFM/new/pyfm => debs/solarfm-0-0-1-x64/opt/SolarFM}/shellfm/windows/view/__init__.py (100%) rename src/{versions/pyfm-0.0.1/PyFM/new/pyfm => debs/solarfm-0-0-1-x64/opt/SolarFM}/shellfm/windows/view/icons/Icon.py (100%) rename src/{versions/pyfm-0.0.1/PyFM/new/pyfm => debs/solarfm-0-0-1-x64/opt/SolarFM}/shellfm/windows/view/icons/__init__.py (100%) rename src/{versions/pyfm-0.0.1/PyFM/new/pyfm => debs/solarfm-0-0-1-x64/opt/SolarFM}/shellfm/windows/view/icons/mixins/DesktopIconMixin.py (100%) rename src/{versions/pyfm-0.0.1/PyFM/new/pyfm => debs/solarfm-0-0-1-x64/opt/SolarFM}/shellfm/windows/view/icons/mixins/VideoIconMixin.py (100%) rename src/{versions/pyfm-0.0.1/PyFM/new/pyfm => debs/solarfm-0-0-1-x64/opt/SolarFM}/shellfm/windows/view/icons/mixins/__init__.py (100%) rename src/{versions/pyfm-0.0.1/PyFM/new/pyfm => debs/solarfm-0-0-1-x64/opt/SolarFM}/shellfm/windows/view/icons/mixins/xdg/BaseDirectory.py (100%) rename src/{versions/pyfm-0.0.1/PyFM/new/pyfm => debs/solarfm-0-0-1-x64/opt/SolarFM}/shellfm/windows/view/icons/mixins/xdg/Config.py (100%) rename src/{versions/pyfm-0.0.1/PyFM/new/pyfm => debs/solarfm-0-0-1-x64/opt/SolarFM}/shellfm/windows/view/icons/mixins/xdg/DesktopEntry.py (100%) rename src/{versions/pyfm-0.0.1/PyFM/new/pyfm => debs/solarfm-0-0-1-x64/opt/SolarFM}/shellfm/windows/view/icons/mixins/xdg/Exceptions.py (100%) rename src/{versions/pyfm-0.0.1/PyFM/new/pyfm => debs/solarfm-0-0-1-x64/opt/SolarFM}/shellfm/windows/view/icons/mixins/xdg/IconTheme.py (100%) rename src/{versions/pyfm-0.0.1/PyFM/new/pyfm => debs/solarfm-0-0-1-x64/opt/SolarFM}/shellfm/windows/view/icons/mixins/xdg/IniFile.py (100%) rename src/{versions/pyfm-0.0.1/PyFM/new/pyfm => debs/solarfm-0-0-1-x64/opt/SolarFM}/shellfm/windows/view/icons/mixins/xdg/Locale.py (100%) rename src/{versions/pyfm-0.0.1/PyFM/new/pyfm => debs/solarfm-0-0-1-x64/opt/SolarFM}/shellfm/windows/view/icons/mixins/xdg/Menu.py (100%) rename src/{versions/pyfm-0.0.1/PyFM/new/pyfm => debs/solarfm-0-0-1-x64/opt/SolarFM}/shellfm/windows/view/icons/mixins/xdg/MenuEditor.py (100%) rename src/{versions/pyfm-0.0.1/PyFM/new/pyfm => debs/solarfm-0-0-1-x64/opt/SolarFM}/shellfm/windows/view/icons/mixins/xdg/Mime.py (100%) rename src/{versions/pyfm-0.0.1/PyFM/new/pyfm => debs/solarfm-0-0-1-x64/opt/SolarFM}/shellfm/windows/view/icons/mixins/xdg/RecentFiles.py (100%) rename src/{versions/pyfm-0.0.1/PyFM/new/pyfm => debs/solarfm-0-0-1-x64/opt/SolarFM}/shellfm/windows/view/icons/mixins/xdg/__init__.py (100%) rename src/{versions/pyfm-0.0.1/PyFM/new/pyfm => debs/solarfm-0-0-1-x64/opt/SolarFM}/shellfm/windows/view/icons/mixins/xdg/util.py (100%) rename src/{versions/pyfm-0.0.1/PyFM/new/pyfm => debs/solarfm-0-0-1-x64/opt/SolarFM}/shellfm/windows/view/utils/FileHandler.py (100%) rename src/{versions/pyfm-0.0.1/PyFM/new/pyfm => debs/solarfm-0-0-1-x64/opt/SolarFM}/shellfm/windows/view/utils/Launcher.py (100%) rename src/{versions/pyfm-0.0.1/PyFM/new/pyfm => debs/solarfm-0-0-1-x64/opt/SolarFM}/shellfm/windows/view/utils/Settings.py (98%) rename src/{versions/pyfm-0.0.1/PyFM/new/pyfm => debs/solarfm-0-0-1-x64/opt/SolarFM}/shellfm/windows/view/utils/__init__.py (100%) rename src/{versions/pyfm-0.0.1/PyFM/new/pyfm => debs/solarfm-0-0-1-x64/opt/SolarFM}/signal_classes/Controller.py (100%) rename src/{versions/pyfm-0.0.1/PyFM/new/pyfm => debs/solarfm-0-0-1-x64/opt/SolarFM}/signal_classes/Controller_Data.py (100%) rename src/{versions/pyfm-0.0.1/PyFM/new/pyfm => debs/solarfm-0-0-1-x64/opt/SolarFM}/signal_classes/DBusControllerMixin.py (95%) rename src/{versions/pyfm-0.0.1/PyFM/new/pyfm => debs/solarfm-0-0-1-x64/opt/SolarFM}/signal_classes/KeyboardSignalsMixin.py (97%) rename src/{versions/pyfm-0.0.1/PyFM/new/pyfm => debs/solarfm-0-0-1-x64/opt/SolarFM}/signal_classes/ShowHideMixin.py (100%) rename src/{versions/pyfm-0.0.1/PyFM/new/pyfm => debs/solarfm-0-0-1-x64/opt/SolarFM}/signal_classes/__init__.py (100%) rename src/{versions/pyfm-0.0.1/PyFM/new/pyfm => debs/solarfm-0-0-1-x64/opt/SolarFM}/signal_classes/mixins/PaneMixin.py (100%) rename src/{versions/pyfm-0.0.1/PyFM/new/pyfm => debs/solarfm-0-0-1-x64/opt/SolarFM}/signal_classes/mixins/TabMixin.py (100%) rename src/{versions/pyfm-0.0.1/PyFM/new/pyfm => debs/solarfm-0-0-1-x64/opt/SolarFM}/signal_classes/mixins/WidgetFileActionMixin.py (100%) rename src/{versions/pyfm-0.0.1/PyFM/new/pyfm => debs/solarfm-0-0-1-x64/opt/SolarFM}/signal_classes/mixins/WidgetMixin.py (100%) rename src/{versions/pyfm-0.0.1/PyFM/new/pyfm => debs/solarfm-0-0-1-x64/opt/SolarFM}/signal_classes/mixins/WindowMixin.py (99%) rename src/{versions/pyfm-0.0.1/PyFM/new/pyfm => debs/solarfm-0-0-1-x64/opt/SolarFM}/signal_classes/mixins/__init__.py (100%) rename src/{versions/pyfm-0.0.1/PyFM/new/pyfm/pyfm => debs/solarfm-0-0-1-x64/opt/SolarFM/solarfm} (94%) rename src/{versions/pyfm-0.0.1/PyFM/new/pyfm => debs/solarfm-0-0-1-x64/opt/SolarFM}/utils/Logger.py (100%) rename src/{versions/pyfm-0.0.1/PyFM/new/pyfm => debs/solarfm-0-0-1-x64/opt/SolarFM}/utils/Settings.py (100%) rename src/{versions/pyfm-0.0.1/PyFM/new/pyfm => debs/solarfm-0-0-1-x64/opt/SolarFM}/utils/__init__.py (100%) rename src/debs/{pyFfm-0-0-1-x64/usr/share/doc/pytop => solarfm-0-0-1-x64/usr/share/doc/solarfm}/copyright (87%) delete mode 100644 src/versions/pyfm-0.0.1/PyFM/new/pyfm/resources/appchooserdlg.glade delete mode 100644 src/versions/pyfm-0.0.1/PyFM/new/pyfm/resources/file_properties.glade delete mode 100755 src/versions/pyfm-0.0.1/PyFM/old/PyFM.sh delete mode 100644 src/versions/pyfm-0.0.1/PyFM/old/resources/stylesheet.css delete mode 100644 src/versions/pyfm-0.0.1/PyFM/old/utils/Dragging.py delete mode 100644 src/versions/pyfm-0.0.1/PyFM/old/utils/Events.py delete mode 100644 src/versions/pyfm-0.0.1/PyFM/old/utils/FileHandler.py delete mode 100644 src/versions/pyfm-0.0.1/PyFM/old/utils/Grid.py delete mode 100644 src/versions/pyfm-0.0.1/PyFM/old/utils/Icon.py delete mode 100644 src/versions/pyfm-0.0.1/PyFM/old/utils/__init__.py delete mode 100755 src/versions/pyfm-0.0.1/compileBin.sh rename src/versions/{pyfm-0.0.1/PyFM => solarfm-0.0.1/SolarFM}/new/setup.py (74%) rename src/versions/{pyfm-0.0.1/PyFM/new/pyfm.sh => solarfm-0.0.1/SolarFM/new/solarfm.sh} (95%) rename src/versions/{pyfm-0.0.1/PyFM/new/pyfm.toml => solarfm-0.0.1/SolarFM/new/solarfm.toml} (100%) create mode 100644 src/versions/solarfm-0.0.1/SolarFM/new/solarfm/__builtins__.py create mode 100644 src/versions/solarfm-0.0.1/SolarFM/new/solarfm/__init__.py create mode 100644 src/versions/solarfm-0.0.1/SolarFM/new/solarfm/__main__.py create mode 100644 src/versions/solarfm-0.0.1/SolarFM/new/solarfm/resources/Main_Window.glade create mode 100644 src/versions/solarfm-0.0.1/SolarFM/new/solarfm/resources/solarfm-64x64.png create mode 100644 src/versions/solarfm-0.0.1/SolarFM/new/solarfm/resources/solarfm.png create mode 100644 src/versions/solarfm-0.0.1/SolarFM/new/solarfm/resources/stylesheet.css create mode 100644 src/versions/solarfm-0.0.1/SolarFM/new/solarfm/shellfm/__init__.py create mode 100644 src/versions/solarfm-0.0.1/SolarFM/new/solarfm/shellfm/windows/Window.py create mode 100644 src/versions/solarfm-0.0.1/SolarFM/new/solarfm/shellfm/windows/WindowController.py create mode 100644 src/versions/solarfm-0.0.1/SolarFM/new/solarfm/shellfm/windows/__init__.py create mode 100644 src/versions/solarfm-0.0.1/SolarFM/new/solarfm/shellfm/windows/view/Path.py create mode 100644 src/versions/solarfm-0.0.1/SolarFM/new/solarfm/shellfm/windows/view/View.py create mode 100644 src/versions/solarfm-0.0.1/SolarFM/new/solarfm/shellfm/windows/view/__init__.py create mode 100644 src/versions/solarfm-0.0.1/SolarFM/new/solarfm/shellfm/windows/view/icons/Icon.py create mode 100644 src/versions/solarfm-0.0.1/SolarFM/new/solarfm/shellfm/windows/view/icons/__init__.py create mode 100644 src/versions/solarfm-0.0.1/SolarFM/new/solarfm/shellfm/windows/view/icons/mixins/DesktopIconMixin.py create mode 100644 src/versions/solarfm-0.0.1/SolarFM/new/solarfm/shellfm/windows/view/icons/mixins/VideoIconMixin.py create mode 100644 src/versions/solarfm-0.0.1/SolarFM/new/solarfm/shellfm/windows/view/icons/mixins/__init__.py create mode 100644 src/versions/solarfm-0.0.1/SolarFM/new/solarfm/shellfm/windows/view/icons/mixins/xdg/BaseDirectory.py create mode 100644 src/versions/solarfm-0.0.1/SolarFM/new/solarfm/shellfm/windows/view/icons/mixins/xdg/Config.py create mode 100644 src/versions/solarfm-0.0.1/SolarFM/new/solarfm/shellfm/windows/view/icons/mixins/xdg/DesktopEntry.py create mode 100644 src/versions/solarfm-0.0.1/SolarFM/new/solarfm/shellfm/windows/view/icons/mixins/xdg/Exceptions.py create mode 100644 src/versions/solarfm-0.0.1/SolarFM/new/solarfm/shellfm/windows/view/icons/mixins/xdg/IconTheme.py create mode 100644 src/versions/solarfm-0.0.1/SolarFM/new/solarfm/shellfm/windows/view/icons/mixins/xdg/IniFile.py create mode 100644 src/versions/solarfm-0.0.1/SolarFM/new/solarfm/shellfm/windows/view/icons/mixins/xdg/Locale.py create mode 100644 src/versions/solarfm-0.0.1/SolarFM/new/solarfm/shellfm/windows/view/icons/mixins/xdg/Menu.py create mode 100644 src/versions/solarfm-0.0.1/SolarFM/new/solarfm/shellfm/windows/view/icons/mixins/xdg/MenuEditor.py create mode 100644 src/versions/solarfm-0.0.1/SolarFM/new/solarfm/shellfm/windows/view/icons/mixins/xdg/Mime.py create mode 100644 src/versions/solarfm-0.0.1/SolarFM/new/solarfm/shellfm/windows/view/icons/mixins/xdg/RecentFiles.py create mode 100644 src/versions/solarfm-0.0.1/SolarFM/new/solarfm/shellfm/windows/view/icons/mixins/xdg/__init__.py create mode 100644 src/versions/solarfm-0.0.1/SolarFM/new/solarfm/shellfm/windows/view/icons/mixins/xdg/util.py create mode 100644 src/versions/solarfm-0.0.1/SolarFM/new/solarfm/shellfm/windows/view/utils/FileHandler.py create mode 100644 src/versions/solarfm-0.0.1/SolarFM/new/solarfm/shellfm/windows/view/utils/Launcher.py create mode 100644 src/versions/solarfm-0.0.1/SolarFM/new/solarfm/shellfm/windows/view/utils/Settings.py create mode 100644 src/versions/solarfm-0.0.1/SolarFM/new/solarfm/shellfm/windows/view/utils/__init__.py create mode 100644 src/versions/solarfm-0.0.1/SolarFM/new/solarfm/signal_classes/Controller.py create mode 100644 src/versions/solarfm-0.0.1/SolarFM/new/solarfm/signal_classes/Controller_Data.py create mode 100644 src/versions/solarfm-0.0.1/SolarFM/new/solarfm/signal_classes/DBusControllerMixin.py create mode 100644 src/versions/solarfm-0.0.1/SolarFM/new/solarfm/signal_classes/KeyboardSignalsMixin.py create mode 100644 src/versions/solarfm-0.0.1/SolarFM/new/solarfm/signal_classes/ShowHideMixin.py create mode 100644 src/versions/solarfm-0.0.1/SolarFM/new/solarfm/signal_classes/__init__.py create mode 100644 src/versions/solarfm-0.0.1/SolarFM/new/solarfm/signal_classes/mixins/PaneMixin.py create mode 100644 src/versions/solarfm-0.0.1/SolarFM/new/solarfm/signal_classes/mixins/TabMixin.py create mode 100644 src/versions/solarfm-0.0.1/SolarFM/new/solarfm/signal_classes/mixins/WidgetFileActionMixin.py create mode 100644 src/versions/solarfm-0.0.1/SolarFM/new/solarfm/signal_classes/mixins/WidgetMixin.py create mode 100644 src/versions/solarfm-0.0.1/SolarFM/new/solarfm/signal_classes/mixins/WindowMixin.py create mode 100644 src/versions/solarfm-0.0.1/SolarFM/new/solarfm/signal_classes/mixins/__init__.py create mode 100755 src/versions/solarfm-0.0.1/SolarFM/new/solarfm/solarfm create mode 100644 src/versions/solarfm-0.0.1/SolarFM/new/solarfm/utils/Logger.py create mode 100644 src/versions/solarfm-0.0.1/SolarFM/new/solarfm/utils/Settings.py create mode 100644 src/versions/solarfm-0.0.1/SolarFM/new/solarfm/utils/__init__.py rename src/versions/{pyfm-0.0.1/PyFM => solarfm-0.0.1/SolarFM}/old/PyFM.py (100%) rename src/{debs/pyFfm-0-0-1-x64/opt/PyFM => versions/solarfm-0.0.1/SolarFM/old}/PyFM.sh (100%) mode change 100644 => 100755 rename src/versions/{pyfm-0.0.1/PyFM => solarfm-0.0.1/SolarFM}/old/resources/PyFM.glade (100%) rename src/{debs/pyFfm-0-0-1-x64/opt/PyFM => versions/solarfm-0.0.1/SolarFM/old}/resources/icons/archive.png (100%) rename src/{debs/pyFfm-0-0-1-x64/opt/PyFM => versions/solarfm-0.0.1/SolarFM/old}/resources/icons/audio.png (100%) rename src/{debs/pyFfm-0-0-1-x64/opt/PyFM => versions/solarfm-0.0.1/SolarFM/old}/resources/icons/bin.png (100%) rename src/{debs/pyFfm-0-0-1-x64/opt/PyFM => versions/solarfm-0.0.1/SolarFM/old}/resources/icons/dir.png (100%) rename src/{debs/pyFfm-0-0-1-x64/opt/PyFM => versions/solarfm-0.0.1/SolarFM/old}/resources/icons/doc.png (100%) rename src/{debs/pyFfm-0-0-1-x64/opt/PyFM => versions/solarfm-0.0.1/SolarFM/old}/resources/icons/pdf.png (100%) rename src/{debs/pyFfm-0-0-1-x64/opt/PyFM => versions/solarfm-0.0.1/SolarFM/old}/resources/icons/presentation.png (100%) rename src/{debs/pyFfm-0-0-1-x64/opt/PyFM => versions/solarfm-0.0.1/SolarFM/old}/resources/icons/spreadsheet.png (100%) rename src/{debs/pyFfm-0-0-1-x64/opt/PyFM => versions/solarfm-0.0.1/SolarFM/old}/resources/icons/text.png (100%) rename src/{debs/pyFfm-0-0-1-x64/opt/PyFM => versions/solarfm-0.0.1/SolarFM/old}/resources/icons/video.png (100%) rename src/{debs/pyFfm-0-0-1-x64/opt/PyFM => versions/solarfm-0.0.1/SolarFM/old}/resources/icons/web.png (100%) rename src/{debs/pyFfm-0-0-1-x64/opt/PyFM => versions/solarfm-0.0.1/SolarFM/old}/resources/stylesheet.css (100%) rename src/{debs/pyFfm-0-0-1-x64/opt/PyFM => versions/solarfm-0.0.1/SolarFM/old}/utils/Dragging.py (100%) rename src/{debs/pyFfm-0-0-1-x64/opt/PyFM => versions/solarfm-0.0.1/SolarFM/old}/utils/Events.py (100%) rename src/{debs/pyFfm-0-0-1-x64/opt/PyFM => versions/solarfm-0.0.1/SolarFM/old}/utils/FileHandler.py (100%) rename src/{debs/pyFfm-0-0-1-x64/opt/PyFM => versions/solarfm-0.0.1/SolarFM/old}/utils/Grid.py (100%) rename src/{debs/pyFfm-0-0-1-x64/opt/PyFM => versions/solarfm-0.0.1/SolarFM/old}/utils/Icon.py (100%) rename src/versions/{pyfm-0.0.1/PyFM => solarfm-0.0.1/SolarFM}/old/utils/Settings.py (100%) rename src/{debs/pyFfm-0-0-1-x64/opt/PyFM => versions/solarfm-0.0.1/SolarFM/old}/utils/__init__.py (100%) rename src/versions/{pyfm-0.0.1/PyFM_exec_bin.cpp => solarfm-0.0.1/SolarFM_exec_bin.cpp} (67%) rename src/versions/{pyfm-0.0.1 => solarfm-0.0.1}/clear_pycache_dirs.sh (100%) create mode 100755 src/versions/solarfm-0.0.1/compileBin.sh rename src/versions/{pyfm-0.0.1/pyfm.desktop => solarfm-0.0.1/solarfm.desktop} (75%) delete mode 100644 user_config/pyfm/icons/archive.png delete mode 100644 user_config/pyfm/icons/audio.png delete mode 100644 user_config/pyfm/icons/bin.png delete mode 100644 user_config/pyfm/icons/dir.png delete mode 100644 user_config/pyfm/icons/doc.png delete mode 100644 user_config/pyfm/icons/pdf.png delete mode 100644 user_config/pyfm/icons/presentation.png delete mode 100644 user_config/pyfm/icons/spreadsheet.png delete mode 100644 user_config/pyfm/icons/text.png delete mode 100644 user_config/pyfm/icons/video.png delete mode 100644 user_config/pyfm/icons/web.png rename user_config/{pyfm => solarfm}/ffmpegthumbnailer (100%) rename {src/versions/pyfm-0.0.1/PyFM/old/resources => user_config/solarfm}/icons/archive.png (100%) rename {src/versions/pyfm-0.0.1/PyFM/old/resources => user_config/solarfm}/icons/audio.png (100%) rename {src/versions/pyfm-0.0.1/PyFM/old/resources => user_config/solarfm}/icons/bin.png (100%) rename {src/versions/pyfm-0.0.1/PyFM/old/resources => user_config/solarfm}/icons/dir.png (100%) rename {src/versions/pyfm-0.0.1/PyFM/old/resources => user_config/solarfm}/icons/doc.png (100%) rename {src/versions/pyfm-0.0.1/PyFM/old/resources => user_config/solarfm}/icons/pdf.png (100%) rename {src/versions/pyfm-0.0.1/PyFM/old/resources => user_config/solarfm}/icons/presentation.png (100%) rename {src/versions/pyfm-0.0.1/PyFM/old/resources => user_config/solarfm}/icons/spreadsheet.png (100%) rename {src/versions/pyfm-0.0.1/PyFM/old/resources => user_config/solarfm}/icons/text.png (100%) rename user_config/{pyfm => solarfm}/icons/trash.png (100%) rename {src/versions/pyfm-0.0.1/PyFM/old/resources => user_config/solarfm}/icons/video.png (100%) rename {src/versions/pyfm-0.0.1/PyFM/old/resources => user_config/solarfm}/icons/web.png (100%) rename user_config/{pyfm => solarfm}/settings.json (93%) 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 # 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 e6ad1ca4fab3144141d9d3aa2f801ffd562b7b7e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 20764 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%0Ar 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 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 @@ - - - - - - - - inode/directory - - - - True - False - Show Mini Webbrowser - gtk-go-down - 3 - - - False - 800 - 600 - desktop - False - center - - - - - - 256 - True - False - vertical - - - True - False - - - True - True - True - webDropDown - True - - - - False - True - 0 - - - - - True - False - select-folder - Folders - Directory Chooser - - - - False - True - 1 - - - - - True - True - edit-find-symbolic - False - False - - - True - True - 2 - - - - - False - True - 0 - - - - - True - True - in - - - True - False - - - True - True - 6 - multiple - 6 - - - - - - - True - True - 1 - - - - - - - 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 0000000000000000000000000000000000000000..53fc165187094749ddc4709298192b44977877a6 GIT binary patch literal 14416 zcmeHOU2GIp6u!G{X;ql6RN_J#J5ezO?X*Q~7UgHQE$x&PgHRNsux!~aU9;V7cBk6< z6Ke{{DpiaR@}P-{KA0Hae9#vy)_`w9VhoWOtAS8du;3pt!g|iV=agXr0evyXdy_lg zJwM;QXU^Q2-I=-b?CP%eU?3p41jR~$(z=i>RcZ@zlN3o85p#qsYDBe|1)5T*wzMFs z7UPkiGB8$Xd!cd^n|4Z;ZJ#U^APipUEQFGT)~hiE)T&L_r*HA4A} zl-MAltWa(7_^9IN4H0jM{2ANGpApBvvf;R!_$TA7BVLUuyAd^}EgOu>m0B?Jcn<(i z=9BDK13t{_X}l{P2f_I#r1Xl0bgHL$X+t_upH5{4hw6tS&GpSot$fy6B8SGf8lmEx z>bQTS2#$zx)G~TY2mj!hsWN#8GUd+ zuutg&h&rrX(8iT@*MiO9WM0M*`nNiM!bISyfC@N%GESaHg9}pE3Gi4fy@i-uWy==M$pGmP zraXG@ahmq%=@KPb=RA7v{0`f;v}4}AahV=HowCwTZ*&H926P5=26P5=26P5=26P6l zB?CWK-tvbt`eTLjV%fJ1LO46e-C*&MGx}-8grpbm`3}^@g~wo9S!+WJiiyR^ZmHf}oaz7Y0@SF{+A_6TKImF`kd4|8u7$6sDd1r&l@q&y_fV z1J2y(TIq=ax`cT){idXLvBKlxw;mhd8&cd(J z?r7o9jm}v84p49CT67+d!BiIe*?+#ot!+9=@pTo>b`?&y6;4EpH+|!bP6V7~Uk`qd z$LEpF(MO}3qg$deZ;UdZv|sYL&KyY$NEe*}odKNzodKNzodKNzodKNzodKNzoq_*r z1_JPQsUbVyHf+eI=4Ps!}?aO9Nc>f%zF1arPiNW_$rPIaY(|}6>y8#n`_!MBl znPTy6z&5~70pACl4Nn8`-6od6lj}ubC={rkU0$&l^!Prl2I`TYibcHX7H#Dr)HOo= z1$6vw>~=Ik9RVHMgb`{t!fPsTc%ouNtXgp2;=6CZ71h{R8fpgOS%iwH5!w}Ooii%| z1A>Ts?14H2_9UQaH$pE5JB{#5C94f{taPx&n#Om@6q!@~AoFBG#VkC)g8YK=DF?ot~? zdGCohN%reefbx_AaRXtPa2_GU%o<5}dyFM0x9WZqn|z;&>nyR{tU&B5vt^6*5t2FX zt>g!bLS^Cc;SH|bpD_8u%Va5YUg7pp%B5r{`o9LeuG}9$Gp&zrC-mDpT3hcm>tI## zBD1N%S-OBgn-JBcm5mqV#t8oW}mC3qEYkOwU8pve_k~w$Sqv{zhC# z&U!b#T}b!7c)m|qiQ!CsxL={0Q{xkQM8EtgEkkwC41w2M)sVCyD_VA~4< z*s_VZ8y8lxFV>rj_a|e03E;A%7_oRR7l%NR!ENX`-k;hAqh_Hm*jRD^)mcwIFRX3Z z{{Cdf^{44?%Q&BLuJAk!)4b>JB>4Q{j(J|OTm=oBv&`r9|NLEm&5ndTpILneG`xKN z4uHQ8z~?gFQtB0;y$q_C|2oD0I-Q87&)?7Q0Q+xnj&S^ZT^J`m??11V_Q(GK@G*zX z=j+BK@!z5GF-FWC^ZERJ7Wacf4PF0 zt$+Lv!GiJg_?t{y`ff5Mh$#g#TRwiQn1BY(Q9513%y&!hJQ$1LpJm3+!KV@`^D9=` zAOY_h`A5MQ=MMAv_lduU;_set{$t^B`8XZ}9qvE#`8$ot2J{xfl-r9N<};oIlb0`S zTRAr>s8cYrWgg>Muz2}(#IGYh$H^TqkL7dFKpn@A>)NnHi0}8uvApFx2&g0X858s8 Y&S+GF`CjC)y{>Dh!&&S|MxQAD0_eLY3jhEB literal 0 HcmV?d00001 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 + + + + + + + False + mouse + splashscreen + south + + + + False + vertical + 2 + + + False + end + + + gtk-cancel + True + True + True + True + + + True + True + 0 + + + + + Select + True + True + True + + + True + True + 1 + + + + + False + False + 0 + + + + + True + False + False + True + + + + False + True + 1 + + + + + + button1 + appchooser_select_btn + + + + True + False + gtk-new + + + False + center + splashscreen + center + + + + False + vertical + 2 + + + False + end + + + + + + + + + False + False + 0 + + + + + True + False + vertical + + + 500 + 26 + True + True + gtk-edit + New File/Dir Name... + + + False + True + 0 + + + + + True + False + 20 + vertical + True + + + True + False + + + True + False + 15 + Folder + + + + + + True + True + 0 + + + + + True + False + 15 + File + + + + + + True + True + 1 + + + + + False + True + 0 + + + + + True + True + File/Folder + True + + + False + True + 1 + + + + + Create + create + True + True + True + Create File/Folder... + createImage + True + + + + False + True + 2 + + + + + True + True + 1 + + + + + True + True + 1 + + + + + + + True + False + gtk-justify-center + + + + True + False + gtk-open + + + True + False + gtk-edit + 3 + + + True + False + gtk-edit + + + True + False + gtk-media-forward + + + False + True + center + splashscreen + True + center + + + + False + vertical + 2 + + + False + end + + + Skip + True + True + True + skip_img + True + + + + True + True + 0 + + + + + gtk-cancel + True + True + True + True + + + + True + True + 1 + + + + + False + False + 0 + + + + + True + False + vertical + + + True + False + + + True + False + Rename: + + + False + True + 0 + + + + + True + False + + + True + True + 1 + + + + + False + True + 0 + + + + + 500 + 26 + True + True + gtk-edit + To: + + + False + True + 1 + + + + + Rename + rename + True + True + True + rename_img + True + + + + False + True + 2 + + + + + True + True + 1 + + + + + + + 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 + False + + + True + False + _File + True + + + True + False + + + gtk-new + True + False + True + True + + + + + gtk-open + True + False + True + True + + + + + True + False + + + + + gtk-quit + True + False + True + True + + + + + + + + + + True + False + _Edit + True + + + True + False + + + gtk-cut + cut + True + False + True + True + + + + + + gtk-copy + copy + True + False + True + True + + + + + + gtk-paste + paste + True + False + True + True + + + + + + gtk-delete + delete + True + False + True + True + + + + + + + + + + True + False + _Help + True + + + True + False + + + gtk-about + True + False + True + True + + + + + + + + + + True + False + Debug + + + True + False + + + Show Errors + True + False + image1 + 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 + + + False + False + mouse + splashscreen + True + False + False + static + + + + False + vertical + 2 + + + False + end + + + + + + + + + False + False + 0 + + + + + True + False + vertical + + + True + False + vertical + + + gtk-delete + delete + True + True + True + Delete... + 20 + True + True + + + + False + True + 4 + + + + + gtk-open + open + True + True + True + Open... + True + + + + False + True + 0 + + + + + Open With + True + True + True + open_with_img + True + + + + False + True + 1 + + + + + Rename + rename + True + True + True + Rename... + rename_img2 + True + + + + False + True + 2 + + + + + Trash + trash + True + True + True + Move to Trash... + trash_img + True + + + + False + True + end + 3 + + + + + gtk-cut + cut + True + True + True + Cut... + True + True + + + + False + True + 4 + + + + + gtk-copy + copy + True + True + True + Copy... + True + True + + + + False + True + 5 + + + + + gtk-paste + paste + True + True + True + Paste... + True + True + + + + False + True + 6 + + + + + False + True + 2 + + + + + False + True + 1 + + + + + + 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 0000000000000000000000000000000000000000..1a403ae3ca0be18c3661898bdcd9bfb5dfd2b80a GIT binary patch literal 16172 zcmeIZWmH_v5-vQ0yNBSG5F7@F!7WH|cNjFdyA2M(3GO7gySpVJc#z=k?hco{$JclN zoVC9D@64LLXYcN+r>dT=?&+T06Rs#PiH<^q0ssKerKQA`Up|HZ97qT+=MoEtB>;e| z*i%*0SsCI+Vee>XW?^kg;p|~=N@415VFm!W&*i6Uxl&hG=sxqJU{cP0?uE1_nXjyR zA*M=FtL@Hq_)bYl&Q`%C;f`g)n)A{odb1V9T%s^HoBif)Ewk-A zvV>OK`$vT;(kqFhKV%(0J!wwdKWw)=TCVt7d4F0aqF-+x_ggBm!>o9iifvtPd!ILY zZd25O?%`(lyV2jZ@G?*P?3(VvrE_#tcJR^ef;u6)_i1~3Xm40bXsLO#-0x=3yBYT6 z&MwMsp~~BZzYp3jl)Zkdy*!{9yF9(Sk2#v!uCMZNKZaTA?Niff!0o5Q&~%OcJVRAj zdEBYb&GqEc>vy^Cd!yhX)SNw&UV9(CQ#m#B+-^Q=J~H$rxKFvzO*y+bLxF#0S5)tH z@7ToWTXGAk?9Rka_)4;-9`Au)w|v(17*00C2X(`?ts~bEL@_j5!sOePg|2`WO>fsJ zbe4Pg$NG6wgU1Va28^Mj=Dm&qBZbCG!mSJS9S?0Xm!yF*-BqDStEXSDuheNZ?-MHP zjdVvWstH3F3oH3UBq~Vd4`%KBo}b>ddNLB!52C~k_FT=2$6R6J+!%ix4U9YHMzG1U zgJs0llSB|Rfs2?bi{YB3;Y%(zr9?%rL|eRF0pnM(w)~*bI}wDWuT64Rns|}9j62NQ zlY)A2#%Z#vk0PydcrpzKdjmWSz2^1*obEyHnydS>Ah zOKxAh40HbEL`f;xSS06^?uYVv&O7V61^3odVXH-#WwDro!Yr@d`bPizNYI+pn;5pe zkH6%Ex?@t!vP`FElk}Pdu*4kFHGXXUd)*9x=$?b%Lb-0gyb~4r4xoh1X_JYr;NBOWAa>HpU}I1IM&RnkSbx521eG**3}T;qy21Y zI{fB3pcgUd_{`LrO5*!_`0-hL;#_wI9_IuxxQFIS4T|53b!gF#+2$dGBb`Q;tfux- zf6#TbQqE9(iB(}NUp?g*C0n2GN;>eq-eLB72Zbpsd2z@x?d`9^;TUovE`UE}jHC@VyBy9H*`*vUVk9P{UInKX``aA8>A?`9K(z`t ziMyF95f2z>$FJ*-sc*Hu#MQiFPtNCspKBH0Fkafv(QL#tj)(fGANsjjj|2oiTXsEf zU!E}?j(Vn!=7c_L_jjr@Jd;O0_04oMbky?r zd^gG=m*Dfh`kNw4pZj%1xDUY>eBhIy;|wHk)|D>4 zLctP-zH+8u&YC_Z@vobt$k%7zm8@Sc7N5FY5lU`4eWa}+Dv%9zCH4D+6O%d z_2j8d1oeAw*IAm&_})$##wlwBjRLB!SyB{#4ImYnDnPAkeNI$4iGLt#*wH4WVzo@k z4e>i)nBM`8*LB%Ak?)!>Jb*A$=~iMy8_6HVnl85iLW9ogKJz}%ce$2lxa=p&-tQF?t#f~xl{ApJlUw(8mJQc9qpdtvU+E} zO%fUpm{t#L7q7=}H=p<3XYj^M1`u0*d}BcIX4deX)T)o1nA|%5Tw5bywiv;j^47Wh z+Z2$)!TrWTwpy427b@0`w?4s7h|33+R!uf;vg;U{KmuBl2dhCFGHox|J-_LG{m4Y0h zeJlArVZbp}DLMB&x!*U;Vw^W0X%gS_vvO^lg>LZ6uJNwEiX%RROTzjbilRX3CWnYP zAwnHco<=vy#~TF1Ek(7`(HCh)L;cEgq9$q#r){()GAbAa+k*UngWkn~K8vQ3wSIb%S;%&fgUQm11wZm7jwTv;J9TKJ0vWP zDKx|<0pX0BJLZxm@=!UZV^Lylt}F2Ps)m)I5eB1Y?iA6C;b=^+?=|@clXdfsn<>KF zhbVGC>h>Qg<1xwWl^;tysZyb(eH;D4zinA5obe{-(B+N~?$`USnN;RGKZXeU1=H}b zngjK51g8tM{BpEnujAPXJ8>NDC9Y0c($Dz;;9BJqZ2*zm8Cu!EE*{mYk)#L$Y2ZF0 zn)F<7$5whr3601a7Nd$xkNN{5L4St(44$?5ZFIjAHvjlp(62PIUNy8(4FC|I{&2Wo zuVB8LvWDv*a!9V!Vq%@8mT=&+gLxdlVDCQq_sjsWJ}{KaW)! zv3K9%z@-69V9G9r35&B+#cDWD;=?p;>$!4C4_UC+(yIGB5gnGid+W}D^n`azDZHa$ zxWVlx>i!Hz59!T*h*+&-`!d`T#nfr@ZaAYFGiS7J1D4PDL1u+Y$D#FC*8rqvt!I#U zW|Ah7_nIz$a*o#iL>Uqn#}^baZ0{8EkT*P>6xtQ~V-&7$wX#Pe3{M*)h^UjBr6Rgk zs7H4*=N{6ucRH_a{fLW0-hvJc#K1uUS(MF>I>(*SDq?E`G+*6wca#Grp;$hlqWuvc zYUDpB|H5-t&3Np)mp$9nJSO0P^U_Y}B=#u$+3?7hHGjA4`uSWUF7mTg0?Aow{y4{v ztH;WtU4DJdCtmq_*v~xst#%BzWr?|o`pV~iEOdM8*Wm)nmfTD&Li%IF1gni^bvO5~ zf3Zj4b)Ts$>83|_3cyMZejkmEly-hs@+N5E*PtMSAN(-`w{X-DVx`&FD;1IZGR%~OvkcV`BeomK?9O_0r;n&oSOvI!*y za{f;dms!X%X5oCuqb`aJv5vPDsy}#`DSgV=ko&Uvsz_@_$Y9RZ83TAelz2#98Sn*W%}phfhQ}WJAV@ zWO4CelL|7Q3lGe{u>_w>PHqZmbY3Vi<~P_+6aOU5@zb9QVYLINWYx7qRvv>S^%PLX z=HyUHuvwy|OyU+@VNETSuJwL7vSdeStfYLkZ^<4bu-$MZ9kYron@d?uaify*BW`L|0z%#2{oG-O2Sym&Ke=Ha0`3se*C_t@k(dT9%9E`nvDz7*~ZS zeM*G$Lr6Du<(oSyhIVV&eaQ*yA+r42u;olzG*XqHj5j84ro^lin^3Q1&WkHV&H83C z*Yu^&e&RiiO8gl2TwA67sIBMNS+I35lXKlX(tF7S`JFzz5LS;=Vn>lJZNoIxTJhdN z=sE^oHq@Jp96Pvau%)xZ)wYOku}Fj9Lrgl1`IDc4vieD+Mns}Pn_2VslXuIPqIM=6 z@KNNWQz{?E`q^I>(%UhG^+k3IFqB~6bpI^XmuwoQ zL)9E;Lwe0{t8?;bKs7OKp{mGxbf?`FO09v;EiS}R8eYmOV+MwT?5O`< zde~V`x86^^RNs;}0Gg_LH2O#h##5#5*~mv6%&dUVKX{e5bGt&fFVut?t67$ci0sZy z8VC-gLWY#TNc<`*=mAEH`#F5ox6ht7mfbmzi|>J6uT~`H5mWvgZ>{k}`n9P~uEIyh zXF{V(R#IJ}D^N0LYXYA_y+_qLRt9~Ij~)_1NU>S)0XB=gpC`n^zuLC=ZV&b+j!bP` zPM_4?`9`w5GSyerY2OU{6UFIUTA>g1)9+xS$OU9{N-4gaawHg(eIG-$5KG(?(yygx zg|K0r0K?qMW9ArD;Bi8*H?jld+>?Kkmk~Vc$I!D-LNH_wRg&gd!Y+)Tw%tMUe3xlL z{xHp-W@>X!Fnx(o#|s_%Yj(lL_TP{*`fM79Z=TPlm2G6;IWN=dO&!nsflDq7HunpP z{lgo$22a*a6w-*36~~tO} zBBq{<<`Cv6^SmTa$n@O1*woK3!1@Aof}X(!9#5hEyZkXFPK z+BC_YrpkW3!xV?x)29rgSpwFv%8K+m-L~^CkFshd*D3w5QZpsC^ILFm{m2NcR03kb z8)Kx5Fs6ugT2OS|N`z2MI(W==Afg zT67Eq*dlhF!s-cX1ah2477IEE_98xsKy2Ex0*nbhRGojlY_PyQ@AS_K48j8*JWe(St z8sbST3bdJjzkAf@IK1`dOTZ?%g=RI^gHZ^syiMe z$>&;*Dhw?KskFtYZSupQf-C`y0RWsGaCu(sJ62FgkAH5vg~V=-^qSi4G0Hm6Aq9t} z=+TA!-N`OaTkA3hZW&TTb}2N3Ab#(#@nv>tE*Q_Oze)6YI^RveCc8*!^0b^VD3$}S%%bat?--oqZq^2WHBboeo44-+uHp*O&k_@ z-z;wNiaK@|=rv&ihUK^nWYl#aSQ?4kOYO>sRc(9{SJOb%AmaW`FFyXz%Jtc|eual= zPsbb&&9oy-x_8Pg048hxjQ#_1+*GTsQBYrYp|XLmn~79!*VpHZcgT+)eqDU@)f#a~ zJ}><#G<{!LAZNFwoswUD8*-K#7Ph% z+jq3oYUWTE4iw5~chB#B4>!$$I7WJ8K33&7O2c7?C3mIJ*S>Yy#3Rw1>O@j_g-+kN zCmjvgcA`10iJybVyPfkS2qOPM8qQq|+vlV?m3;Lp=<)-YEM!@s zteD=Fn{%MpycF)as7tWIcQMB2lq@3iz~o$&(*lR(ggnuNpay=b%zVrqTZf3wYqgD& z<@EQqCfuZmLSbSAR~dN%u&+!jU(J1Bc9xf#9EpFwNxFv`?XYr+rYk@BR~CY|fTdp% z%7s&4(5c3@(gHoR(H{B?S={a-l{2#d$xOGUeWNa8*_$rsB&zNK_yf6&4}A3+M4jn9 zftbgMPjd5@hJBk6cwy0$Uo40SvEmWEE(^7xiKRl8l%-qcnI-vYYB5X&p1fIkK|k8> z-##T{F2+v{HZW^O2KfeEyn9t#|D1$xsl3TAS+Q7BaKSjNg*zmVLzS=JomV@I6`naq zmG-`F4SSE3!{EdYMSVGkd36aYpV zFcWBOphM~Co~X`(x{xgy6Nrg_#Kivj@tU`~XS)ouw=9x)#4r)2RaMJ<vfVM(zrB-V4n6Dgm_wDje^Xx!v##W`*rk!YMIu+BuVoRYIhFg z?9K8U723JF;q0$q9O_2}pr=`=6f$ncHDiz-f}Q&KiUNdiv}&)zA_nzNhMe<@`R8E) z^@u(X!TW%|R3j@GwE0LcdSt=E@KbF+I9jcFrF7&HI9>ltvW$AP)|Mg_3>>WvHAHqN zG8AGUmk8!lJ)X1Ul}O0=PDuj|%er{SS5x(5)QD64R}#xBOW=nq+X$cVf<#SLJf#Oo zY-&6uI3LxDlzUa=l~STE1RW+3D>bjAV?iS)t(7a)L*U|KyGlaBKw=2h&|wef*6A(F z!w0fj0-GfKSLSnC=*~!9% zH+sv%T(?p+n8$(Ch1i|9XK12@RZsJEdIB(D|K0KSJMypsXH8%qu5IlFryy=)S`n@k zw%9iqHO*Lz@@CZg;(W+8+jDu6nd9pt{nJW})jcwLkrF21>;?hNgD)G&OYDq70|C_u z=g;&P6%KPoq-oiSL2?sB-3o8O{%Dc-1Of98?5s)6H%?b(`A$a&n9)m?<<3|&h`OX& zQ&BG9_}>T!FuFhLrDQ**l_19ZtBt?+TmjG66X&zp;shVpTP*&H;3NQsMGYPsMk#ml zq|GRyc7S+NG^s!?aO+0E8LsS*CM;Ge;=$u`ON~1dYSMxk zEz+69#)L)El>C(ScdV_u$Abll8<%q zhMdLDKPa4o^w5R| zC8-0Z0y5J$KsZ7Tx}n5v$;jk7LXJGI&rF|MSedP0#A@8f83>qLP*_mm%@33yHwfX9 zFz-*#*rzz=C*zA$`KaJy8p~KfIv1JzdHJ{x9NoIKz5Q50|HA@NT>Rc``?dIoS?a`y z#iQRy?p8y@j3_?*!-YQ0#}m_N@4E}gMw5=PXsN$@al)aReVFS$HrQRoO8rv4f{I?^ zOEQj_!=czjQkk`m?)a(&-M+#=miCPIdz5ARsx^kfYW4&_&;j{X@#IehPjUA9+ncn} z*-zRb3|mfN?&zep<{r4tooaY+<}1c z6W#*z^#mu?hnpmW)*T~rB`mK#X|s_s;iZZZlgukVG+$W0Iwp?Hu1qiygV^V{4$?EV z1#uYqV$Tj#hjS75lN5H5rwdecVYe|Q0y&z;^^%7x>6Pa3zhgP+?FThle<^*repC?E z#E@k7*faTFJTC1^IyF^Drb3#D;6Jou|42{H)Z!HV;j4WJh3KXPAck|o0+7jR%@EAf zaa$;J&T)SsboA(N*^!jH|FjT{slzVm)1lX5pkO^FVu6~VzSHbH$HSCJNA(^lBrUUi z4n1`-^7C#4%UN>WIdAK`V>4e_EeR%NBWCl~RpK2j%?|lHBaAq~lqMOHhI1OzElOvhDhYwCx z2h2z<&mjN})ZTO4oalITo&S)OGe1(zRl#n-qs06weGC%mr%PVEg4UEP0-;J}TzCFA z^;jlK4h!abK?e%0NgYDd;b3!sIF5d}OflC_^GdNE+ox@Vh0`cQHRC@n*iyuiKBF9=R{pAjEqLg?V_t3A!N_U}^(0$%qlr zTis+#EML})DcDt|fa#lS$t4X_5FQg!5&!Gj{5pu1umUv#;!%XGQ1LX#C@ z_x9n(Puj5Vcp1+6&KYp1fbPwfGT_b}qNYN+w`r0$=$Qn{k#eVYE4WUs7U88BGAqi^kqWHjW%$m47)oC7eqWeX7E8;dh<-PjuXE1T@anccDf2=U z+6NFLf%x5ek@kSj4zujzfqdWku=M?7=w1n>O(phP*{|(hhaY#t(y&20Em8WyB@*}> zJPOe1kMim?@Y==SXn^?U9&7GJ@-lmRerC=MM^rT;t(;QSHVhmQE&Pa;A?mYhBPNOL4_kfp;p zHX<=#-{8uBn*M>xrom4(=rT2Yrhz@W>t;KKd((Ke6@h`SJ_R-Kapsn#R8oP@t1aKP z!>-L9aXR2su4)E`)Wx|g?LeHW&m~X z^7B-JPU(9VmPZsN`83o}J;W>bXApk0f_KNA`%u&)IPK`Tgt7&AG6_U0ibXe6!&vDT zrOtjNkA&atvi!nWJxGQ-l;3q;?x2b38?_AZ{^s=&H49Jt#^pwp*j!m$+l_8Wnd6O; z*Y*;7=|W%;kVz_nLWHK7usm6_F>x-xmH1<^QzI`>Cu*V zkv*rJoHPCMzLkf@lWAF^6w>L%l9uo+Sp1adwR9EP?p<X&d3nX3YG_>(ss&mo>vtZRdcmFlD?~O(<6}lg~(IldBhnRRhBI~Lj7{NrOLO!A~ zHf>QO!K#%ZuOoEC#&4dP4gJz=m87aB=sw?80ut(WI4=D?Gmf}$sX4R(T{s0{@~ebB zda{E261Ib(H-p~{qBWjea?1P8pBgSn(#;Y&XxjuUi~24lpgp9ZhVx-wg|z7J2J3HY zy-;@BpYIP1Y}1}cel9O_K4&I(E_3~LeixB<_4FQPwQV#$=?%CAiB`EhqGV98DC1={ zlvhSijipLzP&oB5OQ=7CY0>bB`xFKHxCVDE6#xJRTZoA%N{fm8fV^Z%#{|4Ut6PX!Y6sqECV<3?C-76 zBL$FC*xJf}Vpd*zP7{qFz>K~>Jz82nBBhv=k*x&}0OT5nRjHWGY8Z@GUJ2|-B4I~< zA7V43rb8Y*b-^SzI5y({dB2eTRbyBS?IgU9eF$W;=CW2SH^0)RKw>7LMJ-4}#$|S|v8s!`*9^=lN%9UK)uybDl_8Dj^sR&Hl=I+gHs;cdMjoqt6sx7rouHIo z$;nOfPFTo{hS)gbv_*!n)W5^VOFkP>O58hOJ`4A<#C@QgK_KAWQVKz6UPXKkTwc_^ z4R_$CBR~WjkZcXk~0RGC()6mhrK-!r{V6M;8&si2% z*RV;1j`i(aU9K;?3y3UUb`faG$%2jTY?vV?c1EVm?l$%>y9@vTej#^zh_RKaGlh|< zxrMC&)p2Vl6@`U~0F?%(97xVy%=Dv$l&7Pqil@A)v8R3NbrJQwk1d4rUOOgu8_+88gSy zH-8}hh9Pe1Wb9~R?`&abOYsL2Vr1vyEI>u|Qcv*@|7`5#-2XPUie^fhuE{Q zGJ{xbY*_wX!^v5~^#$bb4*g#>oK#;nW3VWjI@!568kbA#&5Nkhi&fVDFexc5r}$5eKNOf-*x3K2^+NW4NIF}X{fn&s@a<2{U*Y__ zBQNUz#QhKHf5iTm@Qak399Z1W*yWFV(&7SCf9!)z?2Ii;z<*tu8nJRhcukC$c-Xig zOdJp%2or=A0%0-L*-ifqO4`=R8DeW}`UmO-oY~?9hm)O?8^p@Z&GZ6g z#>8O)0Wlfz@|rTSavDQ8dD+Y$+&n!02BF|+@sgDg>wov^50uFZ6ptAQ!p>^S%f!J6 zGG*dmGXgR3@Nk$iadGi-aC5V4kX{6H``R zQwS&bf1*3tnK`>b98E>cUm|^p<|TptiiU#rFDmK&Q`+sL=^vgzY^+SItW0cds;ul_ zHcl`bFB6Ch3<6QH{5@cnKfU@N5%aVBe>ma)OW@yzffv2MmAwovFQXO9KZmQobM}YE z|A()?x5fWM3op?BGxA^Y`#-w=N7sMFz<(wDzwG)SUH=sW|CR9nvg`jhx={YL;4!s* znFYDMEN4c*>vAtkAq1ljlHz|YiU7A_t$r_8$o5iNP5=NZ?waR?%hMIWqeq8p`dDiZFK1Ce9!|c2| zMciHMkH8L_BNv`*>6i#@z3YJ=)jR3odd6MK*<&X`2;{w=l@!`Gi=#=~9IiC*%p|)# z6uLQC?G1fWwUU#e!@^EbR+RC_6N&4bAtl2K2n=)@#)_NPjp72mQm@GBwE|$7HG1w& zR07-&?t1`RE_WMtPt)C`e6IYhO-)sK2_e%&V0!cN+{A#1IapB}Vc}N*ePgPcOMB%2 z=-nfy{o2Dm6_$g&JOwqyzLnMXXtt2whDwb0-IN;%bkuM4bf-UA{^9W~YtvYXgz^(r zpAsDD?LJD-C-heUagM7IVY0w%XsMRIVUgCv&2u0!3Z zT8p1gXCx?w3?U%)vP*UyRQ!RZ_MhMM!5(`ZxP=P*uD^2A7u48Sluu!N?&7{-GBYs= zwAR(t;eEqit2<8V0B!-uS?T-^=ZQypCIdobJXRZROy=ysrXxdRc} zP7lx*^x5m^4aHbo+`qJJjafsI;TH8k=hoKXID#$0CFKX?Dtz<~H1|KF2!rJfP(gVM zqYOihrUZu#Ur1Vq^mZc5dP5|PQT?iN;c*RhboqXeVcGLwrTe<25x(nK=z$MW{bdvG zM_-74opOF}sdb05WxiXX;<<=>1_qQq!vj!+0L-@ltihqshoQky`8z?CQK%+v$jh-M zun*@O&&JmM?lBdB?xqZcm+CD150agQ6?!dKl^W{mOT_)GYqX;A?%S^|NO{^;v>P*X z$nl1Tk$hljFHoaACbvY346bd1EXQ=V3hAe@>y&pe5qwnexR*}5zZ%>W(qucbv*u)* zUX!&QtyJqzi{a2n#piAI>Hdrw@7th}{WNw4JRGVHzof>^IJ>HxS29vI`DmW_gvtJf z#Sz4gq3?F``(vF43sfi@jPh`5P9|vI8^!JG^gQ^Kf7=W%t~4p5_RXnInIMgW+e~?M zzTo?m+oZ1DgBUp4+b8Oy~Xw zp*w%_=hWNg;G|63&xwAWGBKsTJT#g*twEK~^E25zE_*_LSGeTQdGph7`hJ_Tc%>H= z<0qwttHQ*uB*ZTCh*7wi%|FoOm)m`dP6|A@1Il*H+=Tv^(YDfcKVQ=`AI*pWq}Tv8 zH8uN2GWiaN8y7NcZEe?XPS*)+qUpI1lv|PxU2=;24tQ8g$@TSZ&EMtI$-x-%tuhED zni3whonx42k$bS#@GZ4%4a!$7kCH@eIFMj|q3y}q)FVLK@};nPT(u9oz4ah}r*+nC zxKe>6Jh#saA%poX4VcjXv~S1raP^wxyVq4Lwg#8(C@mGyxl1j-e206@2rJ_}B*|0- z5StgBI)e;-)3g0_#Cd^1-qj&t$O>BrlR%W62!rQRfh-k%+Gp@U>3bF^b3%g%!TuS zi@9E+(e;owjj)5YIXEM{!Z0F>c~-y?On91(d&kGr0le48&nJ>5CE`-zjy#CUD5%K2 zT_U(3j;_+ut_38`G+6w5{hy|0bM9@^6bE2uxIzV?nEd?wVgQfN9fvC^80Mp;dfVVe zX@{%aO5B+*4?7>@1)R95dQ`kZCNg*%Wt)Tp8=rjzCQsI_T^$v;LAksIsm~isvCj^>qyCK(w0%i3P)up~)=ggV5Sa z23{=yRFW~KTi~$78n!kt@RN|50R=dXV%UL*rkqnbPjR5HCF(oR0=dRSFFXs3$Ea%a zqhmzp?}a%G4VdXc?A7enEwDleYZ{*fAf2~3kwL>y!axc1oO8h3G*X}#DjtAYhvEa& z7v|zs6$o+Ru0x>ScoHs1#6JK}4CdRrim#7~H^TRnSg!!hkiNl5C1`(z0y?zXb!wi7 zavY(jf%=93DKktM1vCO1&qomsWkgU$e_O)j&7C;ok1s7VtRi`i8{$k5OB^AaJ_jcY zQ7=Tup*>SeK-;0lplX;FRDy+3gBAf20BGetCQuj@L;{4aC%-S2v_ktJKJoeFCiN#? zt&<95yn(f}ykf-nw;w$L3Ch7?P{4&UJZrDzMb&;r%rb_rZ6z>G8S`z}L zl|_IdU1cz*-GMQTy=-%zX2UDq&CHnwo1ape#8TV8QL!rc8GujmS$GGjQ*Y**Dr2!1 zHXyMeXJyzp{=^mrwTzYE;*luC6gB_y0umd>6--DWI{kst+H!S&DpeW^cU-K@O#^5x z2>ivoIlpO)q?(98jF5tygq+mghfRcwQa_@GgQYKv;guV$r z(vEb}GnmlMg8}Ha)rAvZv)qt$5ux3|vU38ordd*Ev9YP816X|RgnT!)+M8U-!hX_& zCWP^BtMUWAv^i_1p*0xKclj*qzx5;?=Hspv;JZ%OznyN{@dsw#?+8E(CZ2xMD?aF| z`QfNSS1*6DwE3rF8k_+L)@vBk1w~Zw+DlE3TcMO_MsUyi=q9~+u0(ZRJ@6?C18zKn}rZS zDq*P8#cSxZv3`^l+)%p?;y}^1@|OVyZeFhx`#rIH@6SK`N@%PB9%gPEtInPb;EFDu z^t#2O?11m}Q(BBoA|15%cddXw+bm~Muy>`b!e%cF3ZQYokqThzme1zvEd+5>Bgw;Q z6}uT=*@8Wv>eHPnnr8FX;&)f_6k5%nXeL&mGb7x*BDfqZEAt}wkt~7MX1ilSAodrY zklJ4Svk?ErY5xF!QlCg_)>T(>#Fa6BIP;0@0a{EF)7CL7l2lkvx~iJ=t_AEd8yke& z6GmaB$Q)HnDwv<@FJCR(K2#h++=$rKaCr_Sfjo8{b#>D33~e^LfAb<<6v-KDaFS(m z2l})Yt0^nc2e&$8wu-j?m~+eA9)fc6ChUm?btfI>2&HuJEs&#S@DO5;Q-mASGS7vn zz>3@YH!{Pb3JB?P=oeaP6bw#OkYd@M^=xdIF|c%!0@MMlymixt5Y6hZ`qSwhylI!7 z^5R?o#+Urij9kvA+z2iVffolrk=Yg|`0|<&maBUh(h2ZSi7tt zWx=Li9%cYpr7||J&hZH3+xN)Agh1$svQ6j{^VowX4BmSTm3JRhnbRdZ*7~?2C@4|O z0Mnd3UgTY2^<6c=^=3b*Pi=q%QSVC`(Y8s}ep7NsW;8l@o}^V0@H}-Zj(%C z=2qeJBDS|E-&lr&CrDQVFSvEd-_Fg2%Ued>4q|97o=AUSL6~SN)8=FR_ysDFaF-7< zMi;(t?a7&EjPyr(BZnvZ13IS!-}52$R6@;*ap6!Kchb(sd0<8KLX2QCQ1`_SBw@FgQY!aFDwDUTu_n zL({RIn-3U%*PwCdC(Ssn47G4a-Rh^NH{CV9nV^P^^bac=MHANFC>?`dq|Cp`@1T7vIJIoS$X zuBzt8)U@*MfH0O-g9a*w(S$2!*n{c1taI^1TEFbB{%AUR1xO?hlL6q7;PKMlM8~DY z3iC)(*M9e7Mp{EUK$;%cticWF5{XJ?KRKiKUPABEj_4s0S28g}ppg6Z1=MV8ru-?k z2?oO3KXMz7fj|M<)#FcTNNYp@N?_12MUpi%0*=p~VATNO{9*XeL&(=S`Xn|h5(&P1 znGU7DLJqFSpJoXgAw#I2CYDg*u|mhjb}gkW^MEx8Rcr%FVW3bH2dOeC98;u_+zqrD z&QKR_KXa&Q3p z-Bz3KAW;kfISihaD`Lv+?Q@E~O03QfRBX6cBSoCf%D|~?rzE2FTn1!SSP&kqU{=19 z1vhrQ0%-jJFhu}Gx6R(6oc4@r^n8+G|8^vorcT~Z7QCpId5T+s!KcRQ>A^4GcYLj; zn$1=mYB};+iNOagdLDio@!JSa(lr-XNdJJnvCY=wzE?m*|3wHj}6Blx7lKs5WpT6+6ypxFcd_CssQ8x1S2R=m{`Ms n^ZsX4@&?8bxaVgAzXxusenRbOyd(5KKgCE($ctBq8V39yEL=TL literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..83974e113b0b1bb8b49937aed6fbbe6d86ec638b GIT binary patch literal 40783 zcmXV1WmFtp)0|!0-3d;B1b250E(rmGyOZF)xH|-Qf?IHxpuycWxVy9P@x15zwR85& z&g|6fuG`&J6Q-ghgN96u3;+O{oUEi80D#^OK>#BB+sC&b#byAIjV~uDrs1}9tczfP zC$;u^B7YVY8;GmMkV}8)Zr)t0JNZ;u;(F_^%NT-(39Bk9)(7Uc*~oNt5jqnbzH={j zb&;|XN1`l<3&a%n@O?V5$xO{$y~AEhaNDgiyi;6WO@+R`n3}zOGz^rs2<&L2d3Q)c zn2SZ6DV(3l(AkZ(lr;O(O!46Nuh1maxEoXAW?J0cuY1bz@=>r+d<3HIeJRWkWY*o{qEL(1$Mnmx6UydG1-Ji+T0n%U07^|dS1Um&?N z1i8z6WfQk{c5H0ypF%B{7knY72Qc#MxoTX$YM^xX)R!htE1IEp}MLprQoJ1NnaT2!kBPZn#GY8{y5*c@m^Knc=80C&o z*v)Ey4S3w$v*Y7lzun!nz}`SPjFs1OrDuW(S8O2BkNwPZ^H0#>I@i2zy^x?_n}ySo zMPNu!&(_5P;i!pOLP=8ofQhE3uy;YXIMfyt(CY{1sM|p{dxbp@Rp_X4iolu4f0!!u zGHma9-!t_7DNVtiy&lqRijKLk{wSi6jz2y)A~bdP@9zE&A%SKPo~4^|>wG{?>UvJPU21bvYpb=L zv^2kw(Lc;Q)=mY>GrLGzSr>`C>5(y3VQ-CLVpQheOMY4QD?k|^;ry=_{X9@82~jEw zMj_z77{#a_dt`Y%%rp$%ovUqsAE2Ue>NfKdM_bAy)T0J=GKghFTc!gK9*5aDLn=}v z^&={!R`Z#A7CqUAheo(kpAz<+bV+ieYx3agTDnVFraq?sy20JJB(bKNTfZML` z!%p%qwDdVVZZ0lcvP^z{K?Xi7O3GaBFdO#VzrEN!&-fIvA{af#WL(PTN|_fGx6Mbg zoIPs9pq>?Jof&%_B4{Op-1ZvF2un+6Q+`&J{3m%R4@8t3u5je}r}DF_@cUZET%E99c!=P zmi!7g)EFZ-07>GaH99De~90_bZgEFeRW-0f#X=qpkd9I3osdAt+Y$l{Su8~%+S(71} zkf?`pfll9*mx{%{)q}wh%Uo7^QklmBQE@12UMD*n~zA z%Y|5KM^Hx(`t~e8Td-~}7cA|X!|s#k*lUikC^fVys$-?VF!Ea5jr$X4_LE8Zpw~Jh z!?v`xPEJhZka`Z0`54f0eawlzas|T%a{&{jo!!qD$XA`*XTPN0>1H;Xnwn8EM;E_P zzTaBX@RArRo6?=~hIP`y=(#5IP0{cC81r|u{BfNBhL>c`kjby0lWn&g)*{tj&K)1Y z4NdffYpg`6Y|uafl%=HrKTVXKNy!=w4&EFEAcpLDM9a8fCH2jtMPHrwXpUzyLLTn1 z*nWU*g9Tz21b>-M`rU2tK}mPRt;@{Z9K{E!%hO+XE55C4OcgN))d+ruC;m)HVF}xMVfuXBL_82U@t?+}syA7mU6MG89xoI`R{Ya|>N#FqrP);Xq<| zd!i+27Ix+*;ck68xP?U&0cmN-bp|;_&4ze3&PCOP9l_Sv@wT=EtkX#(lRX>>97Sw+ zfsaK-{C?jPg^fM2jF3XpZlCsOMJf)LyCRrn(vdLhYz5QGQ>9>FU>4?Q0^4uU+AR+( z)py5rzxMk5yc+@A@Fg%1_Tb`kOro^3G=3bPZ_PjDqzy88H61lw z8t7kpjK1RuACg`Fu1FQ4WHvh3BSb=SpfMgZQOn??JsJ!KA-u7?g4h_00i(;c_Ks=H z?eY{F-aq_)y=-MxKbjV245ru_)xm@d#*0XpWy@*mx@BzF<1ZXo>S>ch zD0k`Y`N|Hjrc_PzQhKeaxPh9b93c_~-2m0m1ECLSMsjCvcAwn%M~8tFEhPu07V=R1 ziMgfV!zFD=8L3srAvuxG_TRs;%rl*TJ|Ons!3Mtr&Rt%$%zx$8agJWi3HY*T*i=Lv z5FKphtE*vQ;}5a&C@@s{L4L|i2^PNZdIUIc*6pqw#{|$3ZCr$TU<}xpxpridRQ2s+ zQim*%`$cw{;rTIabd1=0J7s)l+pN;4?hM*|Y97cwptlLE?ml;$-r^km7KzGJZ^X+B zi1XB1Qo`HYUR(Qd#yDEIV+0<&w1D;zlh6*x`Q*EFN=NpJA#8EecD1HM8>fmTWilqh z|3O=GM~tm8MD>B|;B1qoLk0#8gZ8gwNY3&MS#9aK(O0wR?Av=1Q$c(UR&jlHLS0r} z#77T}JyroyzL1JuG|TE43uzLHSGN;`KLQhkI53GB zMYNqBR;grlbv)?DZ6bU(hsgvB57jiyztBW^klMhO;~;q5U>J<)Yl`%smKK#pbBvZ2 z&!I6bkG0-84DFrGN+i^ezy|(vajD4fr`P8{juHFI-0w?EJ z7TOR)Fl3lV^WnRiKIb?_z=erYUy&kQ*+;YkyKT!(#@KTeZ9jz_Ek(B!E$}kMq8Z|G zz~6wq`@C36y{l{Xhw4)ahirl#^vH5M+|3m_0Je zeuatYB#K>wPg*^P3!Le$l>mVUSWw^-`wax!kwe+FXn7$&VVH_bE5#KQMua-G93G!H z(ZN0NfQff^3IvVI?$HT{4C%-jFmPKV=*rfK&(yr2MOQe^f6s?Xm08+p-GA(_ zAOTljG6;(_Cv2XQLd<&7l|;KCYKi_uvGvx#Itd<~#g=T)3Di$%aWzJQYE^qD<_9xA zQw{Wti=1e60nziUaJSq>%FwK}k&%%erIyVi(;gz&U@YM4)cW~jV?%?-%N?{)J})mX zyx+u(il2X}W|2=Od-M4!Mdo8v)>3T>`OJ0J8c|!?c-a+azNZ1$57kZ@NVD(GSTObi z)de;x9j5_wTb8!|9R)Re#&f^wA|%XihS^$CKpQz3j1j0 z&hmL4mP$GRGXTtVaB!>ec}-(^F-Y*Ikp0fzjg(b+OQgri_(#T2i=Fm;H3h$=C2i*~ z8#VHkAM7kGtY|*7EEBWT?M(qnc8KOkXXjNT0<}aCl zs3$&hwap1tqXZsDiyo2jUWvK@U^2kMAg3O4NQ9MjqYu=FTU=f)sbF(Gbv5?5`TX-I z=ffa%jE21S5iB}1yxR~AWL%3d=smjaJV|kJ-H>qfn*YKJn2VI$HC_ z$Vd7b%`r-_!A9F@HG4%2jNT_2D_eIBHm1ahM(&y;Y$;cJ%o}-q31Dr>niF2wG!P49 z-r~lg;GMAaTo8@7xA$RmcZ-m7yK#Rc-!p;8Q#N~F-Ax+CQ1lfgjNc{z zMDDG}zGQp7oT=qKnwv6|`YJ^3bQ8$>E^V4K!(OWFba_0X>3+SeX6#7;$UZ$zk$Frv znA5Us2>>U*lSyfLDs&fSe0CdkVK7mA^x$H>Fj)66IYTejP%?tc`6-s?aR+~LB7Dx8 zeSaS$_{}@188!4KE&hu-BE2v81SQ9>N1lymws40d2a3p=x(rJNrf6~5T>apl`7X1#q(WNL+;tHz`Nc@h+&pb%Vq)W$qMw8im)jsnlm@!4dpj=m?&PF# zXK#;^QWz_10#n^~FZqsGNwoi9pzNh^Ap5F?#C`Xgk15(T{KCR6LF4cE*wo~E(8Ihj zdt;V-$$cAjtf>bbU5>XjBpn|2=XY|WCAu86;D;DfJXUub;i`9oKQG4I&cupXt$5Ek zfE^8(p2#n3Ri7h_Jy`Z1GZLmu?5GuuW?a&7R9WnKAKM~JHsFqMVULx>4RU+|rV6Ho zdTRnBW8>s3=Uw-!Fp(}+xbpA3P8|?K5Ky=0J3V9{ZgqFOn3$SMl$BLk^77L|Sy~ThqEXIcNP9QKH&Boj=tf6xFrbNK@w??qVVI$pt(NluOi-JhwNN97g{&BmD`k_ZyOHg;B9I7|bvsQ%8 zaBcK{YYk3DyVR56W{vj_c?8t&rBWBv@!~TxKX0sTYisY~a%)JJeSei%h~LFbAWTEy zPz-|T-Ik3#y`EIeH%Nah?VQpwVNTL2L0Ii?`~qF1?F_j@V3l{J)%{aOOxqXA-q+ja zQCXDgq$|cf@jO8m!vs_wc%U|0;(@Z{$fEmoM*Ts^jMb zcQQf*9XS+-QE5~f_)%~R(G960yI1ta5+`PT-r=?3o_{TbS$z)&AIUM!`yyu5nb1dwub$tKoZ z)}HK{PE5^VMBgdODY10UPM4!^5QQb(>yv^8J`vt9lmTzeKL4|M534SZrH!amcGer>C&+$x7us9U3}iYP$_fUv0?j?>vi>+%ehm<0|A?Mr&Dgx}zk(w6>?5M1nTP(WYP{|f zMm0MLtk=xbCa?+Z#_L;Y$Fk#o(04HYT)d`QUbZ2ZAnCA7y1d-F2xu5Jsvy>$0v?h_{(%KXH8|I^cpI`9ryiVVH)vawxvkU7UP&Y$0R%nTri+ z$jAHXY^EJdm+3S`$pwQ>3#8n>dSftt)_84bhI0>+{jX{kXEe*lpGzTsGRW{_-16kK zr!>xwNa>?+o=nMIB7FkcLJA&M15h2WS9Ogp|^{I z@cSJ*CkH=H)$Y|nBup%Wa@n~w?HYl>{cDM!cF)EyL09t9n=|K@24}<1)6-nSC^Iyy z#K>^4(Papuj*}fAXz9=^O*F;g$7;uUrQZJDRmYh+a`xrmPJJN9cj;)7j*Pi{S-p|= zBi%;mAK zkBIdOZ5sqXE9EtaI_@InC%KvKX6;8w=zY4JE*0@s{D(<{5;#~c>g?=n|HW)Bal3a? zfI+0|4go+#*vM-=TOfOvU;JY1h=qe^48NVDd9+A%lNPz_hgZ6szxWg!+!ZdLQVD1o z&G&M&8z6w0f|kmuRYq-#?AB5!*s8v=74;n9=wp6rA62a zNiyTEb9Kontx;n`;vecC&*a=>^upi*z`!uMc0*HszOkL5DrG!&!duPQ!eKqyQU4H} zzD4&S{SAWY;yp=PMe35Xla@+OJz{@o!8b_yb(Zc+%pHdw zZj?%t_~nlcy1%=XNPEDZtK1-lsptm=2cyv^q;CVufcO*T({jshn;}XsyTEx2tV~?krEEc)hmPQS! zjb=9V$1UIDJ~tYqUAZ=}WHRE|eQeWw8Z1c?8R=7v@hkHzGdRUIIT^zEy_|A+ny9dr zZzrQbWRI|p4@oPki=Q9`gBzHl>1<#gd`yeuKDN=aRr7NO!cSMP>CZh;-E1LdUh493 zDdx8yUY+Y)4em}?Rq8h0UG1*0iIJ??&!h6v0OuRwPxy_lb>kmD5Qh}d$lo-c{NOn8 zu7I(P^C>YPJ%04^gO+2oyBC^y^zzWN9n0U~c5V(-4lM%#h87;5vk6to592rVNNI!< z-rsx2nj}QA_l(}Y^*1bE*%ca(w=p(syx^@&J{_{p9(RfTQfoW;RUHx!J916sJ&nyM z5&(}4tCr$mq<6cd6!EiMCYDP*r5d70v7*LueJCx!^c(=W~HUx`0mVS9evE*eahSi2qihI?9++2 zQ?#23k~zrVx(4X7`(wO5V(+Q?4^R9_g%F6z{4L6@=sv$SDF>CY@?dx8S^Ux!K zn>nfN6G>mWHwX+E+N%)^wtX>TgoE88W%p&r=w+rQJNHm@gs^#9ph@Rl1bx$4{<$*0 z93tdt^}DsShR0E1ce{+a^HvACbl!*#xUPH7yTG0F@9e#msWyyvQ#CAH)V6q*%EDly zU>FXf0SQOKvg4~UenOUT#k)QJAc9(Oy-soIGA*ybi8M#UBBJ^FllCz+K+saS6&jBA zF3m(lEz(2_7ON&3=;C`Sr_s)fIQA&x{yUG4eE5K!ZtsY=#EJWu_-N)YxraiUaeboh zr!eDS0E^g`I6YY=l27v;=*@DiYc$rBg2SzF8(7go>EF^Ru*U<)e4HNs-QM21ZwQ>*;QJc3gg3@U1WI(+5s(IXcL$B48a(e@Bg|J!a%Z#lvqt2?^OgL^?? ztdBG(Ty#;5J$}or4rwKjOBtdI_0bNa3duJ1w~;p@_{VDjpD1}#uh|6D_a}rui3?Nz&^KvWl0Tj!Ssbj`Fa4%l`~j;S?tPbi(gB->rn9n3 z@8Bl=)1(uduMj-YU&B~D1cq4T$LI>=3_sp$@SZi{lpICI0LNv2090K~SG1`rj9K3r z`H-8No4m%V@vM)<1!Kt9w|QUK;68l#;4xrgqR?_#W9|A+C&wNQ__^BY*6MG!b%}zE zNkVXBP-T@bdi)d`Dl4^v!6Ps;F&Rmb(WbG15U+RK6A<+JPLz#BGi4Rc!jwI7OwN66 ztSH=J-ngn;ckuJn6-8H1-0SwpU`=%jMgoMHR-m4a6)mNZ6oBAeHCUgU1=Iek@L^jr z-i|;~AxwMky=+GOs|2(E^yG}K^$(|x%{X0tyCNIh&~UEBUjeiAA5V=&$X*|T^T90) zCc86>OrG!0G&C`vzR-AvR+Ac@4xY;fCX9Qldt2Wy{6hEtK;c0}Mwo&e@AF=Of6Qh~ zOfFW5qbcB%{h5hf+C(>pxlWC|{_5#J$h69*x4P%=z+GIEr|k|bq)V|z%UsDUkm1^t zjI!7Rz7%D~CqGtlJ}&4C!sun#6tc0-lDr-dA`^$qS%|*+P)s7P6lbS^pKgthZn58r z2bN~Lel#&2IDd3Nxyn7yVLVHS77h+>Gc`3e+ysMwUDwSGHjgUb)(14L7oVvo{YVQu z`3OYTQ$s>EoP>3{{9Md4MY;AAfwi!~oxQ5*sS=mf)i;~8o`4&-UW@p1XeAqB!h4&8* zK1+lrCnqPsi6<~;`#7s8`sKiMwPUQ+3JEj2d=nF+{I|7bGeVAyExjxHIU6#Gl$NF( zM(4-Of}+nWK*%cEm4b>*8p+F+Ccq}3E;TZNO~;nup<8E5l!vc-I-c7v7Jr;}Y;h!@ zu`%negS3g8A2-~zZIk8c7_RL01im)6k%FYsVkd2`QR-l*zc1eLG?T}w&B?FT1;PG~ z$*Heb1VJ(_Y^~SXj{2AVt&m~gvfuZ|&cB9+IrqMV_r^2DxIv2&qk=UaEZp2*pkw5| zB<~SJZD^MmjMJBcbn}KA0=r#fx{xdyF=6K#0zAfN9KLY$YVBgsk%w0$b|)O$mrj5N zUtLso+~_DY6Y6shz72^Dmic`n>dxHvrqNpSu7yiW=tf2-H0(vS^>ZXlk317b?q#$% zvQRe%Sr8NM4|I4d(lagkH#ObUa#XenTNH{;00yI1%5jJyv~W^En~PeDZ*q$v6(!O= z7I`udL$^&)#r`Jt>e3Otdv{`CYyWv3yO^PrVItr#%_=eZ_7C36`q-$m||4=^ri??7hq|9DYqC7Us^XhrM0lN71Pp6jh}1T z+D&!*@8@v97on@-(nsYiK`B+J`sah}_(bu%9N0zznKxN^tKbyFVljqWihBQux=@t5 z!Dg{M4Xml_V%w$Y2Z8B26cOR8tn9FJRNZVkUupYR@XL&TMC2379+&vry?wA!*;nRg zLcK>lRhwIpO$A)9%?ivFnz#RabcMa-2O&T_$(uYiZEO_QCS}M2kb-b9fAK%O$=T8t z^fPOd(eHMaFJ?8f^V{3=jph^`uV5B^x{v-E^!e7mLCJ6l>qi5|V?yr!J7eZq)n@T`yg5>_L0ey<1>V zu-33E8ZmAy0ygZo%B^oR2SwC!!Z<63O}J#pSMo|#^#b%w?7u}IqDhYCDwCs6+98`$ z9TGaHBj_OYM-cFb_FYG+uEux=?-&@s=78`brl+OD|6IRQp$E&pL6UCQkxR$Gz~E){ z{dY@YXMX;4Zo$>k#YO<6=LSlte}c|_nx!9kHgOu8>Klt{Gn@oo$U^Bt^)$mstRwgt zH)APQ^CkG0`CF%t&@I==QM>B_6bq38jubE(?f6B_&HX-&@r6jhrM&RZ43`Dl^ur&2 zMe+c%+km9oOH98Fioe;kZ4#5n!LZ2PKsY7Gt(XP27oV@F1k0ZCvM{KNTofX@gwhqn z7)JM-U9Kx5Q^55DaE_)$xzKEKA;k8-8`-4`Jc&w5O8zPMCr6l`9l@!d)TclA@0Q6>>0xP3mSbPKXAqwaT@2I6M^1ku z`RWXBjtx6QN@%p0^MLpqcZ_cN!rBTw zg}pnB``V9(9y4HHjf7Im-8Z?aXf`@H|68yWiJ}Z8Kz5`Hjf6XXR5A)>mxVM6P*CSAK9U-u3}=KHMcEi8GxeDF(Qx4ez^fHo!g#@jcwRIU8ssR_72Md z_}aET#u7h*aa^XdBnq&3{mwQEvbacR~>zr~Ai(KUSK)hr_Fc_TIzJ zrf$}u8}!%DQ&3Y2O*FE-rT=lso3VhU^BOx{_JLj&z^Z{aSZ=6T_*RqJJNni2O0hN? zd0PmU6!^*9`m|nQTOsnE97@kfuOaem=+&VE3+Aey<^2~*4dYGn_UC%CIv#dONPQMI z71Ai7Vbd8YNQzH)`E_-$I-fqpCZ^nHlbEetCy1^|ydk*u^s{5yYon_qC@2USz@?<6 zU4#X0y7&JKNmP1R;W2k!PKmDfBSuYB7nx^k2Q2Td^cPG5q6(10!uN^vfQp*-C%1#u z)cJKcZHhC>w+CLoG_B(1IuS@<^);jfL2#W_VZ14cVa-OG;4v!N^r8C$Zx_1@3ia^4 zLaV*_hvHk0&2WlNXgR$D#($V3=ulhQ` zU?w_LtV#^1{LZidnjQ~{c+dXSeqZ@M0N{@kf7U+heKq3*q1s zA}=7=cG=y>H9LFtFWY}GL?g=xvO+9a@sic$?{G&?;PJyK? znOi%|AJJl~)U&V8y`VD)tL{^&V`vLcAV%zk znwlX|>D;AdAVS?Bo&*QxCbdFd2VF~=Uqv{A=%f?dh}W{@qbhMrYTMxTERL$hP>($ zbpBW-7A-q6(hVlu*R0g%C!U6(!F+JB>meYo;zC$I{;+d)XP$+%pNssV{gJq}tB)1N z*X{Z5AWH^h%qM5yzM=45fa)L3Vso*4-S?+Y--}$nQH2jP+YYT@J3qG4^n1_NgGg=% z&)r(FY7SI0-2R2R89YF>Deb~)%)qEQ9X=S`HVcqD;a9GgECS56N?LPiW^0JG3a z8%8?3L!`%?cp5hOKuqHnyiKGJ3(px4o|5u#S_F7W&a+>-iDFkQVyKgSPzAN!&f&M! znEgtVc2Onf%#j^Vw{K&ckigT>e%T0EqFZrh2X|+2K|Ol=>2fOOpWQ0u#?qUM14w{# zQL1jEZ}Syfw~tCYq{~xRZv8x+Xr-hdZ-?*OJ~dZV!prJ2cUu-^h4)1els!BLQ>ayR z#(43613{vWR2z&D`ju1MW`8ArdoiIHVAuf8?{Q)>J~ue-$%nW5|N9pvt2`|F^=rz` z>h_RGaU11X!-sRq@h3Uii@wdm9uDSd(A%<^H(BuL9kD?Rh{gFI>Br#CkF5dDE)}z?}6VIws z0@V!{K=E;viaH;YAUp7?s#(_?Vd+uv#bi*CdZQD=7zWliH3J4d4&I;@ZZY>wK!AmT z9T9=v{p2TlUE2v-(Ey(fTsb_R`GhRnXBU0J^Ja=T&Z;P>P;~|WOis>sGrCxuX6d-z z5sg-VBc-saqAZ{*g_X zNe_SZLwzFEIRmcU&@d$_HW1-SjhBbiWZq|=Qp7|QQgjz;@$L%DYNAhu_v2J%w7o8I zEVR7fTwgn-Yl~**AuoN2M04anI)ows5LT@W7;?uGmaX0+9S@A961;f6tc`qv<9WMuW z0BZsqfDL?YJS9my`!F!WpziB288rQhH~1a-jVT_kaYB+46U)c{j&*PUMFt}Os(AxS zwSe_pd3OX$N9Y93&p+x;@KZ(-u?d*SF&*~WaG)4kXhJ?-lqksmTm@u&U|?vG)uv&V zLh3?M%~m6tkD2890VUZh|0?PS%@3-F<=-zN_xq#fGu?Ea-LnL_o#ggVXoV=oXO#oZ z&W9wcM=YvrAL?a&<@*y;LXne!SiEoNpQ;xpBBd#6QBM;#SI4^Lto??@XW}yb{!X6m zP1&YFF2|Q@Ky958AV|&pj1eto%8Y1SVK6Q>onS!mP%pUvij!n zmzEn~{b!DeR$Xev)A5B9626MZdpJn_=H14tHuxcRyv!HEO zl=GIn7m87vuy1C3{L!-Zhv7-KdLQr9k-1+5>sr|lO z90Xx1+k{&2*|lxLXSl94H*|0S5HB(t*B!g^r(NsgHQg}kLdA&YbN>S+G42pa5CS01 zVc)iv9v8=~m@4=g4l+21c%zo51#w+-NBH=qGSKNBRW?L7zxX~kg_VD*5QIp}b0ebw zX19(dM4I)=;R1MA9XJpo(Xk)U0!77tfzX|s9ywL6-%PSO7NBJjA1pM1&M*1hPA)Dv ztwjV?ZP%YzgFjeYht$48216YrkJkMZSOk%`e7(>^)iCpYKl};vyBCCh{_S(QhilCL z0F$ABqOynGbom0n9_NUzf3!t!>-deJ|M^C2?VfNOEoQ5M&(Y!Wps4np`6G;ple2SK zd^RAe=p5Dd{1O5twqSL!Ih1b5C)YOQMD~Sq>5Fr zy3P(R-Xe6${8;1c9f+;g)F$$^ zahDEqy+lDwSz^d$*-A5e*;QPVuHs%(2%5u9SBIwF;WAYNQidZBu`iruC3P*#YMNlp=cIg|u z>CuNH4LJe*Dg8v5!#){H15CWrV;D@i?*m$31Xh6bvY1_g*cFXhfAaRXedmv38?o6C zjIwD6SN&&XOsB2r2b2Id4Y7Oy0wx*|BQ6@NOeXw@(D(P^M%b}z$cQNQ9Ff};sjHA~ z#hapWDYZ<7qkLW8D%vUTHjSmUae?3UxfoNL@_1Zlri?lbxNqhP@U*=XZ*^rQbK==j zko}x*Kw+VG`yHbe{zUA?;SG3|B6oRr^fWXqEGkRT61zWobQXZ5!50kp9fxI!_zG`7 z>vuop2Zcr;cE>GVL|*d{5(`pviLi#M=rsk>qhX3aFbMh0xXi*+R2n-Ks=MbyDp0!s zRp-oIL955-?{1#jsydocFiaJ^2Z8FiX2dv=o5;MgGpF4MI6kL1|e@LAB`A z+-O~&Lavwi1d~F&*j)aTl_B)Lm%FlPitJlZ`D4?&ou|i*hi4_())&7JG7m(N$4!jU zy`oG%Sb!hTx!P-92@aUcuc)uTsuW0u<)mry7!~D_-M@u%#E_JdrcuaXMQUy~#`?A3 z=VhRhpa1F8CpPOj#zDR!gN>$EtaoJoIz8KqWM z=f2@u1UPuXk#$Z51qD=28f<^Ad~!=)7cG7G_ctU)UBBO>Es<~hBXIbHGxy8RztVc; zbO=L7WpyPcR(V&v^pW6;&;>8Lz%gsBEg7)1ygX|UwppnY0|iw66u@60`fvTXVBXeG z;54Hu2%cO7%iY~QG%6~;mtr|zV0f6#aTo18nhzf;q8B~LiV;1oj4$v?W9{43?GMtj zA|)@egxg;E?9mGG8|^?CKDS!}!9FNfAR``s&Z>155Vv{_YPjWoV=lkT)z9JX4x+2n z8+C^JDmd{BQ;y66OBd)yqa1d)FU_NH2Z!TUjWa-or(>P_VxubGm18<}7FJFSANO+1V zyZ1(QG2b3fG#1#XlybGm?WV)07U9u^1K!N(W*{rvu{}9x0sy34C=Rz~nQNaG(P-7eZ zzFHp??0r}?XW|`p?m#U^w3U)#VG>F9OWHq8g;NYJaIU*Rid#=W4@MW8R~;?gkZy29Cv4A@pIbQmLW z$Hi{MTD>*PEE|XVDuBU7ynyR1#x+6gs3_b6+~KEcOd)nw4Db^%2^%zCep4|kbS*)X zQgZV#TS@ln;O5TtOIMYkP-ZzFMWI+Z7(N`{+8+cPd9tstedOTYw%fHG_oAvaXG>l7 z3c23(rONZ|Y@sM_sD$@!hdwfQZIHFItD-oV5v)89HbTzv5HA+;K&O zhEu=l9#ymV&Xb9fuOj%+=Q&Gg<8*xjIK3axbGr4w>bU*jc5C04?&}FtQc|Kl{v8md zGHZ;t2OlpK0uX#`uepjwxVMbsHWJHeO=e!3T3+TTu~>1#-6fzB7S=Olwnk1MlQ7!L z@H}lk@88RPDzcubn|^QF#PNU$1(Sud+3G~`g(+tQNm5hzMn7R}81RRVAc~QZ9-dVq zd^m(Oy$B7SK)))|Mfj(s-<7Ec^rQ|Z>Z_^MwA_cNnD$;+pb!Q4vx$lY3%_&-UcB&M zdhJ||FkRx^Pq2T3!r}%jAEF$N`Bb3849RUjP2=HIfzLXw(Et7WN3n!h;Dfs@T1$*% zA^#V@Cbe{Xm&6{;Le@cC3j6M;@N@(zqu6Yuj1c&j|7AWel=AX^}i0wvVPd~%9eV178rYW)oza}F%YNH9u?~L= zygnyA3lF(H8+fSl|3(@dCdDZX5JR;2&_1D=^GXei_90t|ZmetkQ_iQz-dOzYpc8Sm zeNbRN9iaq1>!B2E8Upt3RvvN$cx;W@LpLeAsPIPkl~J=oZy*|bNx8of6i8kegqAxdq#PXy^h&qk!-3e0&&-L1wpXuMeM!@ za}|V1@t3Fd^UrK===)K$^I!&>pe9z4SKHlJ$w(NBxW`WEF16$H#{e9%SiL4=Ge_ zQ;Ul<9#T2X^J;3apOhayZdda@n|QsK?vr{<-1Vu#cB0=R`0BR7Gkvw@bHzX5@{zbu zJSA5QkrPa(?w8HHJ?y#fF*C@|-Znire47nVkuqYX;cDQJR6wB5XeRELl) z4Cu?KKCQGz96rFXN-r)!y_;U!KgfF9%D&9IA12q%m+OtXG@N2g067?*cc*=YOzFRk z2V6|dO{?1eU6#QRW3QZW@zQ%-U*JWQSzGPy?CrcYLVjM8y|>O3r7>)tw>CSJt~m#E z%H1TX;amNtR6=YX4lXYSBXIZY@YK-N!>72ynBP`plKZ>h?=&868xu0YW%6rg--qx9g~&VaKm5#xog&;8?>rqA?=x;Hn=Gq-LJL4sj5tBCgDk%Vr0sjV!| z+rBYzx|M=~ylMmZQGJJyM_Wjtv8DmVdz1m(C^54i$2o*<)rJ150|r@Q^1i;QdK%GB zwrMDoOME@|q`Uy5@iG7*ne0ZbRu;u9J}_g4P906H%NHHF7nWAgJzm1yGu3;Syx4UB zzV!$#g61s?qCxU3WoLi#t;?{cR09%~UuFG0H@XoPMXNZ0*f%X77~}!D9IuE8*9kmL z9V8>X?N`&RgZp!cbmgou{Ky=5L^oy)B(}^)fNIkb z52~QPc(;kNN-NXkTc?t)5_d;G!LQ%dY|A+xX-S`4;q=J~l=}5U^Y>80! zXb*I$Alw=NV!SL>*|Cl+1H4M=CY!M-MGUXh|nuud=NI$rr*C zZ(KJDaFl!f;ZVv?ae=NZq57|#h5^J_UndL3JNEw^fY6ZaNTx+6r^n0rO-Yc@DuJ$o zumn6{kW~a-{3cs2M)0!u@q27xiQ`0q7h&PfFXNRI+)km#fK=k^<#NKn>&C@%i3w!V zO#g2lDIri}VZvQwPAJiFE&v{7Z8M%gg3Tx>)EXHb+XMcnz9WD{D|$TTGO((xhb(Tu zV(KR~Zm?v}QW(8T7uaZrPb&%F)!MaxCO2-FMF-8^oez0Hg$h}h( zAh6~$U0asT-&Rm(;@v<@?8V-1V!}?2z(%eiA`HErwzBCe{Fk~<=FC|bsW?z@JkwSB z8o)qqac~xk&bt?JX`}8p;a?bd*d-cmg-a_U!!rTNi7x4~+{0Vg=)XsK=h6nX$Hy-k zCZ%XvdK8}V@D+TnAwQWC#*DxI8*SG1D1}CK=N*`zK4n_BEd-3qj=mS)Ab%Uxp zDJi>$*-ol0r~ey{yafUI(EX!SS+?hU&#Rw`pS4mKVVh%9Q%x(}@fqQK-jtwODJ;dU zO7Xq@E!7Mm;*g!fCzc(AvKJEDKa}HJWL)~oi{Y@-cDhl@gpoki()k@i}uZ4(#hQhuj0N5{Qd!XBIo9)bA9OA z6Ox*Xpr7NIFp1{3?{FDkz@=|B2T=Zm-0FJPk0nEz>Ug@i_g|WIZt+=oJTB7Z%YSNg ziVf|81!`()W-%mqnc#dLk~Gh!(=DoemtSXw{R1 zC<_Tnvw_|=z?F42GVE2aBeb>1Yt%4F_^~el1v~rHF=KEd+2hmtYqSXTVSFLmlUtiZ zNy@OF{@KE-;bYG|^#MZuOS-9$4||IV+eFSh$-dB8%om61ku&adc#1%kU5U_by@7Rt z@Sj7$NEF?v&8vmeHU^-k(tGjGx?L$$Rqf*kBC5;&*5~fN`kEoWwk0xNtF5wYQ0&T< z%TF23b6U4A(+Fv>u&cVC{i-_kLekQ#9y&}hUbDCd*q>E1Fk=fO7jc??sHD5L3hclJk*>UUhiw1a_xsITMw9ZYB zzL(b%dmrO7bvYem1;9YFkGWj`7449O82(hNhH-xqqn(?h426`67T z+bYyzk}ej>`+hUT?Rrw?yJ+0YQ|-8@ixsv~hT1-l`qLKfwWrktPL8GIz5aaEMW^Df ztEZfsEu}_HI_7i5B|00=@~Vj_o#hcc{^LLboV{Wdz%)#;b$=dmcBYmnI|K`!Qm)z z!~W5~`}VR8v652PxbTw#RN>xllFm?nY>_Y_9+FKayyn~QIy^w304Kr?^-A~g+w6L_ zGqFG^?9T}Z5Gx&Gsa92bP;ZjTr}wL(_06?j1SW{yKT^?bn*dTM%uu(O~t8L0Z1 zh4T9uLdx6w`S*X7%TJ(Qk{i*{=!wZ^8U*0_hDK+`3|~zf)_zuts)avNC~4HxRM>P% z^wN5EWO=v%W)M7`q{?a5FZs#%AE?CrdXP!Ei~AkbzDSKiqMj`bjwb_N!{z|hSMOAO z2_WE%Ds>6Gr&;ZNFOhTk^y{S+2uaTz*PmAWm{WCPt?1X8bJhEL+BKI3l`ZokLsHB( zyvUQ{ecaEl@Yw=#Dk|Q+jTEXaucR-7a$p+Abz^bMFOZR$TAs#&F_j1R|Apbi32>AD zA6su37uENM{qC8eJEgl(>5?2Im2Q+8x&;IT$sv?3=>|bsI+Pr`1*E$>rJFOq=lP%W z>b&74pS}02b;q^7_qAMHsxBMeALoAiMzrvG+Q>#ll*s)z854^nUfa#sgmmex%wKW@ z7z)*+HVeJEJ_5su31Q6RaRoh4BI^8>(znMkveVC40epJ(KHI88nZo#v|Kc^`Jzf!+nLBT}jH9vrLnfs_x(NTk z##dp3cTP;H)donwrWgDk_{iuEY99 zxS@t+q?8Lj9Ovfl?t&j$E_7>qpXKr$+jYsxtz{5L(>6B7Fr;9~Xg{Gp^z*e#1UenJ zB`8~MA`sVFf+#U)Sf-6aTYW2BR>2i@pvNKl>ohC3ObNKeVYyzb{Ca?D~io?Y)TX65U)qJgzoe*4c8P1jvVi zZN>ykHXb$Zk5WiHBfXhs;uFW&&i;51>*#CpcY62{|2kX-XG#0oiFJ~LeLmF$1tb4C z4iAq2s#cwcnKs$Q5}g!0Pj_W-1MY4#R=a)Ze*^xRx!5;fM>Lk1cs=eq631f_4LGkL zK%~H1Ma7e%qL;$=|F$^V$zyU_KH!UcE+^B_&`8l|OEOl0)`>{~3`|RJc{a0SLzm6O zo#-LxyGL9M9f2=Ost^4++cyt?g2(~9xey){t$>QNW!2V{rRy^+An@MY2BYUl@z(vL zg#}J#KLuj74oyQM+Gvn)Y(7|c*%LxYI5pAQe!sCZDHG_V1IkG_`O{XB`LCBakoTFi_olA+ z?dbendYY;hD945x38oJraw6~bXj8<`Z^#VcOR@S1BtshU+;^M(7)5N^qXLFdA8_z>DppN& zvd@lO53ZWM*k`sxZnw_2VLkzpC|EWkJxOYgQXLKdiP9Bld z@+n5Ver?nDj`9h5;133nwhGD?u##+T#@zO%TZtKc9=$u?y*HuZ7*y5orL@s=r1mlJ z>;i}~&Vr=#W9nXczVolLd@v(A7?Z3MKa^wN)C%DSxUQPU=cx1DP!j1QBsD;s(A zKUiio>NBsgjDPQi+aFM&gQ3z8cyG4nM(QmRZaBln;`O^nxsZutXvT!O!A})h2^RiN zMLV6=v2&0;-lKHMDTwk#sG;@J@7=5d)ZylF&A8ua$+LZSyYra6UjEP=obEOLW?>X= ztGx4LDrvw?A;2#{@UTDDuR4okQ2r+rx1tOjm@if7^78Q2B=rNuOvBsnj~6>!zVDnA zlIq+9y((!hC$80W?^LKrW=jPRXkMs0-voCQ&qN4?e2;&F_z`9Wmc>qp)Lw25}MKn8K_D4q2mZEMKX=3==bE&iW}UA0(hGGcOXI?DJaM__H= z)-0Z=AlGsp7z)H*ftD*S8j3nD$a(LW#dPF z!4Plm<&xqYnSP&B=VvCFyYuEi=4vcHW`0>zgA2-A>X#tuxKB_Zzy_T*R0Z6d0)UCz z3nZZ9JZW={F1=@T>=j?g6m(zcwmg+)`|4J(X+1EcvqC$UTG%;eBde=v`u_BPzD5q8 zHZzc$6V!Sf7Z=jCl~$M7NrI4CasH2jcA0d58e=F;f~u~rPCwa|A5AOAiBJ+etaj( zh(X_iG^-|30nvm2CF?`J*cnI7p3@0HBOuCnUI&X=&LvJZ#|Be?CY?k3mwa zcf4z@x2RdwwAR|3A+DUEU-mtVkTSyqOw{Px?xU@(4fEZ)*uf3!q+p|t7}Dr}RPxgq zKI#SH{syNe=IL2}_>zn6_EpcX6#uZR)+IT@g+}kRBowymyAy}V!a&ZnlZ&Z4w)I+om#Ftl#cOx$Waa# zy*~|)Cm+99zR^CKF(gq_R|beE#S8@OgQ@W%YWn*D~~YKB!oQdu7Hvy8GZ= z&H4UpCLGyvb2okot@d^&1+U$LR>tRQw6>#Tha?e z3W99t_3P{s8!ucsx-jPw#*i4CKSf;eqN72>Y7tY6rc~(u0c3~81(<}_v^22fu~A`TzDR%0kp-f)qm6DZlly<+l6#tX1u4um1PC&k50& z$+CUEEI&P7?K#CggMtHfb@WOA^`=5yR?W-CMiR5|G7q(K4Fz zM{w)Kh(Dtw@3Rm!u@@-~NL1TR(YEt8YUk*nd$;W_^VVPk<>2T@zWWGkl5J|{mj~>h z(G`jcCmJd6>_x_OW^63Ev?Ly%kODR?9y;5~Q_GX2P_jeErk)|?kO5^bp=kd3xz@;9 zk*&gN?YJuOTYo`jPmg3U&{So+jwV27bM*Txx;w7DF*^s0$c%Vai=6r@$!&LabtR-B z#a-={P6&Cr@_+p4M`P135kWvoTDwhy{HGSk(SWGW4a2?2+!Y}e|l zd{1qk2d!%BkPT9bhqe0aKw>OT;KlFl@~zO5P}yn{5n*9`Uf!`2#j;oC;tC>?Ddgv5$c=eA!^|=`V{^wkK`u?+aSGrBZ)X{WT~8Qtaehwy%kj_s z|30a{*YP*Bm|lLbwXQdPdIIp784(d*+>3PF{VKC=tb>Rf5M?t(XarD@zrUJix(qj8 zXkHJOP~W|`lYssr=vz-F@+nAkqO=~fJ8Of*k8Z76l1l6<&^kh`qnFt&|| zpGCs(bP&6B)5g|4b2FG;2mja@+HAm$El}Z~Xamigu4C*3B#J=OgigrX8{y+nc4piVFZo+BPhBJU!T`icsS6Dll;w^$Oow^4o!<6071(Of5H zIZ?|AjQ(T!_P(|2@NBF24wT^o^az6#p#+<9#lJTTW~DLz+c7}peY^L?=n8RPG;0?- zfCxq_6onUwD;*gJ)-$65e19}SusL%4@1!d99BGLw^@qPE@I|C8X$x ztPkf2z34fi`K<&F*8-c|jcRufg}u*^5CO&FqS4+U)CPu#K&SPj|LQ>}-=TG;jcpqz6FzG)``uUMu_fmsn>o0%M3ycpxC|W6Qol{x`4o$I0DO>m^S8hih$Q-^`k! zZt?`Dl)G$_wa#|M>v&yP#p(^k0F=e=_3!V82I-$?3c&#Q1=dUqv&qNUQ9K;BxeeqJ zoT#smK)G*4jo+zn*3D#VJNaI&Bfod)O<4D2ztH4HffV`5>`30n+CX2TzgMq!MX&!b zA6|s)^!O{IEeX4Zmwhj(m8LwPNGetyu$?q=}!Lw?dN_o$teg=l-!1s%sHgwe5Def&>^Xs4zD-BPGD-4Qp$`A;x>+ z701d^T6IpUt?opZL$!UhA$X*{IqXrU&`67T^%^n{p~KColIhvGs3H6ei!T}e5k>oR zyT%HBsSyjAsrMDDKb|R18Rj)H@^^IpzcEStb|xGjzqU4mr@RP54hw^smAPaur;o5G zp>tSxIXLUrzaptM23hvwKWY)LDdZ!x^X0v$UD#hKX_}r8gi1@3h^hCjfD@NwczIDa zKWnwy4p!%8_C18ZUjV!P2kWT~>*SHd^;HIYk*cix2kULAqh|aE>-Cd4PPh``f0JFl zxghmmVwFVY#pL<^*_N3HHB#cvP7!J{?;Yiz@##4A3rbpG1R4l^%R@kl`ZTFrC%+MV zw7a|j53N7+Jdab*G3`@8VPvJdnQ!x#`jp_%M7X;#_8QH11cOB9wq z%uTzcA(j0O7sa=KWrDB=zD=r48~fATes@*(o>;dV?a}HDnI(U22>M#9562+OUPcXm zYO{5QG1qAM{sO*^{r-0BMhQ*3dX3BcZ`FiEtH`$v4BSop$lGYbbZWko=Lz9lal1_| zWMULIG@8r&CXS_1)i3hz{tM@NVZJ7(B!VZ}97j@pmq5v+f>@@Jr`CWb&v$GnV3gXq zuj%aRcJ!ydYpTfzRNtC3%kPxKU8hV-9I>_aP;$-&l8)EX41D%~A|q^4E2d>(=GPTI z>Wv(^l%F`VyCx*!jK)@sJsv16f9-EUS>k_E>t){d0&kjG@M;y$?0rGEEesZaiAOYF zK(T}BlM1ON5&pz+r-PpHb?uu~?R*)APxraPY$3PVae2kq+*IJ7KX`6e=WB_{#+d*8 zVvo+3bOaQH(K|cjUA-(Yw?_?j(MxJsdBP}YSqcAY8=x0s3k{GaJTIETJ@=r4KCnHXAp2fW{}vSZVS(4*^#3fm?eAaqmSW0=s3(u( z{TAp5DN{r5tDqLY1b4c6whK3v?o@ ztxZbZ*g(6l9OY? zi2EBr^4K5*JrZ~LeFJ==E%g=LEL>+KAZ5L=II`LNLP=W6o6FIk2?t(;B1~Y;{jLIR z2)^GoN$^&^*DUjW+61FjtN%X)-q0!*`Ts}Yg%^zs^?hYyq~#46QCDdwUT^1V zKK~%p<6b02E^oc3e)EB!@IwtLjRap(a|H;xT4+tr!9j?>XD@2ArFSP2gZ`_b3iQLE z;rn+pvX39D$&5rF29Tu_dLRy9|9F1zazJJwIXOLKZS76CL>{u;V3+c5_Yn{f$T`?a zruXh}*3@7&>ty+An#nANIVHlW8Zow||IAEmQNtcU1u`x{8niysbs#i-ID07y2~M!~ zS=L9*k`$DvBKsl*1wA^yPXqmZxmz(IOgfVjD^HJg&v-RV`xKQFu>M`MkjnhIMg2w$ z$26z;e%c|ohg1cJa6E@Q9@BwGk-LQ<)Yk>;1ZL{FbqSD6zN3d6s&5VWawb_(Za>tH zes9xKof%L2AQ$TKe=Qy7Uo}~ zR_%5vqi6n~kFMqi>s!91$CrYkUbp0D>-5@EX)T_|4_C!XfrI$o)V`}6B3#@=9J~yz zg;yfb&!LW-bRLmJZ)dt?3`=rYC)R zZs?U!{0s3HzoXu1CAC>o7VY7AR-LYM1z!l|!q#z+gD-3Kv##fj&(2D%ja48sT5aeC zN=oYQjKaPLy7>Rv=}-zJA~K|@fe9+_RB+$lE3vwMFQl!4sw+h-Cqr% z)6JyY)S{JjvS9T~pbDFqA+TyxKHbKj`_549BO+@-glLy$yXl{36+nku`XTUW4JmH# z{iV>oZb{Os?i^)5r%#|t*SQPs>>o~hVB)Fa!o@+R*Oe#g-IpVpWPtl;%}V~$EMrNAF1-S~W<6g_k_@%DXk_ zJ|9>b1e)x}=$x{mrf_L<<3(!LN<2c&#^DW#5lGTfS#M3r-Q@){- zJilz)7jL8rg1q3T3r=2oIYKsyF(1EDB#|JDw=&D!NHF^@p!sKm>D+3_dK$$v0_I1F z5{d>zzUNV~jyXtsT075kEx(gnD}am? zXCST9eaHa_z+(JlW+wZFCxpYMw$7Spz#EIO060nQwa0G2xLJ>Cu!U$hmgaBm;Y@O` zY~i15+hjPE9`|NXyE8QUik*I12&1F*72s+*=LS zHM?@*yhVbA?LwgM!pRPMeVXL{0~?(aeTcnQXJlKA++fDIsm)EWXLIZLofAXskt%qu z8LelVyM<4oZ=m(0Azp?9?cKK9Yhsnfr{HBUE*MCZPs|2<$!C` z6VS-Yg5k|dYb6VV@4SdL=CUfNiM5Fjk`*8nZyuGb8S=(=<+liwQKrR1Zi58LcD&N< z@BaP0Rqd+(Ap7B_Mda~%-KG78Y{&9I5}vgrd65nnU;ylCaHN~2q30tDvj4(Ep#U(3 zTYQs*nV7WFL@lvGIiOR}^h{YYZPE2aJ(Ko1d`YO~65UwZ2}D^n=JjKda~pHZ@Qc`k z8k7x7W#$?bQ#`vS;xo~r>+<)3OHI-AAP@$*`}tX#FG3;wbXusn&QBp+OQ3?d5Imdr zq9+k%ZRSq{AKD1vF1S)?lo?uKlqaj}tN@c3SsUrl880t{T_;uW)SIEIO5RvxT--^` zq|jAgYIvEg#Z`bEXEg(FZzWD8l#a09D>|bsaXhJY;A!Qb{ac&JoE&#$HP6`k%HMy{@^|6%JDXp&!5QI|N<}+3+SM)WP*Q4u zEw}nYB93s3|6^0p;Z4uDkus6m7*Z-?2Uh-tN*^wIEO#$I{qvZxFpexph}YR-qhCoL zR@=NYrzAjU;Kj5-&n-1NMjs+MkXv!(J+(S%PekA+$t`yE60fJ4!ZJ1AR-8#X^V)U5Hcb{~D z5Q#-JFu??|4FvwM5OtF(O*^FMTJkfu9=p9=n&y*EF6s?qVfJ$^v1=eQNDYDw8~qHDaa*VQC@u(_MzjkV(%*|+wQD$$6E zA@a7=Imfr7KR^6BqN30C{%Y_pG)4%&4c;zC50ubg3{c#3FbM|YV2DJPbUjtBSIF_{ zg?W#qY8g|-`sTZC2l3+G^P&%V;Q^~#WG9qH>xCwWF)8|Yj?H>=U(`OiGqn7p>$CBM zY-hOQMT=ePBekcukP{?%^SqP;8o)_`P##w@| za{L3LqV=9=u9!X7XiLLP)g@;dWM=r`QUgX69 z_~6(Xuh-C=!GGIaOb&w{5O7IkQHuH`(#cTS0*JvMN0_)m`N&nCHEbroqHkQ5=4p&K zLHv3&NEiEOX-UR+)f#>7>$Kj1&E2J;K*KTxkN`GLPBPs`ZylCfNtZy!OS3gws4xHT zr$){y>-`kBm_iQUYQ&o4d_nU#0maHeH~3e*dJ|1qmJZdEKLc0AjF>kam;~RfV_NPR74erWKv%=w;fEGo!&yZ+2f#!h~UQ5S{mdSbV<6 zA)z7#mc$t|>zVg}Y;EwnN_)&d8H(lXa*fcjd-x@XsF5#8h?? zta+I=+q^HPxDLwqWn3Oy`y8GK^b8;Em9zM|JMK5c#l`cFFrBX}!2s!9nAT+Yet9Yx zU-F9A^p^x$@B7?L3Evj}0@bXN+t+GuRKMo`MzanfJ!Ke`!Iui&%8z&Exzfzn(hsjt(x+LzXM=4j6=>tD z(MZzp)!w6W^7s%7rSGAEr(29zY~e|K-ElOxGt-n;GMpbvt7w2Tv8i#fINc*sQ9Y8 z?7eYy_t#~~`TNVo>fBmvOc1Etv{2QiYnk=Q1Tm+O_rNR=8^g&~&W$+JjU$FZY=Sep zck&dCWmJM4s)5e&3X9wt9HBwe)=!gPnO;KJOO3ux9MSyRN?XpOms;+)FOgAq)QXh} z;Z%3oAy_kCuODi;ZH)bmvfTnUS4Gx>*WQjaj@HSENqNnYB4{d`jc9Bmxw%y!YNe5~WEHc_T#Q0B-EPpH+FE8K!W)zt7mG;UhCDQug2w~JfW zUaTw^SbotxFI;nG&*LW>gAtJsghr4|$mG1xdk_0#h{PGdV#Pp)n{XzV>t7Wj_)u{t z;WTa(JnxPQ=fhvn^(A=h7Iea} zT{sT@`wKE~)&xN#h%r|l8}@WY5WUkcNiU4Qe@L@CMa5`C;VGjY9##p*QqKiL zKUqx;Jm+fTwBTwFSXl}o9IF}m=~+}#;Kemd1$%oVAotYI^BWagT=og_`?Aq~jEx26 zx|+E+>}##NltOVM8|i?JB6MSy)$2sB9C{j}qO2Cea?*}CQcnCa{?fD{quBhFgu~nK zsnLJ($-|!2z0`Rn1W7WLWnwe0<;5J<$Vb8VVabdzT1odDYXXwcTrry>K-Mot(4{hz60y|54p`CC}g*q1>joQF@l( z+C4;_*q)dP|6|_e_t1dDfqBIaKzC~Q<_YLFI|Jvp?GI5?q+k&8*RJNomvrLrJcSrW zDQ^F<&9+;WKEuHDr6BdOGj4J3hulu|V{Wc2j8h3{(h;R00GyExAte}mN%miAQQ1-w zdW3Zn(oqNH3a%g#`aW^NcT^ANj1QLq7)$OKw}B!>{35m=00cYRtOr*no9ka&`NR~y zgFdDB(#>{dBSPZ8R}9)T4i1n{r5L-Fs0lk>aB_lLHI8>Z(<8n?e6*gu_<*&}GSUV{ zf5B&5<=NJB8-(5)EFdJfhIo7^QG*8tey2ia>u_Y`E4&l4IA2pL_0!RCf!zeJ5s?Db zH8q433~2D~@X(2cg-Be~D^C30AScy9IfsNAYZ$}0s3P#m4rox(ACD4ZCxsWGcKr(t zu{6tiizv|ARB+~%uE$s~X zjm^6e*8V%*HI9Io!^#T181a*HrDul>2P4*B_9guvg}!fQOYFx2s5Tn7MYQJK-tj2( ziren3%49^5*Q{$hOKzxHMu_i3&RxF5HCofW#)Py`#L&Wvye3U_@{0g;A@R8ER{YB{ zB@{=A&g*gSy3q5B)uG1u$@b%c=M}X)Qa4&GKsASACoeS;IchARWMLsjI#G}%hai{x z_#H3n`9K=k$YPE4mFOByuqWD|O;0pDx5mg2y3hK8aEGB*QV3y~NU1nFN7dL=oF7VF z3sq+p3*DOA*m>4QZpR5k`gN^|1Yn@+6yi}ZsD6P6n~%EhP~EZ|y{&^=Xs`}LBQYG0 ztOG=0dEgzkB`=VeJMtuvXG93GHQv_UTZ_wk7VoJwO_1f)_H3*(Q)GRHNG_inVVV3e z&~g72g-0ax_N$i;D*YqCrot(4{4Lw0o|Yj$7J^9AGs$XkosM>tLjkM^Mi4il;UrCH zCbxw@kSACOh1BU>VLqBdwE^kI@7m};_G3^mR=8qR$lhH|{^~Vkdz;RCBA+ryNz)Dj z@~Gqgc>6qqSMIvcM>nkd@yLsmgfknIrjdV3B1suwl}!9OT{XU_LPr(GOnBR$a5?2eHWhK`gsrfgcIO*d6xYy#UvTf28t^5wBf!*KYy|)ZExS7 zTDM`PDmlMqK!;1UwKl?#2%}CH!;ZtGpl9x~;4c8sEi=q*l(`jouyd2!TFVcc?Ng1c z;`5Cq0_4Bp`-ys*HqxknrhV=j?~6m62cZp@PPQI+{}=XQc}G!N?QKV>5&>)i!o==+ zN-i%{j0sEmi$?HkJT%Z}aJ!SB;Z4ltdCTvyi_e=cdZbc7IVF|=_hTs@ri{DDj~k7I zL<1!t60dOqzAID;f zpjsoKGKpcClwcB?%{w|sPQ!#@V1-&Fk*+T|e~P)O zf4;iftQFT6=!Ajs`0~94s!ZyWNOzykMd6()zWOykX#0Z{Tt4o{r${*55yVZ2n7rTsuXE9sDgCa?;_pbt@BE7hFkup>Qm$x*xG_Y$#q& ziIWwu!K9bh0yo<>(OJQ>a;bf4Dm;!4hjT^S+VZi&1@zGDSAT(5#7qpl&#L_}+k@ZL zAr#j^;IfOCEnH{&eaZ%3c@TVDM)F&=yp-v16^d0KsS0X?UR9l({o5hOP+5QMbuC-; zJ@0?XKQ9#NL_&$@vbzsn)%;t;=C0*wqtpaH_#)R!Mdzch_t@wR`*Mp&Emu1z7#%p$ zcbiwYlGp7*@;U0-`ejGB^dE5_Z}%%q*A=M#z55$8MlOLmj?Jf$khQ}OinK@@QC;>x z1+Ub$C#)Bv^aK?S#9Ex@65g~TWQ^$Cj0$@7C*kwCWfq8MFer43MDR!d5eQCmqrw@0 zO$zOrsaHOVm7mAOQU5-82SepR$As&ko?kD`(Ki2Xp-qK&yBPK=l{~LJ8+j7uI=h=t zk-pVl^nYr7u(5TdFs?pXifu%Ocai+s>vFL>O-;>a3vZq^@`*o!*Xj1lUAI?L=H4Cn z>SPkcTBm&K-%=d_xHU=SAM>>_I3R0TEG}k?Aenw7bcZXVxYP*~RA=uAe zU&0wey*94F6M-5+7RI_`E5HMM79x3F0E^%#eaSCu9y|+>dgB)%7!k| zMGXZkPoXNA*$Y8G63E6SAgDu>RxrT!#@u)QkbvOvo6B8&IDMf74{klNMU}>SwJQoS zqssE7*hy|YL*)%GVIK$iOro|>OW+CaY7rY?gzsi|EB&OiGy!6Gs~nVWY<_p5M{O2#l~L>tKII;J$N z6h?f#5?+ZCUPffioN4SzP!QMrRZmK{LIr+lp%Pfo!gHXAH&@$ytuzEYHyaH^b?`EV z97hz`-Z%~C#7(BC;WLtNhY|DPx9=J8fyg2#XC>9cY`yFaqd#z4G?G+vXEF0ntC{FYv#OX9X$h7AcPRQ&^WO6>uI02;ztpO1A?I0AP z;lwML@vD}UkuN3mW`6S1WC0xt=Bl6KdoOwmQ zlxRF(g~)UXUTe3Z#3rzR!fiF*#&b_Ukr`t%Z@Aj;d-J9v@;h}oO3v+Sr52>tWT)rX zHX4e&<#`RoLliMCP&>l~K&oRyfdJtS&Pye2=tRF;<3TK!En$2VwEkJG4K_6goQo}u zG-r^4=@l+;{-AEE+cCO*ZiyfHS#+@OmFatyN~+Cw#qcaV!-giprjqx(El)*U-omA0`j@=4u5Ynah9nuifg(2$u~;B5J$okXi_#M{2rxu+GAiiCeT^;>k=r$(T$K5x5)aigiO?#P}mnQ>(=Y>XvEq+`S zJbS%Il(Dxr`uCJ{aLL}uNvx^ary2}s#irT0<03~DZg5Vkdg){wWhsOdp3pVd)}0sf zF?|X`W@vdR3E=5I65RS@bY}E9ov#U(ILhNbu!TkglRR2ULP=puSX0+M7FGM`k$iYe zZE}_?VfPNNG{`|3*~YE*{e{O$Ld*OqK54m8ci(8pvpE%_+x#@Ylx%Tjh@VLHQAxt- z9(F>I2loZLQnODi^t|ADkJ@#cNeykJeL{9@>q;N`GA6l`l^K*r6_i!?-DTeF3_!UEJyziP{cT*>8nZ5M1d`Fo5^0_9qO~Le;r&*ij`xtA;=y4F!mVp%* zh_^$In!IkHDSO=ENfYySxmUa}K#97R(U*;c1i`kp z+gs!&p1$MQIA179d~UaI&&~1tC1jRafJq|0NsNG@TIzPK=~vLnbD5ZSxOgVGCt`6MOVq`g5Z@Uc$rE~@}MIFPPap43QwI;%8KUzL?JrM5Ri_;TKn2zsy zEkxr~D}+VC;Q>{f6y#QuVJa}Juq14;IWSCcB{d&CAO&B8BD#nhtL8#3vds8B&%pdo zh<=?Zo!1?@%i{?r^T5v%Vfv@@hJw3N%ZEXDn9S(ZrI0qqt-qrq?q7aB+Q&tw3S4AC z(?TVq%H4dv(7~ked^`TI-C8+47l6<3^ALu7XnGKG8^qM7y1~}b6mh(DBQ|dm#E8bk zPpd*%?}#Fe0+nAvnPC^0g@X$AV9aQL;To!E}j&p%z` zdU+)zVtI~-JgW;Tlt&oq#-x`$67dfV2Hg0L;ehU5o$&8pvPVipI^jzo#l*Gn=YEc~ z4|SDlV^8RbA7vkOu4OO6QWI0_cJ`KyiT?|adS4TaeKtQ&3X059A;qyzy7gedtN|tq z-l2^|n27sf?wqIAP*W`7VNB3*{x<*EM7R|77A8IcgTwbQ=XtbZ4*RS2l>hqG_C7HX zwe%^{{hqA!Q_-C+z^cr9)ifGnjUs@Gn!S}aa3b$-RqIiWJ&J?F`GV+7&JG@9dsf`w zQS|TlMwg!JBSx~}ty0uYT??=Q05}{9O0K1iXgTGrc&}!E3maY_^+jY+5R3^J8$WbZ z;8BZLX82&_Bjqrz@821}3KR7#szFJ;dskWh=&r9 zb3VVk9<>Z1nz8T!`V0`@uM2(cOb-jb5I25>^7r#!0I2c;yy( z<;5U*ij!_1jm*b()8|y;?~r#4NQ#IACp|-+taAcL2FCDy z-sFkvf0bmKR9jgi8aF3r53);&OOLmA!Brh+ijnyKA-3{;D@kW)mrHzMX$`1eUJ67| zNuTp*6Zcz&a}d?GxQ|a2Ac{fWK8zZyD4$`i4eEGh zK~Y1iTO%R_dFB>;hpL_@KAjBoav|zX36E0=O1lWW09 z1Mlm0{8npIKg}cuEKz|R0BXR`iLX~;Z;FK-@#pTaqQY2e;K(h%5a@~`h+ncjBb>uy;%LKcouWHkfm zRiwhmm(6z8cb>jizo2(J$633GYFd%Qf8<|7wXn!vf89x!Gm?)+Kni!j`aAI#d2JV= zfS_PbkxfU&vZ0^5qEXeMJSwOzV1033!dtXNsC}$(aR^Xg%FcEYgEO|b@ z1A~nne+I}-n@&LDTaVmcs&p0nd=h1?q$7VfK_oxHW?{Xv9)R+aqOu1ns$BG=3EzLd z7X9B&*A=6&Ml#^PA9%)KAC0l|01ENFZH23dd{+cihNIZIxHy{Es!Neo3^hhF*2&(W ziDeTeO6@wl{)cp@!PNE3VJ<8(i&m3beHE ze{Edm4n+=O@L{WKi%9QKX5ZTx5US~g@mw?_~DJrLNf}!#|gWID|$12Kwelb7IqtrCPihm+w;U!b$1g| zs7a)xIoS?qfq6O#ql=5uaAff{f|s@y&o&{pYuve`Za<9i$d6xpvX(y=CdQQ z>+5xYQ{Sg|MbupK(#Dv4<9swfpI)7yE5u8p;ro2k zPLA^V6R0tIh(qZp6%Ku!fOPd8+se@~D@1X;a61RznSE z;&ds$f+%N+Q`Bq^1y(VHjvU~_6#=BF&o;u>_hAv+sqH!P_YdF!Bj_GI)k=j$MJ;b@ zDZ){B2why(T0o)pC-}mCq4;R3GTo!h8(Sm{&n0@7cDBv($%?@ap0fNv2A?5)PFCYf z!1>&h3qTnQ^3s^~B`r4{1q0f7(hwk%%V1yDPK>ix&fbLUG#`1Yc`lG@H($7US1UVi zM$0+!DY)?O7dlLh?eH{KheZ#JA93g|y63FQ=|Z^U)~a=UZC=+Hh<5h8aJCpdk@d9OqH~q5A1f{J2fPCHys;Tw7N8*1suvgVJ*m0S0)$6v%8KLCE)h5DFM5*~6fojI|JM$Kl6c#Rk{K+u#k22KAfd%zLg2o<-4|7hO-&0x`mzz10Xd~0WScMcVwHPZek zpt!HEPvOHS29q`qOt3>1&Hq!=b;ncvfB)CLuD$o>+KTM#aS0KT5Dgb0Gi7h?wW91* zGA@yoBovu9TSoRQ$yN5=gx~w~czl2F|L=R8`#P`JInVPv&jZ*TYDDB*ax^T#-14zF zGG@3l$=(1-7BQy>0n(j|=^`X;h!576lS%@O9;)+=XVJQH)hM2O+XT1W>Q4LVRY-&5 zRSwS_!V#D@<$df1V;K!B~ce7)ZPD=wMq3qkGlR+-P`8r)5($Tn8#Z3@(p5xs zWSZDzW_zY8Fp)Jz-vP(>*(ntBW^`>$xz~*UEpU(I_}jYYK64xh061=9s_o zZvJx?OXLqB#r#Sbe%K*#V{LOaN0Td}EQm-@2wDySkla|;G_ss+9;BXr#9Sh@xkbfXMy9px2KC@0o_G7lN5M2^q5bPn28DTd6s0 zxQFUFN(KC9D=;NXb4-#$D9)v;3t%KpMofbOIfeJZ!%Mb7Izlw&V{;3ya$XUBK4Uai zh8+h4u>3ugB)%hv$wiOyzPoN^rFK`SVKpP5_8Iinbh^Ou^S|wW2tE->f0+^CPXei_ z{=ESZbeAvF%zbKUjJ7&Ut^ByCC@AE}r_s{4eT`RPBm{CI0phJu*qOcSb}jwklu1rP z!!ZedI96cYd`jMhd;4q5p`lR5)K97A&_}D5RKaoQ@1OM;C+?n@BSP|%^@(jVxv}1< z@fac+_m|YEK}Zt3z$YI?feqQdEgrK&MBhM<{PKC{0N&mjzu~?whMK3uuf|V#?o#hw z+kkpICc#`!aYpmR>{O`lRJ%;tWn@xH_QNA$`?p}8^(_7B2b)biqH!Vw&cGmh>y~22 z*hHwRuAkjrb)>C1(cTFr-VBJ*y&5hlb}wdmN~nbY7etpSD8;$kZ= zoX*Iy$}(0rlyM_YNk6_KMszhD3t`^|Sci}FCX&XlC#x1gSt56v(l+kljXnD8z-h>B z^r``{4fPamfRBm&NxXC$<>vMyI;)~(JzIL^=6woIp!&&kYC7X{2BmO%T7F|qoWGj+ zp8OlQr6YW0b#=SdKr-(kl6TWPH?l5Lj)~=_->1%Bz5_TT)}@QJR^%P{Tm{cUj;8cG zyC`I+fM5Te`s}*>FiCf3clH?CH;hExK;?X<$Q=dwt7s@cC@-gSzGUx*TzP>abYzSQC4X1D5Wh_7S^1g3 z)ZYF!?`f;}1n)_m)o~Z|h0-w#perTb+oX%z(E?B5L?#@mQqwf)QS_t*d6Y%PZCcA1 zJIcBEgCaRg80HS`p4_ls&qvg4!#N&9USgAp+L$9%A1t}4evON=!A=QZw<4vzhK2{@RpBKi0{`Wsi>ssG!RbFUeR;jEh9PN{U^- z=Mo*}MVB~mfW3ek)+~?vZkW+NnhTpYick=yj}q!ij2)5`J>!j|z}p62hrlQK`Ew^n zcxRQY4iaB9(_!_Vrbe3To4n(AP@-{C7aTKC-kz-g9FB;U9^DsIFBqwYm#3kg-5s3W zT9&W5yty4#=%%G<#@Ot8konw+R}295oSd98`pSW{~7nE=hAZKVs2mw zG#g3cmF5{>65ZPDD=xGX(0cj6m;k%S*hB5*_<0$`6e zS;&^8Sb-ZJ^SEo+xv{DMIVJp+1mdsX}Tbq^SZ3jt!zjSGK4(lgJV)E#6b zH6V&N6&Ci^%9ttNxt1-mvfjim>|E0O-fboX5>F8QRqJzj%nEZ!fGEABTnKft$w$%4 zY7XtX8Wt}?xiFf7%#*pLzXUVG9zF&5Wg!-(&NrqGSyX6rsftEkp@})X9jNBhQWT{z8Zx;*rIPhol<2NPXv((wuz-rkz!%3;g?46uf*O^5 z_V=CK-L-DGti0i@_@eZ`K*H<02Bd^mkbY=<8OFK?4lmxWD5yBZKfM#R+-j zeHi`e z7{jy{Z4kNT8pg0#GA3j7`#1g4;1A*s3h0lo!-0_t_W1BVA;TyoTV){m*vFF`E>oNo z^Lwi9eO=-p40AsVWEnMI?Y<=#ZplF=H)L(?Y7^MVh4d$!9ZIXpMfQ4)^YMA=nZvYQ zLAT~-5)m*sJ^cotIvpqgbhOoFP1yA<9ly+NTAIs$ezl#G4hDR^=q@6tehhpow#ch~ z!j?D@Y8C&|P)+jb$Y}Q_R0Iel!Pq`PbCC_OpbP+iDRS#H)981+6#sotZ1K~#!i}74 zr!?ju)>ISW%A#rQ&GL)U>8vS@hABzcoU0y3mo~tpV5oT3uq-LFYSaCu> z!tR(W0o|DxC=-FC%w(T!pB5ae=gYaZCuEnJwsyQ0c@*oytTfTsH*iDq96{YnU@l)N zygtYL`v)xxKU$u$^Josq+`tOweoVm;R4P~KEA+S?m~4hghG7P_7|wcg0M??@A+f5* z_oVr8N+0>RaTysaU8!jz2^gSDINX5*X0GOB|&B%M_X@ z^Q_dgh+jk{ahhB2Ax~lC1p1=dg)X3cMSSo0%`LM~7XE^Abc!JrcX>B0E1)aqKt=I& z6CK&u6q@?nWO0F=8?t}x7Bk>xRYSPJz*dsN;5({D0evLU`emtW zfQ}5Y5>7-R4z^kWDP75dI5%4vdLe$VQ$xCK)uW-(`AoHKf!_x&qUX4Op3DY^;(*(! zdKZs`jwa}~P6``pSy$ylFzqp+4@@e_WA$TSN!z^!z<$hfa(1dh0xJUY^uYFnwU;Sm zO(I(agn3IgcD&3<7oz6`$%q)cTdPESU9*9wyd|ZjUw2kghm_f#h3gl$a%S1jG^e(- ze7v_;aQeAegJwsnZw=xuecw|Ba~(HrbKl!A3ChAUQ@iZ^)g<6VwFPQ?xijw9!5~2b zU@F=Yj@0QFWwqmiv^j3oniikr6}+S5R6i#xHBqZ8x2Zm%#;09MRRVJs#L*=k^~9BH5EhqF{A z+xrCG^zWg$oC6)2U+I<)|caYx%EUWq#|B+73;uG2F_a27`yhZmx$!sIV~W zi=QtxXd$a<*%D(KHttU{vvRUpqvhF;1BfBr{vmae6mf;BW$Uc}bkQ2kRJhaCE_G)< zRb@!|ajMTnnH(k#z+41;4gj<4{zaeMr(Y^5=bH{2O=iiwbc4<|}L=#-w5_f$C1O$k57%8HFREZvFCz~9c#9*=5jRuxkR+g7@D{9YH zwIVmp+ET`smX^|nhh>Mgkjl%Fj zxFq_ivE>6uibM>kZf!N0fBPUInyE;xL#)%jORh>^^Hml}zBb)t@RN5d{sfZD&xq=4 zbk^$^XCI^eWVi9ra*u!jfR?IzMv-b>3f*ryOA&VWY<RM@=}u{Bv0rEHxeY4ftoZ`uieBzD$P7DZAB{lg zBW4?mWFeJy&VMuB+2&OKGlFoN@!P;!hf)n${mndXqwttm&`@Aq~(M|DD+%U?{Ba?O=bc-;hc zb^(&@8jf#VtZxn5IZEMWFgUO>yLF?U`rGmqc+VH809bQLZAk`M3q3G%oxEuD=EVN&{{q-ShE z0KkL1q69*Kl&1%WhYgz(-dieOR8-bM8aQ`=|Is$7?`0>2g1S039UaCbI$X(P8veWJ zg9uXEc}Q*OYkhQZ@MPMHnl(@R1tXqn-v0Z)MbLW%jk%GTJ+5UkdzGD&(^HGIL2`eK zc*T1sRoilMc)pv${Ezeb?4{f&P z!oH+?1B{?e2<-p$m;H>}cY+V^@duR*C#i_{S zScw0j_aNfb#~@IXjy%O2lO=yfne|#`JFy;KXwM|j@N~ReQsSE33M{>c+$*&@FiFme zbRze8&ehp|er~>q8Mf{0zq>lOF-T`K!5P7CG!7B3wU`Qtt0!aP8`#%lz2|ndIiHoo zb!tfHObP-j_1``u4ulnLU1m3Z)g?2sJA1_J*DsT3bs_#~|{!hK6RTDc$b} z4|IRv;rh?sVe`K~aj&nlLcPMJ3@90>78p#4LSeoz?{MQg<2kqW4V2ICnU)NI1tZ1T z4%*w=+rQD;REL@z8;7#Bp2BfYiovwB$mTU}Q+3k`UvJj9L4JKKD{DF>b590PxXiVE z^O&ExHTFxX9E;!@>XEi~Tn>VazmbB?X)%WFPmK8(l}MRNm3a>N(c!<#*{{aq{)Gqo ze9IyQ>UUEC(_ay5cwm#XE9;MXQD}vX;W#QN>F=E^2)s+@4V2D=i(k&hq%GLn1xekZ zMq0x|x4x22&b|(}NMzxY7NR|7Wbe_VpAm=-WFpzTLwj{?mDhaHps{*EYMQd>m)pdq zDY@^l0D6wm?R&kylP?Jh%|5$8WMNLHPet9NHr zn|L|ND$lrhm<*Zp89g?IAv;%zoBpw021hLqcHNS(%2+-_jfYvj5E~Dak|-(sJ5=}zx|Y+Aw@JTJ!D@@@5|!~l zb91E{QbbJ6IB`*?mP}lGg4I6m5?Nf*@tF727gd#hCsKwVQ$#PTsV?p>?1~O++J}46 zq4e;{1r0-ELopW;O3$5ppa*?F3__56603v(sI+Usjji#kVbr(%vtBDf@xLYOV61j< z%g&VW<6mQNRHjG?Bv_W9SsLrfji?W*uiW^#U$SI~A7Q4j6cn_%T~&R=yz+ip?3ojZ)2^> z5OmD!%Z{O2f*7=qkj-@w80P5NlNJZt|35oE&VP)0?yjL>Oo|yb(+;dzhX!%JEmVKy zvpW8JhV?;A)v#yFs^H^4r0eE7#Wo|YV>CD2XL$5;hJF-G0WtEC?46upCyLKTbsHYy z)3IB6W%faN4>itDYFkQlQR~?IFw{Z*hhmE}j-(^vxI_cXLV$uE{qB$pT33M&{}3j4 z-+gG%P=VwO!Z0%{u*QCA);pXW z8Hq?#cyjytL4H0T6)ml;lfNZ(uR4wows&w)Vt{R^SElF1J&k|^=wzvwb*z)nUW#Q- zw7z3uacb}Do4^ladWX~3Edz75+K$w8a;HEk^$AH#(9^3oH7CgmXdG)QGet&r_nuS- zgiec-E-7|s7cFxcsXd+%!EyN3k<73Z2hS;EO(NIC9M_bLznw1aR9%vAy|ajFP=EaA zgMDD^{?FHfkM9;1N{*%+{Xvqg?w_$H*ErwlpA{H#_@vsY5a~f3>mU=x(bP~=F7a$! zCDUa>z?rfM?$vk>RhQ9p{ulpTqK#F0)8e(peJO^y0~jpOZO_e=%a!-{o}FzpjmqJ0 zL$TGgfuoD103jNK7<^wsy-iO3QnJorXy@s+JruX}q$gTGFg2}K7E|Gb7&TR@!!4R6 zzG75lmh*j9@A8lVa+q7_XtFR>tsMj!O(2#P`wfAL4|V@!6tnf)pmNBq;Gn+}yOVtY z&C=Ry`U_mM{+__3q@>yR8x0LNB=tfXJJO_~oOB7xoQwU$BX>=9B7JY!=DAY5=N&1j zTFw|wM~=8XS`|?+*b+2|i#1k@9*Zc+Qg>)Sko`2UbY7_*X8d8fHu~7`d$fL#+H1t% z&$0OS_Q;F+vkV>A){fNQ2;e+rnwVp8|NgG=j;*$>CjD(@W~9sw_Az?@(msVwqt^!6 zjH~Z5&uq=Q8U6EB_j*g;OHVw(Hc}O44b*@FXB9>cFXKSo7h~Klb?dxi&&I0V+V7J5eQN^8MGbwuvdRGLOKh$|1i1pMbi_P? zq1mJBU&pGUhG?20d%}yQi2(cMh%N=W(Xf>4w7BweaX~@BOm&iBgB#;zW-+AjACV@i zz(c@ZaNAjuXJT-XQYu6FiMZ>1gJDVbLJV6Bs7s15W?ghRaVQ+4t**Ci>~^ujos7L` zp~mqAEFIds_Dqe#P~bZa;|e>}u%&mWonL$%4dmH(NbCIe_mb6$Ns2j6xo%}i?3nk( z!BBpoj%dIGJNITaA~Pjf0%=u=>WFxFKn;o2GY6NG-$UT5bC)MBP}#>L5OwPeMs-l| zu7UFG>}*T7E4$AAcgX#l8sd>mMj(mhSBRsBpK;gMk`gB$2QzQ)8vPFoi8Eh+d|`4+ zz6cU*(DyX&7M`)KpsA`;BB*_CAw898eem0jOyN7-;F_bgchRSDJAuB0!q`h1K*xm!nPOjCUjEJMh^$6({&p^YMYrgI?spsGQ#{DqCSe89Ws$8vp zmB3SNyg*g6g;I>y1=AlSkGfY{guh15?F6|$$mYSA+I`XY2~5#JT*V#~Ldyb5Lv7u~ zZ=1diz2&;{Z3rs4q!pl*UZ)k@RH{!e;0*;^CXXW{TZuvBF}W$@U*UnXk#qq@7|@#- zCZq}66XTWtwM39JvPHJ`MV6ALMYx@4>K;ErfIO2Isorpg2(kz!IvRi^J-!zncOLn5 ze+U?BOjGG|^9cvK* zTPG_x1j5CgI79q``lU@x8|W7vCsoN3#7WmIh`ob=QjA<45k>+=MKA!M29^|e&@-*s z>QVXp9Qq{dI}goaaJV*u(*zAe5jED@9}GGIZvmuIcZo3`>THTU7O;JE?m@>^EGf`4 znu!7IzsU<2J#bQL?8ia2^6=2#=3trzg~A(f4@t>hC=wD!EU-=mkW9lHnmyAlWZ%gJ zD+eH3u7}jR2NOMBK&CK{&0%MILld%a(fWZX??b+9J?SI6Uv(W}+0EYy6L-{C1_^l9 zU?F4>H~HV8(BK8$;t$G0{LOu_&(bsK_5#oq6p$f6hoq% hxj?6Lc600A1&rV8?a#SmJvIPc`Z`A16 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(""" + + + Applications + %s + +""" % 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 7943e4e39efd8fc25296d3a28da55f9919ed31a2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1670 zcmX|B2~?BU68@7|3|f`OvI*4)PmiPx*dwQ<#g+iF#HTz=z$z{fF_2%fnfwq4LW0@Y zR3H!tTNZ+Vl;9%)3?QILSyd9EfQkZAx+x%vU9_b?ZBOTXckaxc@0^)CcgYG53)!%4 z%Q^r6HV{Mik>ELIb)ZqOz85T84WPII#0WGR{c!q!Q?P5x4n4{R0GGzqfjGG(-xXeT zhJubjWL6x+qj0DIkH_-_nF-uj3Y+T5;>4HD@7)5E99BsYAVjAE;c$e5CC0Lt*a#Yx zLEW);YyxJyx61HOglH@dK&CLTp;R6SK~Kb4!ElhY8VqEyQ#fEe4FUoKvHlRm0Z%1D zAd877)8NehbTEO6r9lw;b1$!04wV9dNz_;tgTZ2QR}1l?gRxX5m&)}zm4fF|y!LqR z_Bttw>Hq-5v+x5EL2#CTrK?LdY%svC`G#qz1E%~Vj*gD5t`2`Y7!1b20Tb-!OAPSq zYcuwlj05e)!S-f_ETc#&ER;KDzZKJl_SXBWVZME9A zI&Du=!$3z%8hASW3^hF=PH=`QOo$UE#0%;184MV-^Dvm8C)HU zY-;E+z|i*?^|y@rez?PJ&2ZDKckft7E%!z(_st_?7W3FB49obadBQsS@czArWA`Q} zCubf#_;&iq-1OAk%+$jC^TmaQ7mF|!mtMSBUS2K<5&s6C;)f6^j0_;&u|cnM*zUH= z1Bdtgc#qF#L?S;m^;~9FcJ_$bY_?de;}a7Tua=izyT^R;U-(V<5cl~<&>lZ8`t=yb z->FX{A`Q*IY4Rq2HE@88_5G;X{vBU@xM6*BkCn6sdpghU_C{axQp}2ZZc;*}B-9GVD?J%TMEvJ0QNS;z3fL1T~^tn!*0J{uy8e=k7>{i14rbezP8=wqL;A*ANnWp z071Uimf%6!EZlEP*zHb`?g-cw3Lt+(BG&+Q$J~rj2JkY&3GzmSUFsv+3s2k(eD7+%#EyR#PeKiIx&k$Rk3!A}W$Qv?sEa$aYMo8LUSW7C79UnlW@M(`&Yt-MR_eBnnrgtt(=QwnCUoJx+2dLXUlO6iLI z)5O<+U_(#D8#!>~+r!axy0-eb>z%E;fgu|r(Br+&cC+IS(FBUw-Skr(aG-v-_2w>Q zxs3W920)um?5wq{@BAZtTh0)kE1wJw1z+t%+ASe(*yL|mN~Q7+buG@N4FIz@Gi;Gq z(`+kf>^_W~>h-r@6Y0YqT5GixMj^4mv-jgrPCfM>;uiayb`Ulh?Yv1}UBOfw-bj-M z0wEd?=aC$dAsaEZc^K#vdYkfzT78@Pm)=+O-XiOD@qk8^YPrl)wuniPb^B zy%!jNyj9#b!`wa~m}qpu{vuY_9t#Cp#l-y^qq>Y(xTL4t^jq3)942=h=h6R*=*-)+ zjBOx_TmU8|k_e1|UKOD`x~wN=Z0FIWFK~*=C%I-Zp3!~d;FnE&yEwbnnHx(G!To}U?63HnOl`}=c@ vLkCR>kcaNm+1dL1FZ}w{WAOd{q017TgD*%|V_I@oKTu*&*gkzg%=te7aFZ2g diff --git a/user_config/pyfm/icons/audio.png b/user_config/pyfm/icons/audio.png deleted file mode 100644 index c0101346b611e7495861f4efe2c511f76b51e5e7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1544 zcmZuxdpOg382_1#wYlcDP%Wj4+h|9w2Q!nE80M04nOQb$)AnPKVADonltl zL5HREOw8q+D5;PxatTMONfadx#+mciInQ&x&-;Gg=Y2n)_ws$7_xa|L{Pt@ij1d3; zXcB$A0+lv;UEylVd;BubQE8xDPa+wvJbZX;uF~I-6K5kW&<%wn zj+uR`M&*(DT~$eYas}1jLP%A>r5w}z6ZPsB9f^l3j7`ITF2BQLe)^b{n?>IuoIfcm zEm5SuXS-zxN4p=-(6<8jY&HIo<9Mj%<4;>2mRqb2g_zm>W|aVtCPNHgroh!(N`nK_ zQ<~#9w=68{I*@LlaoG{u7Y6ps8f2joYdbHGQO&k>d})pwX)Z^;Ky)gLfW%p(zS;|S zhq0bZqG5|MOKeB2ftAYB62Xfmt(YKfk!{`B;!kpKLB*r1)t#q5ui%^%MzJmM;M%13 zk}Qd+%b=65%lY8%(Y2h~IDw16)&P$VXgnJJI3Cag^}HM0Ic#M`(VmLA1Cfn;y7SWC z?3N-Vj_#s!&k${bt9F3_rW<;B;VS&EXZ8oj526(CF_?pEUw@7qW^0`d)42!9rKq&u z1Y*JfJF85Tp@#+%o%647;s;2GXL-;o=hc86t@f!4CIu_q@$IS5-Ags!P9W*3%CuQS z4|mm$qaG%0klZ{e;D%hU#Y{1mZ4}pru@!GB3tYwW^SaUTwT1_0?*AGEaVBJ5b3Ik6 ze)dAQPW0J|?IKONhRS`hh?e(cf+^_MxtTMm^tH%Bfsd|CEvv#lA#=P29wT3F#X?ao zfkhFyPfg@-mHTwON^C0b8)<7_Di8hFBCwIiQUN+w?8!ZAS__NX4(blQ{_}IrrKQ;5 z7CSQ|^vh(5B6T@`q=`D9%V2l5ZTWydwpL4@>(kDp!*b^tU$?kqPLwI;Rj19dWyh-F zB~s1Y`s+eGX3Awxm&{O+jEjX_)E+%OQzX>rZ|H`Ru_**Wx|df7&beK-x;0trxp*$z zZeu?Gy8Wk<@fH&EPHAPCZuAkK;W|71a&`X|8St00&W}Kkdh8GJjr6W>@!F?BS1D0SAL zxZ;T3&N)bu{|c(QglMKWcIar;P{EA=x07mnrAy!G?UV4fsV_`~-JAB+n?!(bdXGO( z7iyVCIW3zU*jl_-7}~9GD_y(ifg#7+7a&M}23(10r0G)fyU5@#BsY%n7xv~;)ioT1 zu^9$v*!6(K|1)hd)X2ED)qw6VK=PVLE(Uj{i?k=xTnbJu;liYBC?EEVrD$%N(6GyU z%Fm@y_BvJQ*^kx!eUOjLzqRdl3OS?e55>5vEYC7$jhM!2S~>pOZKs}&5eShx;A6D@ zyLbP$j&#@}>FsXB;d4F}2O3c|nYZTZqfS#p?gxyVxAC@&7Vpa#^mUr04nA8b32D39 z!7VLJWB2(IXRl54?~Jk(DtwKKdSQ==p%L@NNS7w*_l5c|k73`|Fb||N=Ss%qr|hKp zFcNPwoI@=KZ0n!Q419iA7#B6nT5v0>`HfkgGTQ5`MsO>O$XbkVT!UHdTL6(U;Cknq{Q{mCL=3J`#dGHam+6_rCl di=8|}I5;?NKlFb92xL>4nJ zNUsNB#yF{oGC)De64!{5;QX|b^2DN4hVt@qz0ADq;^f4FRK5J7^x5xhq=1UVK#D># zOL9^f0)R3_3ZBXNc?uyJsky1DHrHsBf%ipdZ&9wFhW=V}MVHE09J8Z@~aA5*-~K z5fM>PP>_?8^XAPPi0p+67cO4Bc=_^WAotm`XF&4#^XCu}$awMM#mt#A=gyrwZ{EE5 z^XD&EuwdP~bsINs+_GiMwQJYz-@pIh!GlMS9=(438e*XO^6L^n`(#Rj{DK+SIFyvN zwDk>)J-vfMH*MLv|M1}tA3uHm{N?Lc&6p-z1_s79PZ!6KjC*fqlC<5TCeqS|*mRL?Kgc7(I8Vi=8|}I5;?NKlFb92xL>4nJ z$hLzpWB=2SsX#%=64!{5;QX|b^2DN4hVt@qz0ADq;^f4FRK5J7^x5xhq=1UVK#D># zOL9^f0)R3_3ZBXNc?uyJsky1DHrHsBfZYpno_di4|!2nE;;ZdJgpSz-K{;CN})=gZxe$uiHlb3Cnx_r~r6&t6n*feeBrs*p;&sep2 z#_G*8S8tiQdMnTtp!q-(xF)ho0G+E>666=mz{teP#m&ttEFvl4>6lif#XQ z8W13Ilv$zc>xRv19^Jh6-?ox*^G&|HnMr59#~t{-#It^DV2glL{i@!-kE4TcEV%OT z-p;}>w+z1?PA#6E70US!LfC%He;CD4G5_F<15KQ|YZz6wZP-1z`D{=_^8Ki6{avaJ zN7k*_@#bs#uBW^^zIST;wfe&9^4sO*Om&4j>bpKNANjrI3)7R|Q=T#KpZwhZ_*dum zPKyse-tD=U{igWAzqi#}eLQv9Y-+XEJ!hQx&W=4!!(c)y8wWhvU6w0CUnUvw{_K};>3gl1wceg9u(=*FzrTHMS*!j2>yL7`K8m_0?z;72 ikl~}S#Zrnb|3dd~^!AY~RX+CuWU8mDpUXO@geCx;cxYb$ diff --git a/user_config/pyfm/icons/doc.png b/user_config/pyfm/icons/doc.png deleted file mode 100644 index f8388267bc53ad5b95537b2977873701ac3de87b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 702 zcmeAS@N?(olHy`uVBq!ia0vp^2_VeD3?#3*wSy#>i=8|}I5;?NKlFb92xL>4nJ zNUsNB#yF{oGC)De64!{5;QX|b^2DN4hVt@qz0ADq;^f4FRK5J7^x5xhq=1U#L5e~$ zOL9^f0)R3_3ZBXNc?uyJskx~NA*s0qIf*5ykA0+kAalscA{WaJky1SjSyc%+tR zmXzFY>2w2127x4<^9w4AGSf3k7@VCI97{@yGLuS6GV}9vgEN3maLmcfPF2V#DJihh z*H11=O)SYQOHIzt&CSm%2DwN-Co?%UuQ;_>KdDl;I8onN&p zZ_9Puk?XiC-}6{u%6r8rAArhKrhnG%zo$Rxf&SbtZY#d}uKt>}<$cc94;{z8cOCoQ zef;}`Q$J^&|26ykuVq(%{r~^}l(nx4&>f;BL4LsuGV1!hQ>M<92A` za$u2V;P7(L&tTlTfNe{YTmTot0$~9MRfh(*0}QPUOht^WN18--M73=5e-P5pBywZE zqa&Bf?#D-!zU*O~{q@o;^8-1PL-!t-%^{%RaKQdUf!l(-hV)bR3I~jz$_qT0S@&69 z;D^cqV^xO-=8c(K|8@#XgfTm*UyupEDR0WO-Js**PYaPZ#^2g)85_AZEX$o=90q!r N!PC{xWt~$(69DZ(DkcB` diff --git a/user_config/pyfm/icons/pdf.png b/user_config/pyfm/icons/pdf.png deleted file mode 100644 index 9f40122a8e9ef281901e085651fa66de960cae4c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 925 zcmeAS@N?(olHy`uVBq!ia0vp^2_VeD3?#3*wSy#>i=8|}I5;?NKlFb92xL>4nJ zNUsNB#yF{oGC)De64!{5;QX|b^2DN4hVt@qz0ADq;^f4FRK5J7^x5xhq=1U#L5e~$ zOL9^f0)R3_3ZBXNc?uyJskx~NA*s0qIf*5ykA0+kAalscA{WaJky1SjSyc%+tR zmXzFY>2w2127x4<^9w4AGSf3k7@VCI97{@yGLuS6GV}9vgEN3maLmcfPF2V#DJihh z*H11=O)SYQOHIzt&CSm%2DwN-Co?%UuQ;_>KdDl;I8onN&pK?PTIAN%F-q_%-x$%8-V~{Ru^Jlh}&z$X_xi~y`b$XGV{4yf+ zWn?H&`^%{CSBbH&a#P=y6~8Sj`Mhk&=VePjFJJm)Mi|9-EZE1`1tA5moJqLU6Kq8j5VGvjv*QM-d>CJKja|7`e0Z0qDT7{ z@Ue=x9@zCTit+dV|C5)xEuOis`2EaGr$@H>bMLhORy~vHEa0IcWLx}nu9@QL5C7Jh zX&z=3(1Zr%7`Gi@ z+rcECz{PMtSfD}Gfx-O%LmLBA2_x$XCeaErj?Z?Dt0hEM9Jpw!5zzSR;LT>oh)-;D z-n^G8s(8a2u>7j+ zJjt8L#OQU^+W%$ol~c1VVm|%2De->yIiSp{$#TD@bqZhb;MWIIABs=S_AyQP;aJC9 z@7=`^``2qPQ>12fE92L^Z&7o_S!&dSSYtQ+%hYC)a9y}J+~C8l_ggjg7FAu;*!ziN zM)gzP8CSILH(b-+-*8WRe#6G_PuBw(7%L7eyZw(>d&4WXDQgbA*>(Eg)|m(N)`VR6 iI#nofzso27&zyp}+m6{Mw{HSQ6oaR$pUXO@geCx$l#&Sm diff --git a/user_config/pyfm/icons/presentation.png b/user_config/pyfm/icons/presentation.png deleted file mode 100644 index 3a339af593f4932996667370db88dbdc050f86b2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 882 zcmeAS@N?(olHy`uVBq!ia0vp^2_VeD3?#3*wSy#>i=8|}I5;?NKlFb92xL>4nJ zNUsNB#yF{oGC)De64!{5;QX|b^2DN4hVt@qz0ADq;^f4FRK5J7^x5xhq=1U#L5e~$ zOL9^f0)R3_3ZBXNc?uyJskx~NA*s0qIf*5ykA0+kAalscA{WaJky1SjSyc%+tR zmXzFY>2w2127x4<^9w4AGSf3k7@VCI97{@yGLuS6GV}9vgEN3maLmcfPF2V#DJihh z*H11=O)SYQOHIzt&CSm%2DwN-Co?%UuQ;_>KdDl;I8onN&pG({c6t+>by80{rP(O=NlPcPs@Hg1J-h0;pch9pXZf+o>%^PQTx|5 zr(f5ce_eC=b=?(Y$m7;ukK2AdZvXSF_s@$Ne_qZ6+WY6_>_4yO{&_X;&#U=B_xydm z2$g7u zy(C%^MBZ?E3;YYq@; zP29q_Hf(L_y2CGRR%D2_ugaMC>EHP$zLTdlCB8p;vvcF`{ZEo=ISURHn)rH$X~^?& zh(7%Ls^Ln8EX%38Wp@R3xwkT3+GD7vwS`yEq18%~fz_G)&hCqCIt-jT41yI53I>e( z4zS)}l77Hd)1b+~;P1dNje${yky(O?;{vOQ0(Zy*GuMdCoH08VIIx~u=h(<~#qWH6 zP>XN&fu|<}v#v1|hgwf#t@C7L6$x-?{LlGi@A>yXrfLN8E%<5_DY1cji=7a2S>tsN zu^0ZA7`pjycDFR@XDUo!JN7Z%Qf#;D<(k(Al4T^K>V2yp{_(l7vHrrm=jk?$lke2b zU%SUt#jfn^@|Uq(d)qEN-}U)cZ)+Sh vg(L2s-MoMEqZICF(UN^ZN^i1n34LRJ(>cXB_UJWGf@bh^^>bP0l+XkK!91hU diff --git a/user_config/pyfm/icons/spreadsheet.png b/user_config/pyfm/icons/spreadsheet.png deleted file mode 100644 index 710efa631bbe46a69e7d43808c675b0f5c2411c0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 707 zcmeAS@N?(olHy`uVBq!ia0vp^2_VeD3?#3*wSy#>i=8|}I5;?NKlFb92xL>4nJ zNUsNB#yF{oGC)De64!{5;QX|b^2DN4hVt@qz0ADq;^f4FRK5J7^x5xhq=1U#L5e~$ zOL9^f0)R3_3ZBXNc?uyJskx~NA*s0qIf*5ykA0+kAalscA{WaJky1SjSyc%+tR zmXzFY>2w2127x4<^9w4AGSf3k7@VCI97{@yGLuS6GV}9vgEN3maLmcfPF2V#DJihh z*H11=O)SYQOHIzt&CSm%2DwN-Co?%UuQ;_>KdDl;I8onN&pjHh&2juQ60FprD3nqb#g8ju=dvf#l z7rV~$aGeiCp6-jiz1R4Auk{DY`fUgbSP~q(B0PLcbk?3cpyJje?d?aq=3id8;PRq{ zmlrL)wtVfa_3LhJ*nV&4|NsBZl4mjk-62{MbP0l+XkKZ6g@< diff --git a/user_config/pyfm/icons/text.png b/user_config/pyfm/icons/text.png deleted file mode 100644 index 2546fcd90b78ab5752f74234520af202a6c3987b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 798 zcmXAlYe-XJ7{}kuB}=C`tC3Uis2C#YEF(%oowrN5E?>zZM=u=!!)$Hk)mC9(O>wRa ztp}7FMumD>MN&vAS7Q~pfuQN{Z5`z>l!S*Rq_&wNlqwpPlwd-drieN{O^})_sR}Fz z8;E)wQ_-|GPasfGxSS>$aRsSXlNw#vk)VN4;2IsS6Vx?l>Ewdk>>NQ&YxNMQKNTO7 zl|i&xt#-TJ@AuEm&3V0Ehr zKmx%31Q;lRal74+_xJaM_|(*t&1RdPo}QVRnVp@TpPvUFFc5()%gf6EgXYT0%IfMW z7=Vpn07O6&L}2y7b9YXF(PUauDMg&vSYE>31A3#Wwe5Px?T3%Y#>cG-&Ly`u5Dfiv z4d-1#h`|??3T3JV|KwQ-Q^;v|mUFqm$Q!9Hd?=PZ8S}+wB(iH?XMS864LScrkDYpO zq(^b9EU(~AbKi*BvGM%Z;IrkV9%D!xcquds+dsF6t-bHn;)u?m3SshkMqElKD#~Ft zzKiPIl3TmOXR1P|+k|#8n7f&gJ6UlP(HXtG0n2bYC&ctdZXd8H6DQQ7=*u#$Gc`@j zOG)MYU~KvaIr^iozD2e@aiyOuz+~qGBf=|9EOs1rC%K82rd64gp9(Qc?ox7`qifS$ zhy@>^@%xDRdtjr6wV$w*vv`E1m^CWO4=K1F!8X=g-_0UrwK;PvXzd%yk7wArTRwcf nSXpJ**OfNuzE@@2Gn)FB={Hgt{G?7Rd?*r?RhF8IYp?zTm=%Df diff --git a/user_config/pyfm/icons/video.png b/user_config/pyfm/icons/video.png deleted file mode 100644 index 55afa98662c7439702c4391be32151b59c65e253..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1313 zcmeAS@N?(olHy`uVBq!ia0vp^2_VeD1|%QND7OGoEX7WqAsj$Z!;#Vf4nJ zNUsNB#yF{oGC)De64!{5;QX|b^2DN4hVt@qz0ADq;^f4FRK5J7^x5xhq=1UVK#D># zOL9^f0)R3_3ZBXNc?uyJsky1DHrHsBf%ipdZ&9wFl@+Mo$;Vkcv5P@A_uQq{heETG?y0yzC9fraSuz>xc-iKB^S-C2j9}#|+2Trc2JzJRTnfTzEQIH^v=t-uh~( zxc^nT;G^AFyj-F_E#k~I^^GcclIec6N$|KDEZ^HL2yRXD%)^JLrNU0ZK4B(tA;`e?y7+bL3Cv<0)SasI_CAaj~N#c(YL7*x>s=RUEzB;@PrBh& zH)D%og8`esF@{4D4id~9$qb1+3OxwHP0QHrcBOO2*;w_|95lANuJ3MPXxI5(O|0kK zRl|QgH_Ue({_&96Vc&zq|1bC+7nk<1Yuu@Ae>qY6-K)v7Yqj^V-D!HvaJ2dMZM$~H zu!p<&;w*k~%)4!W;alV}{X=v0Uhk~<**mx5&CKevXN-+f=bm`FyWqv#SP7|{IXP;~ zcdqPSDZPH*cNRIBH>Ot885zEOeQr=yWB#r(Ve!YC*VuRDXUi=Ow$PzK?rCL+Fg2guB&_nRD$dv!BcjdVIbiOV!A>s5L(B z_M7webFwb`{Y$Vp;Qn~>iWvW|`+K5R742;lemwcu^H&o81xx0 uulP@kQGh{%aR*z&{2E6WF!;j1pYh0Ho3#3@qwc_>hQZU-&t;ucLK6Tg{6(q& diff --git a/user_config/pyfm/icons/web.png b/user_config/pyfm/icons/web.png deleted file mode 100644 index 17017ce3f5c04869fd808d91732939ea9b32b36d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1845 zcmX9-30PBC7JeZt0R&N&im4Vm5LuK(q#_nd0tCVUMu-({*@BToAxV=L*+e!GkXn@r ziUOk+5JanpgIGYI)*&h-ERqnyl7#n?m%Ok@b?nd&oyW}mzH{$?&iTLZo^$Vi%IPuT zRu-Er006L}MTEv;XY!g6@Yp_1EnQ3Cg%lctKp-eo`<0k$&W|`O1OVdwHNz!tzVs;; zbOgiV!8qPAFf&EK0x~l*JvrPH!qgN#%abQayT0({W{mXN8i~OH(^&u=%LtrksXVR| zgU!ldIWbrn{PYx%HMdbK!l)b8sALhy<_Unflnkc`Rwf4oXU~*Vv1II8GK9y^7I4zo zAP^GbLTopXZa#0EirfR|UQfRzGrPO?&Y85umTaIGS*bWSRZ zD`W}1nAtmpDPF#w-d>5tN4f#P=q^2q5r*9|K0dBcDCXzqr>3S*6jdsf7@nJ(TU=ax z^X84gV89~F%gakkOIT!KVF5u9wOXxGsW2B?7{Cbs2N(x)F^*QN#qdzr=R?%L4N;wk z!d-?UTqF@B32nP1(p3`aCW-QpM0-eLeI#+d!*tK#Sf62r@5n*Fk;8jOjs%P)QO1%f zW6aR8RO;B#@Yly8$2rmC=?Ab4>^;QlZta9Ma9XfOQ)x*il%Eyr)$clua!?v zPtV-AI9ppWTYp~JP^G+GgLR~8tyA6mhwA<qq?n0{jdpZeC|;T_Bc>935wYc z?RSIdZcyw_h~Wtx^oEXn0e#~Sb>4-#9zZ=0q23Ot{|WT03#PlnhkW70eQ_glMx^$_R@Y<)VrQXIbz5PntJ0g8Rbn*LUbxsP8jj*_-Z(cX zN{bSF%odkOt+)PVn?cJf{v^KG_!od*Z`y6_gfo9@lyz1e6<-f@N(DIgw^V+X{8VBDgh-5c#-s8%>=Ds{ zKfZ$B2p)baC-hl5^DPUkA4$M1z~aT=lsHFr2wwAQ^^hG?0?{1_9}F`e&AR*C-`<=x z*RYSBq9v~+{b;!tNT=fS*ot>s1gZ^;zlQsQ*$T>i*<;Y?y|^T*Id-^@z$QlD35-#i zJ2sI;MpjWp9kw)4zj?RIGhcdL7=G-=JU=#Yy(dm>A@;T}0CztyE+AaJ=M8AMMO$_| z7Id+bi9q0dam5HecC>c_tpmQK;`iVX>Wcn}ut+z3R7*7gNk8~2Q z1}q%HZJIat|JlfU$d-TD#L3I&q0rktK6eLt#iF1#&8Ilzqp1VwcF)ZjcT=&PXup+d zrtYX6^88blp$1-*%0m?Q_y0cl4qR{hAfh?`YOtxbIN6Bo?^I^~=S_`RJz#u_xuM8K zT55i|B_nE=rK5wevMuH?>Bb+{SGUyyyt<;S|E!pu1J}QO|8A5sa;v2BD^p^p&yHcv RE^NR6T3Afz&y?h%{{oe`z-j;h 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" } }