diff --git a/app.py b/app.py
new file mode 100644
index 00000000..0f0b6820
--- /dev/null
+++ b/app.py
@@ -0,0 +1,17 @@
+#!env/bin/python
+import sys
+
+from PyQt5.QtWidgets import QApplication, QWidget
+
+from core.controller import FlatpakController
+from core.model import FlatpakManager
+from view.qt.systray import TrayIcon
+
+app = QApplication(sys.argv)
+manager = FlatpakManager()
+controller = FlatpakController(manager)
+hidden_widget = QWidget()
+trayIcon = TrayIcon(parent=hidden_widget, controller=controller, check_interval=30)
+trayIcon.show()
+
+sys.exit(app.exec_())
diff --git a/app_qt.py b/app_qt.py
deleted file mode 100755
index ab4389d7..00000000
--- a/app_qt.py
+++ /dev/null
@@ -1,20 +0,0 @@
-#!env/bin/python
-import sys
-
-from PyQt5.QtWidgets import QApplication
-
-from core.controller import FlatpakController
-from core.model import FlatpakManager
-from view.qt.window import MainWindow
-
-app = QApplication(sys.argv)
-app.setStyle('Fusion')
-manager = FlatpakManager()
-controller = FlatpakController(manager)
-window_main = MainWindow(controller)
-window_main.refresh()
-
-# tray_icon = SystemTrayIcon(window_main)
-window_main.show()
-# tray_icon.show()
-sys.exit(app.exec_())
diff --git a/resources/img/flathub_logo.svg b/resources/img/flathub_logo.svg
index bbd82f4b..2c72e02e 100755
--- a/resources/img/flathub_logo.svg
+++ b/resources/img/flathub_logo.svg
@@ -14,7 +14,7 @@
viewBox="0 0 11.495958 11.042967"
version="1.1"
id="svg3871"
- inkscape:version="0.92.3 (2405546, 2018-03-11)"
+ inkscape:version="0.92.4 5da689c313, 2019-01-14"
sodipodi:docname="flathub_logo.svg">
@@ -27,15 +27,15 @@
inkscape:pageshadow="2"
inkscape:zoom="0.35"
inkscape:cx="354.2246"
- inkscape:cy="117.98097"
+ inkscape:cy="117.98098"
inkscape:document-units="px"
inkscape:current-layer="layer1"
showgrid="false"
showborder="false"
- inkscape:window-width="1366"
- inkscape:window-height="739"
+ inkscape:window-width="1360"
+ inkscape:window-height="707"
inkscape:window-x="0"
- inkscape:window-y="260"
+ inkscape:window-y="0"
inkscape:window-maximized="1"
fit-margin-bottom="-0.2"
fit-margin-top="0"
diff --git a/resources/img/update_logo.svg b/resources/img/update_logo.svg
new file mode 100644
index 00000000..fdfd0c3a
--- /dev/null
+++ b/resources/img/update_logo.svg
@@ -0,0 +1,179 @@
+
+
+
+
diff --git a/view/qt/systray.py b/view/qt/systray.py
index 4ec445ca..739b6bbf 100755
--- a/view/qt/systray.py
+++ b/view/qt/systray.py
@@ -1,29 +1,76 @@
import sys
+import time
+from PyQt5.QtCore import QThread, pyqtSignal, QCoreApplication
from PyQt5.QtGui import QIcon
-from PyQt5.QtWidgets import QSystemTrayIcon, QMenu, QApplication, QWidget
+from PyQt5.QtWidgets import QSystemTrayIcon, QMenu
from core import resource
# to update
-#apps = flatpak.list_installed()
-#print(apps)
+from core.controller import FlatpakController
+from view.qt.window import ManageWindow
-class SystemTrayIcon(QSystemTrayIcon):
+class UpdateCheck(QThread):
- def __init__(self, parent=None):
- QSystemTrayIcon.__init__(self, QIcon(resource.get_path('img/flathub_logo.svg')), parent)
+ signal = pyqtSignal(int)
+
+ def __init__(self, check_interval: int, controller: FlatpakController, parent=None):
+ super(UpdateCheck, self).__init__(parent)
+ self.controller = controller
+ self.check_interval = check_interval
+
+ def run(self):
+
+ while True:
+
+ apps = self.controller.refresh()
+
+ updates = len([app for app in apps if app['update']])
+
+ self.signal.emit(updates)
+
+ time.sleep(self.check_interval)
+
+
+class TrayIcon(QSystemTrayIcon):
+
+ def __init__(self, controller: FlatpakController, check_interval: int = 60, parent=None):
+ self.icon_default = QIcon(resource.get_path('img/flathub_logo.svg'))
+ self.icon_update = QIcon(resource.get_path('img/update_logo.svg'))
+ QSystemTrayIcon.__init__(self, self.icon_default, parent)
self.menu = QMenu(parent)
- exitAction = self.menu.addAction("Exit")
+
+ self.action_manage = self.menu.addAction("Manage applications")
+ self.action_manage.triggered.connect(self.show_manage_window)
+
+ self.action_exit = self.menu.addAction("Exit")
+ self.action_exit.triggered.connect(lambda: QCoreApplication.exit())
self.setContextMenu(self.menu)
+ self.controller = controller
+ self.check_thread = UpdateCheck(check_interval=check_interval, controller=self.controller)
+ self.check_thread.signal.connect(self.notify_update)
+ self.check_thread.start()
+ self.manage_window = ManageWindow(controller=controller, tray_icon=self)
+ def notify_update(self, updates: int):
+ if updates > 0:
+ if self.icon() != self.icon_update:
+ self.setIcon(self.icon_update)
+ self.setToolTip('Updates found: {}'.format(updates))
-if __name__ == '__main__':
- app = QApplication(sys.argv)
- w = QWidget()
- trayIcon = SystemTrayIcon(w)
- trayIcon.show()
+ if self.manage_window:
+ self.manage_window.refresh()
+ else:
+ self.setIcon(self.icon_default)
+ self.setToolTip(None)
- sys.exit(app.exec_())
+ def show_manage_window(self):
+
+ if not self.manage_window:
+ self.manage_window = ManageWindow(controller=self.controller)
+
+ self.manage_window.refresh()
+ self.manage_window.show()
diff --git a/view/qt/window.py b/view/qt/window.py
index 55959838..9e2527bb 100755
--- a/view/qt/window.py
+++ b/view/qt/window.py
@@ -1,18 +1,16 @@
import operator
from functools import reduce
-
-from PyQt5 import QtCore
-from PyQt5.QtNetwork import QNetworkAccessManager, QNetworkRequest
-
-from core import resource, __version__
+from threading import Lock
from typing import List
from PyQt5.QtCore import Qt, QThread, pyqtSignal, QUrl
from PyQt5.QtGui import QIcon, QColor, QPixmap
+from PyQt5.QtNetwork import QNetworkAccessManager, QNetworkRequest
from PyQt5.QtWidgets import QWidget, QVBoxLayout, QApplication, QTableWidget, \
QTableWidgetItem, QTableView, QCheckBox, QHeaderView, QToolButton, QToolBar, \
QSizePolicy, QLabel, QMessageBox
+from core import resource, __version__
from core.controller import FlatpakController
@@ -38,23 +36,24 @@ class UpdateToggleButton(QToolButton):
self.root.change_update_button_state()
-class MainWindow(QWidget):
+class ManageWindow(QWidget):
- __COLUMNS__ = ['Package', 'Version', 'Branch', 'Arch', 'Ref', 'Latest Version', 'Origin', 'Update ?']
+ __COLUMNS__ = ['Package', 'Version', 'Latest Version', 'Branch', 'Arch', 'Ref', 'Origin', 'Update ?']
__BASE_HEIGHT__ = 400
- def __init__(self, controller: FlatpakController):
- super(MainWindow, self).__init__()
+ def __init__(self, controller: FlatpakController, tray_icon = None):
+ super(ManageWindow, self).__init__()
self.controller = controller
self.icon_cache = {}
+ self.tray_icon = tray_icon
self.network_man = QNetworkAccessManager()
self.network_man.finished.connect(self._load_icon)
self.icon_flathub = QIcon(resource.get_path('img/flathub_logo.svg'))
self._check_flatpak_installed()
- self.resize(MainWindow.__BASE_HEIGHT__, MainWindow.__BASE_HEIGHT__)
- self.setWindowTitle('flatman ({})'.format(__version__))
+ self.resize(ManageWindow.__BASE_HEIGHT__, ManageWindow.__BASE_HEIGHT__)
+ self.setWindowTitle('fpakman ({})'.format(__version__))
self.setWindowIcon(self.icon_flathub)
self.layout = QVBoxLayout()
@@ -94,17 +93,17 @@ class MainWindow(QWidget):
self.layout.addWidget(toolbar)
self.table_apps = QTableWidget()
- self.table_apps.setColumnCount(len(MainWindow.__COLUMNS__))
+ self.table_apps.setColumnCount(len(ManageWindow.__COLUMNS__))
self.table_apps.setFocusPolicy(Qt.NoFocus)
self.table_apps.setShowGrid(False)
self.table_apps.verticalHeader().setVisible(False)
self.table_apps.setSelectionBehavior(QTableView.SelectRows)
- self.table_apps.setHorizontalHeaderLabels(MainWindow.__COLUMNS__)
+ self.table_apps.setHorizontalHeaderLabels(ManageWindow.__COLUMNS__)
self.table_apps.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
header_horizontal = self.table_apps.horizontalHeader()
- for i in range(0, len(MainWindow.__COLUMNS__)):
- header_horizontal.setSectionResizeMode(i, QHeaderView.ResizeToContents)
+ for i in range(0, len(ManageWindow.__COLUMNS__)):
+ header_horizontal.setSectionResizeMode(i, QHeaderView.Stretch + QHeaderView.AdjustToContents)
self.layout.addWidget(self.table_apps)
@@ -117,7 +116,24 @@ class MainWindow(QWidget):
self.refresh_thread = RefreshPackages(self.controller)
self.refresh_thread.signal.connect(self._finish_refresh)
- self.layout.addWidget(QLabel('flatpak: ' + self.controller.get_version()), alignment=Qt.AlignRight)
+ self.toolbar_bottom = QToolBar()
+ self.label_updates = QLabel('')
+ self.toolbar_bottom.addWidget(self.label_updates)
+ spacer = QWidget()
+ spacer.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
+ self.toolbar_bottom.addWidget(spacer)
+
+ self.toolbar_bottom.addWidget(QLabel('flatpak: ' + self.controller.get_version()))
+ self.layout.addWidget(self.toolbar_bottom)
+
+ self.thread_lock = Lock()
+ self.working = False # restrict the number of threaded actions
+
+ def closeEvent(self, event):
+
+ if self.tray_icon:
+ event.ignore()
+ self.hide()
def _load_icon(self, http_response):
icon_url = http_response.url().toString()
@@ -143,22 +159,37 @@ class MainWindow(QWidget):
error_msg.exec_()
exit(1)
- def refresh(self, threaded: bool = False):
+ def _acquire_lock(self):
- self._check_flatpak_installed()
+ self.thread_lock.acquire()
- self._begin_action('Refreshing...')
+ if not self.working:
+ self.working = True
- if threaded:
+ self.thread_lock.release()
+ return self.working
+
+ def _release_lock(self):
+
+ self.thread_lock.acquire()
+
+ if self.working:
+ self.working = False
+
+ self.thread_lock.release()
+
+ def refresh(self):
+
+ if self._acquire_lock():
+ self._check_flatpak_installed()
+ self._begin_action('Refreshing...')
self.refresh_thread.start()
- else:
- apps = self.controller.refresh()
- self.update_packages(apps)
- self.finish_action()
def _finish_refresh(self):
+
self.update_packages(self.refresh_thread.apps)
self.finish_action()
+ self._release_lock()
def filter_only_apps(self, only_apps: int):
@@ -170,12 +201,19 @@ class MainWindow(QWidget):
self.table_apps.setRowHidden(idx, hidden)
app['visible'] = not hidden
- self.change_update_button_state()
+ self.change_update_state()
- def change_update_button_state(self):
+ def change_update_state(self):
enable_bt_update = False
+ updates = len([app for app in self.apps if app['model']['update']])
+
+ if updates > 0:
+ self.label_updates.setText('Updates: {}'.format(updates))
+ else:
+ self.label_updates.setText('')
+
for app in self.apps:
if app['visible'] and app['update_checked']:
enable_bt_update = True
@@ -223,28 +261,28 @@ class MainWindow(QWidget):
col_version.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled)
self.table_apps.setItem(idx, 1, col_version)
+ col_release = QTableWidgetItem()
+ col_release.setText(app['latest_version'])
+ col_release.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled)
+ self.table_apps.setItem(idx, 2, col_release)
+
+ if app['update']:
+ col_release.setForeground(QColor('orange'))
+
col_branch = QTableWidgetItem()
col_branch.setText(app['branch'])
col_branch.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled)
- self.table_apps.setItem(idx, 2, col_branch)
+ self.table_apps.setItem(idx, 3, col_branch)
col_arch = QTableWidgetItem()
col_arch.setText(app['arch'])
col_arch.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled)
- self.table_apps.setItem(idx, 3, col_arch)
+ self.table_apps.setItem(idx, 4, col_arch)
col_package = QTableWidgetItem()
col_package.setText(app['ref'])
col_package.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled)
- self.table_apps.setItem(idx, 4, col_package)
-
- col_release = QTableWidgetItem()
- col_release.setText(app['latest_version'])
- col_release.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled)
- self.table_apps.setItem(idx, 5, col_release)
-
- if app['update']:
- col_release.setForeground(QColor('yellow'))
+ self.table_apps.setItem(idx, 5, col_package)
col_origin = QTableWidgetItem()
col_origin.setText(app['origin'])
@@ -260,28 +298,35 @@ class MainWindow(QWidget):
self.apps.append(app_model)
- self.change_update_button_state()
+ self.change_update_state()
self.filter_only_apps(2 if self.checkbox_only_apps.isChecked() else 0)
self.resize_and_center()
def resize_and_center(self):
- new_width = reduce(operator.add, [self.table_apps.columnWidth(i) for i in range(len(MainWindow.__COLUMNS__))]) * 1.05
+ new_width = reduce(operator.add, [self.table_apps.columnWidth(i) for i in range(len(ManageWindow.__COLUMNS__))]) * 1.05
self.resize(new_width, self.height())
self.centralize()
def update_selected(self):
- if self.apps:
- to_update = [pak['model']['ref'] for pak in self.apps if pak['visible'] and pak['update_checked']]
- if to_update:
- self._begin_action('Updating...')
- self.update_thread.refs_to_update = to_update
- self.update_thread.start()
+ if self._acquire_lock():
+ if self.apps:
+ to_update = [pak['model']['ref'] for pak in self.apps if pak['visible'] and pak['update_checked']]
+
+ if to_update:
+ self._begin_action('Updating...')
+ self.update_thread.refs_to_update = to_update
+ self.update_thread.start()
def _finish_update_selected(self):
self.update_packages(self.update_thread.updated_apps)
self.finish_action()
+ if self.tray_icon and self.update_thread.updated_apps:
+ self.tray_icon.notify_update(len([app for app in self.update_thread.updated_apps if app['update']]))
+
+ self._release_lock()
+
def _begin_action(self, action_label: str):
self.label_status.setText(action_label)
self.bt_update.setEnabled(False)