diff --git a/README.md b/README.md index 18b7b9a..fd06add 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,9 @@ # Pytop Pytop is a Gtk + Python gui to have a custom desktop interface. +# Updates +Added task bar. + # Notes ```sudo apt-get install python3 wget steamcmd``` diff --git a/bin/pytop-0-0-1-x64.deb b/bin/pytop-0-0-1-x64.deb index 1fd06cf..491b1d9 100644 Binary files a/bin/pytop-0-0-1-x64.deb and b/bin/pytop-0-0-1-x64.deb differ diff --git a/src/Pytop/PyTop.py b/src/Pytop/PyTop.py index 8d1c9d5..d487e46 100755 --- a/src/Pytop/PyTop.py +++ b/src/Pytop/PyTop.py @@ -15,12 +15,12 @@ from setproctitle import setproctitle # Application imports from utils import Settings -from signal_classes import CrossClassSignals, GridSignals +from signal_classes import CrossClassSignals, GridSignals, TaskbarSignals class Main: - setproctitle('Pytop') def __init__(self): + setproctitle('Pytop') GLib.unix_signal_add(GLib.PRIORITY_DEFAULT, signal.SIGINT, gtk.main_quit) faulthandler.enable() # For better debug info @@ -31,7 +31,8 @@ class Main: # Gets the methods from the classes and sets to handler. # Then, builder connects to any signals it needs. classes = [CrossClassSignals(settings), - GridSignals(settings)] + GridSignals(settings), + TaskbarSignals(settings)] handlers = {} for c in classes: diff --git a/src/Pytop/resources/PyTop.glade b/src/Pytop/resources/PyTop.glade index 2396a89..7764d81 100644 --- a/src/Pytop/resources/PyTop.glade +++ b/src/Pytop/resources/PyTop.glade @@ -67,7 +67,7 @@ True 6 multiple - 6 + 72 @@ -79,6 +79,62 @@ 1 + + + True + False + + + 64 + True + True + never + in + + + True + False + + + True + False + start + + + + + + + + + + True + True + 0 + + + + + True + False + start + + + + + + False + True + 1 + + + + + False + True + 2 + + @@ -87,6 +143,134 @@ False gtk-new + + False + + + True + False + vertical + start + + + Minimize + True + True + True + + + + True + True + 0 + + + + + Maximize + True + True + True + + + + True + True + 1 + + + + + Move + True + True + True + + + + True + True + 2 + + + + + Resixze + True + True + True + + + + True + True + 3 + + + + + Always On Top + True + True + False + True + + + + True + True + 4 + + + + + Always Below + True + True + False + True + + + + True + True + 5 + + + + + Always On Visible Workspace + True + True + False + True + + + + True + True + 6 + + + + + gtk-close + True + True + True + True + True + + + + True + True + 7 + + + + + True False diff --git a/src/Pytop/signal_classes/TaskbarSignals.py b/src/Pytop/signal_classes/TaskbarSignals.py new file mode 100644 index 0000000..5f2d663 --- /dev/null +++ b/src/Pytop/signal_classes/TaskbarSignals.py @@ -0,0 +1,181 @@ +# Gtk imports +import gi +gi.require_version('Gtk', '3.0') +gi.require_version('Gdk', '3.0') +gi.require_version('Wnck', '3.0') + +from gi.repository import Wnck as wnck +from gi.repository import Gtk as gtk +from gi.repository import Gdk as gdk +from gi.repository import GLib + + +# Python imports +import threading + +# Application imports + + + +def threaded(fn): + def wrapper(*args, **kwargs): + threading.Thread(target=fn, args=args, kwargs=kwargs).start() + + return wrapper + +class MouseButtons: + LEFT_BUTTON = 1 + RIGHT_BUTTON = 3 + + +class TaskbarSignals: + def __init__(self, settings): + self.settings = settings + self.builder = self.settings.returnBuilder() + self.taskBarButtons = self.builder.get_object('taskBarButtons') + self.taskbarMenu = self.builder.get_object('taskbarMenu') + + self.SCREEN = wnck.Screen.get_default() + self.actv_workspace_num = None + self.window = None + + self.SCREEN.force_update() # (Re)populate screen windows list + self.refreashTaskbar() + self.setScreenSignals() + self.setPagerWidget() + + + def refreashTaskbar(self): + workspace = self.SCREEN.get_active_workspace() + self.actv_workspace_num = workspace.get_number() + + windows = self.SCREEN.get_windows() + for w in windows: + if workspace and w.get_workspace(): + wnum = w.get_workspace().get_number() + if not w.is_skip_pager() and not w.is_skip_tasklist() and wnum == self.actv_workspace_num: + btn = self.createWinBttn(w) + self.setupSignals(btn, w) + self.taskBarButtons.add(btn) + + def setupSignals(self, btn, win): + btn.connect("button-press-event", self.clickEvent, (win)) + + def setScreenSignals(self): + self.SCREEN.connect("active-workspace-changed", self.activeWorkspaceChanged) + # self.SCREEN.connect("application-opened", self.applicationOpened) + # self.SCREEN.connect("application-closed", self.applicationClosed) + self.SCREEN.connect("window-opened", self.windowOpened) + self.SCREEN.connect("window-closed", self.windowClosed) + + def createWinBttn(self, w): + btn = gtk.Button(label=w.get_name(), always_show_image=True) + img = gtk.Image() + img.set_from_pixbuf( w.get_icon() ) # w.get_mini_icon() or w.get_icon() + btn.set_image(img) + btn.show() + return btn + + def clickEvent(self, widget, e, window): + if e.type == gdk.EventType.BUTTON_PRESS and e.button == MouseButtons.LEFT_BUTTON: + if not window.is_minimized(): + window.minimize() + else: + window.activate(1) + if e.type == gdk.EventType.BUTTON_PRESS and e.button == MouseButtons.RIGHT_BUTTON: + self.window = window + self.taskbarMenu.set_relative_to(widget) + self.taskbarMenu.popup() + + def setPagerWidget(self): + pager = wnck.Pager() + self.builder.get_object('taskBarWorkspaces').add(pager) + + # ---- Screen Events ---- + # NOTE: This is the worst way of doing this and kids die when these are run. + # We need to filter actions and more like add/remove buttons than just + # clearing everything. I'm sorry to all the families hurt by this.... + def activeWorkspaceChanged(self, screen, workspace): + self.clearChildren(self.taskBarButtons) + self.SCREEN = screen + self.SCREEN.force_update() # (Re)populate screen windows list + self.refreashTaskbar() + def windowOpened(self, screen, window): + self.SCREEN.force_update() # (Re)populate screen windows list + btn = self.createWinBttn(window) + self.setupSignals(btn, window) + self.taskBarButtons.add(btn) + def windowClosed(self, screen, window): + self.clearChildren(self.taskBarButtons) + self.SCREEN.force_update() # (Re)populate screen windows list + self.refreashTaskbar() + + def clearChildren(self, parent): + children = parent.get_children(); + for child in parent: + child.destroy() + + # ---- Taskbar Button Events ---- + def toggleMinimize(self, widget, data=None): + if not self.window.is_minimized(): + self.window.minimize() + widget.set_label("Unminimize") + else: + self.window.activate(1) + widget.set_label("Minimize") + + def toggleMaximize(self, widget, data=None): + if not self.window.is_maximized(): + self.window.maximize() + widget.set_label("Unmaximize") + else: + self.window.unmaximize() + widget.set_label("Maximize") + + def startMoveWindow(self, widget, data=None): + self.window.keyboard_move() + + def startResizeWindow(self, widget, data=None): + self.window.keyboard_size() + + def setTopState(self, widget): + if not self.window.is_above(): + self.window.make_above() + else: + self.window.unmake_above() + + def setBelowState(self, widget): + if not self.window.is_above(): + self.window.make_below() + else: + self.window.unmake_below() + + def setWorkspacePin(self, widget): + if not self.window.is_pinned(): + self.window.pin() + else: + self.window.unpin() + + def closeAppWindow(self, widget, data=None): + self.window.close(1) + + + # WINDOW_SIGNALS + # print(w.get_icon()) + # w.get_name() + # w.get_icon() + # w.get_mini_icon() + # w.make_above() + # w. pin() + # w. get_state() # https://lazka.github.io/pgi-docs/Wnck-3.0/flags.html#Wnck.WindowState + # w.get_workspace() + # w.set_workspace(workspace) + # w.close() + # w.is_above() + # w.is_active() + # w.is_below() + # w.is_fullscreen() + # w.is_maximized() + # w.is_minimized() + # w.is_pinned() + # w.is_sticky() diff --git a/src/Pytop/signal_classes/__init__.py b/src/Pytop/signal_classes/__init__.py index 4bd914e..6bfa30d 100644 --- a/src/Pytop/signal_classes/__init__.py +++ b/src/Pytop/signal_classes/__init__.py @@ -1,2 +1,3 @@ from signal_classes.CrossClassSignals import CrossClassSignals from signal_classes.GridSignals import GridSignals +from signal_classes.TaskbarSignals import TaskbarSignals diff --git a/src/Pytop/utils/FileHandler.py b/src/Pytop/utils/FileHandler.py index c017ea2..2376792 100644 --- a/src/Pytop/utils/FileHandler.py +++ b/src/Pytop/utils/FileHandler.py @@ -35,24 +35,22 @@ class FileHandler: self.TRASHINFOFOLDER = settings.returnTrshInfoPth() - - @threaded def openFile(self, file): print("Opening: " + file) if file.lower().endswith(self.vids): - subprocess.Popen([self.MEDIAPLAYER, self.MPV_WH, file]) + subprocess.Popen([self.MEDIAPLAYER, self.MPV_WH, file], stdout=subprocess.PIPE) elif file.lower().endswith(self.music): - subprocess.Popen([self.MUSICPLAYER, file]) + subprocess.Popen([self.MUSICPLAYER, file], stdout=subprocess.PIPE) elif file.lower().endswith(self.images): - subprocess.Popen([self.IMGVIEWER, file]) + subprocess.Popen([self.IMGVIEWER, file], stdout=subprocess.PIPE) elif file.lower().endswith(self.txt): - subprocess.Popen([self.TEXTVIEWER, file]) + subprocess.Popen([self.TEXTVIEWER, file], stdout=subprocess.PIPE) elif file.lower().endswith(self.pdf): - subprocess.Popen([self.PDFVIEWER, file]) + subprocess.Popen([self.PDFVIEWER, file], stdout=subprocess.PIPE) elif file.lower().endswith(self.office): - subprocess.Popen([self.OFFICEPROG, file]) + subprocess.Popen([self.OFFICEPROG, file], stdout=subprocess.PIPE) else: - subprocess.Popen(['xdg-open', file]) + subprocess.Popen(['xdg-open', file], stdout=subprocess.PIPE) def create(self, name, type): diff --git a/src/Pytop/widgets/Grid.py b/src/Pytop/widgets/Grid.py index 16c3ef7..096e2de 100644 --- a/src/Pytop/widgets/Grid.py +++ b/src/Pytop/widgets/Grid.py @@ -50,7 +50,7 @@ class Grid: self.grid.connect("item-activated", self.iconDblLeftClick) self.grid.connect("button_release_event", self.iconSingleClick, (self.grid,)) - + # @threaded def setNewDirectory(self, path): self.currentPath = path dirPaths = ['.', '..'] @@ -112,7 +112,8 @@ class Grid: for dataSet in self.toWorkPool: self.store.append([dataSet[0].get_pixbuf(), dataSet[1]]) - self.toWorkPool.clear() + self.toWorkPool.clear() + if len(self.store) == len(files): # Processed all files return False else: # Check again when idle