diff --git a/Makefile b/Makefile index 91c682c9..6c3632e7 100755 --- a/Makefile +++ b/Makefile @@ -13,7 +13,6 @@ source: install: mkdir -m 755 -p $(DEST_APP) mkdir -m 755 -p $(DEST_SHARE)/applications - mkdir -m 755 -p $(DEST_SHARE)/applications/$(APP) mkdir -m 755 -p $(DEST_APP)/images mkdir -m 755 -p $(DEST_APP)/pyelliptic mkdir -m 755 -p $(DEST_APP)/socks @@ -37,7 +36,7 @@ install: install -m 644 src/bitmessageqt/*.py $(DEST_APP)/bitmessageqt install -m 755 debian/pybm /usr/bin/pybitmessage - install -m 644 desktop/$(APP).desktop $(DEST_SHARE)/applications/$(APP)/$(APP).desktop + install -m 644 desktop/$(APP).desktop $(DEST_SHARE)/applications/$(APP).desktop install -m 644 src/images/can-icon-24px.png $(DEST_SHARE)/icons/hicolor/24x24/apps/$(APP).png install -m 644 desktop/can-icon.svg $(DEST_SHARE)/icons/hicolor/scalable/apps/$(APP).svg install -m 644 desktop/can-icon.svg $(DEST_SHARE)/pixmaps/$(APP).svg diff --git a/debian/changelog b/debian/changelog index 8e8ed8c0..c90ca319 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,4 +1,4 @@ -pybitmessage (0.3.0-1) unstable; urgency=low +pybitmessage (0.3.0-1) raring; urgency=low * Added new API function: getStatus diff --git a/debian/control b/debian/control index 92a4eb15..2dd19194 100644 --- a/debian/control +++ b/debian/control @@ -2,7 +2,7 @@ Source: pybitmessage Section: contrib/comm Priority: extra Maintainer: Jonathan Warren -Build-Depends: debhelper (>= 8.0.0), python (>= 2.7.0), openssl, python-qt4, libqt4-dev (>= 4.8.0), python-qt4-dev, sqlite3, libsqlite3-dev +Build-Depends: debhelper (>= 8.0.0), python (>= 2.7.0), openssl, python-qt4, libqt4-dev (>= 4.8.0), python-qt4-dev, sqlite3, libsqlite3-dev, libmessaging-menu-dev Standards-Version: 3.9.2 Homepage: https://bitmessage.org/ Vcs-Browser: https://github.com/Bitmessage/PyBitmessage @@ -10,7 +10,7 @@ Vcs-Git: https://github.com/Bitmessage/PyBitmessage.git Package: pybitmessage Architecture: all -Depends: ${misc:Depends}, python (>= 2.7.0), openssl, python-qt4, libqt4-dev (>= 4.8.0), python-qt4-dev, sqlite3, libsqlite3-dev +Depends: ${misc:Depends}, python (>= 2.7.0), openssl, python-qt4, libqt4-dev (>= 4.8.0), python-qt4-dev, sqlite3, libsqlite3-dev, libmessaging-menu-dev Description: Send encrypted messages to another person or to many subscribers Bitmessage is a P2P communications protocol used to send encrypted messages to another person or to many subscribers. It is decentralized and trustless, diff --git a/debian/files b/debian/files deleted file mode 100644 index 881a3f20..00000000 --- a/debian/files +++ /dev/null @@ -1 +0,0 @@ -pybitmessage_0.3.0-1_all.deb contrib/comm extra diff --git a/debian/pybm b/debian/pybm index d806eedd..95e61e54 100644 --- a/debian/pybm +++ b/debian/pybm @@ -1,4 +1,4 @@ -#!/bin/bash +#!/bin/sh cd /usr/share/pybitmessage -python bitmessagemain.py +exec python bitmessagemain.py diff --git a/debian/rules b/debian/rules index a5e5f162..0f873425 100755 --- a/debian/rules +++ b/debian/rules @@ -20,11 +20,11 @@ install: build clean dh_testroot dh_prep dh_installdirs + mkdir -m 755 -p $(CURDIR)/debian/$(APP)/usr mkdir -m 755 -p $(CURDIR)/debian/$(APP)/usr/bin mkdir -m 755 -p $(DEST_APP) mkdir -m 755 -p $(DEST_SHARE)/applications - mkdir -m 755 -p $(DEST_SHARE)/applications/$(APP) mkdir -m 755 -p $(DEST_APP)/images mkdir -m 755 -p $(DEST_APP)/pyelliptic mkdir -m 755 -p $(DEST_APP)/socks @@ -48,7 +48,7 @@ install: build clean install -m 644 $(CURDIR)/src/bitmessageqt/*.py $(DEST_APP)/bitmessageqt install -m 755 $(CURDIR)/debian/pybm $(DEST_MAIN)/pybitmessage - install -m 644 $(CURDIR)/desktop/$(APP).desktop $(DEST_SHARE)/applications/$(APP)/$(APP).desktop + install -m 644 $(CURDIR)/desktop/$(APP).desktop $(DEST_SHARE)/applications/$(APP).desktop install -m 644 $(CURDIR)/src/images/can-icon-24px.png $(DEST_SHARE)/icons/hicolor/24x24/apps/$(APP).png install -m 644 $(CURDIR)/desktop/can-icon.svg $(DEST_SHARE)/icons/hicolor/scalable/apps/$(APP).svg install -m 644 $(CURDIR)/desktop/can-icon.svg $(DEST_SHARE)/pixmaps/$(APP).svg diff --git a/debian/source/include-binaries b/debian/source/include-binaries index 8569d576..f676fce8 100644 --- a/debian/source/include-binaries +++ b/debian/source/include-binaries @@ -6,6 +6,9 @@ src/images/redicon.png src/images/subscriptions.png src/images/blacklist.png src/images/can-icon-24px.png +src/images/can-icon-24px-red.png +src/images/can-icon-24px-yellow.png +src/images/can-icon-24px-green.png src/images/identities.png src/images/yellowicon.png src/images/inbox.png diff --git a/desktop/pybitmessage.desktop b/desktop/pybitmessage.desktop index 83da32cd..363908dd 100644 --- a/desktop/pybitmessage.desktop +++ b/desktop/pybitmessage.desktop @@ -2,8 +2,29 @@ Type=Application Name=PyBitmessage GenericName=PyBitmessage +X-GNOME-FullName=PyBitmessage Secure Messaging Comment=Send encrypted messages to another person or to many subscribers Exec=pybitmessage %U Icon=pybitmessage Terminal=false -Categories=Network +Categories=Network;Email;Application +Keywords=Email;E-mail;Newsgroup;Messaging +X-MessagingMenu-UsesChatSection=true +X-Ubuntu-Gettext-Domain=pybitmessage + +Actions=Send;Subscribe;AddressBook + +[Desktop Action Send] +Name=Send +Exec=pybitmessage -s +OnlyShowIn=Unity; + +[Desktop Action Subscribe] +Name=Subscribe +Exec=pybitmessage -b +OnlyShowIn=Unity; + +[Desktop Action AddressBook] +Name=Address Book +Exec=pybitmessage -a +OnlyShowIn=Unity; \ No newline at end of file diff --git a/src/bitmessagemain.py b/src/bitmessagemain.py index f91d2195..9a26f879 100755 --- a/src/bitmessagemain.py +++ b/src/bitmessagemain.py @@ -47,7 +47,7 @@ import signal #Used to capture a Ctrl-C keypress so that Bitmessage can shutdown from SimpleXMLRPCServer import * import json from subprocess import call #used when the API must execute an outside program - +import singleton #For each stream to which we connect, several outgoingSynSender threads will exist and will collectively create 8 connections with peers. class outgoingSynSender(threading.Thread): @@ -3810,6 +3810,9 @@ if useVeryEasyProofOfWorkForTesting: shared.networkDefaultPayloadLengthExtraBytes = int(shared.networkDefaultPayloadLengthExtraBytes / 7000) if __name__ == "__main__": + # is the application already running? If yes then exit. + thisapp = singleton.singleinstance() + signal.signal(signal.SIGINT, signal_handler) #signal.signal(signal.SIGINT, signal.SIG_DFL) @@ -3857,6 +3860,7 @@ if __name__ == "__main__": shared.config.set('bitmessagesettings','messagesencrypted','false') shared.config.set('bitmessagesettings','defaultnoncetrialsperbyte',str(shared.networkDefaultProofOfWorkNonceTrialsPerByte)) shared.config.set('bitmessagesettings','defaultpayloadlengthextrabytes',str(shared.networkDefaultPayloadLengthExtraBytes)) + shared.config.set('bitmessagesettings','minimizeonclose','true') if storeConfigFilesInSameDirectoryAsProgramByDefault: #Just use the same directory as the program and forget about the appdata folder diff --git a/src/bitmessageqt/__init__.py b/src/bitmessageqt/__init__.py index f9bb9e5f..f2ff7cb8 100644 --- a/src/bitmessageqt/__init__.py +++ b/src/bitmessageqt/__init__.py @@ -1,3 +1,4 @@ + try: from PyQt4.QtCore import * from PyQt4.QtGui import * @@ -5,6 +6,15 @@ except Exception, err: print 'PyBitmessage requires PyQt. You can download it from http://www.riverbankcomputing.com/software/pyqt/download or by searching Google for \'PyQt Download\' (without quotes).' print 'Error message:', err sys.exit() + +withMessagingMenu = False +try: + from gi.repository import MessagingMenu + from gi.repository import Notify + withMessagingMenu = True +except ImportError: + MessagingMenu = None + from addresses import * import shared from bitmessageui import * @@ -22,8 +32,10 @@ import time import os from pyelliptic.openssl import OpenSSL import pickle +import platform class MyForm(QtGui.QMainWindow): + def __init__(self, parent=None): QtGui.QWidget.__init__(self, parent) self.ui = Ui_MainWindow() @@ -62,7 +74,7 @@ class MyForm(QtGui.QMainWindow): traySignal = "activated(QSystemTrayIcon::ActivationReason)" QtCore.QObject.connect(self.trayIcon, QtCore.SIGNAL(traySignal), self.__icon_activated) menu = QtGui.QMenu() - self.exitAction = menu.addAction("Exit", self.close) + self.exitAction = menu.addAction("Quit", self.quit) self.trayIcon.setContextMenu(menu) #I'm currently under the impression that Mac users have different expectations for the tray icon. They don't necessairly expect it to open the main window when clicked and they still expect a program showing a tray icon to also be in the dock. if 'darwin' in sys.platform: @@ -71,7 +83,7 @@ class MyForm(QtGui.QMainWindow): self.ui.labelSendBroadcastWarning.setVisible(False) #FILE MENU and other buttons - QtCore.QObject.connect(self.ui.actionExit, QtCore.SIGNAL("triggered()"), self.close) + QtCore.QObject.connect(self.ui.actionExit, QtCore.SIGNAL("triggered()"), self.quit) QtCore.QObject.connect(self.ui.actionManageKeys, QtCore.SIGNAL("triggered()"), self.click_actionManageKeys) QtCore.QObject.connect(self.ui.actionRegenerateDeterministicAddresses, QtCore.SIGNAL("triggered()"), self.click_actionRegenerateDeterministicAddresses) QtCore.QObject.connect(self.ui.pushButtonNewAddress, QtCore.SIGNAL("clicked()"), self.click_NewAddressDialog) @@ -228,12 +240,13 @@ class MyForm(QtGui.QMainWindow): shared.sqlSubmitQueue.put('') queryreturn = shared.sqlReturnQueue.get() shared.sqlLock.release() + str_broadcast_subscribers = '[Broadcast subscribers]' for row in queryreturn: msgid, toAddress, fromAddress, subject, received, message, read = row try: - if toAddress == '[Broadcast subscribers]': - toLabel = '[Broadcast subscribers]' + if toAddress == str_broadcast_subscribers: + toLabel = str_broadcast_subscribers else: toLabel = shared.config.get(toAddress, 'label') except: @@ -459,6 +472,27 @@ class MyForm(QtGui.QMainWindow): #self.setWindowState(self.windowState() & ~QtCore.Qt.WindowMinimized | QtCore.Qt.WindowActive) #self.activateWindow() + + # pointer to the application + #app = None + + # show the application window + def appIndicatorShow(self): + if self.actionShow == None: + return + if not self.actionShow.isChecked(): + self.actionShow.setChecked(True) + self.appIndicatorShowOrHideWindow() + + # unchecks the show item on the application indicator + def appIndicatorHide(self): + if self.actionShow == None: + return + if self.actionShow.isChecked(): + self.actionShow.setChecked(False) + self.appIndicatorShowOrHideWindow() + + # application indicator show or hide """# application indicator show or hide def appIndicatorShowBitmessage(self): #if self.actionShow == None: @@ -470,33 +504,53 @@ class MyForm(QtGui.QMainWindow): else: self.appIndicatorShowOrHideWindow()""" + # returns the index of the oldest unread message + def getUnreadMessageIndex(self): + shared.sqlLock.acquire() + shared.sqlSubmitQueue.put('''SELECT msgid, received, read FROM inbox where folder='inbox' ORDER BY received DESC ''') + shared.sqlSubmitQueue.put('') + queryreturn = shared.sqlReturnQueue.get() + shared.sqlLock.release() + i = 0 + index = 0 + for row in queryreturn: + msgid, received, read = row + if not read: + index = i + i = i + 1 + return index + + # Show the program window and select inbox tab + def appIndicatorInbox(self, mm_app, source_id): + self.appIndicatorShow() + # select inbox + self.ui.tabWidget.setCurrentIndex(0) + # select unread message + self.ui.tableWidgetInbox.selectRow(self.getUnreadMessageIndex()) + self.tableWidgetInboxItemClicked() + # Show the program window and select send tab def appIndicatorSend(self): - if not self.actionShow.isChecked(): - self.actionShow.setChecked(True) - self.appIndicatorShowOrHideWindow() + self.appIndicatorShow() self.ui.tabWidget.setCurrentIndex(1) # Show the program window and select subscriptions tab def appIndicatorSubscribe(self): - if not self.actionShow.isChecked(): - self.actionShow.setChecked(True) - self.appIndicatorShowOrHideWindow() + self.appIndicatorShow() self.ui.tabWidget.setCurrentIndex(4) # Show the program window and select the address book tab def appIndicatorAddressBook(self): - if not self.actionShow.isChecked(): - self.actionShow.setChecked(True) - self.appIndicatorShowOrHideWindow() + self.appIndicatorShow() self.ui.tabWidget.setCurrentIndex(5) # create application indicator - def createAppIndicator(self,app): - self.tray = QSystemTrayIcon(QtGui.QIcon("images/can-icon-24px.png"), app) + def appIndicatorInit(self,app): + self.tray = QSystemTrayIcon(QtGui.QIcon("images/can-icon-24px-red.png"), app) if sys.platform[0:3] == 'win': traySignal = "activated(QSystemTrayIcon::ActivationReason)" QtCore.QObject.connect(self.tray, QtCore.SIGNAL(traySignal), self.__icon_activated) + m = QMenu() self.actionStatus = QtGui.QAction('Not Connected',m,checkable=False) @@ -509,10 +563,10 @@ class MyForm(QtGui.QMainWindow): # show bitmessage self.actionShow = QtGui.QAction('Show Bitmessage',m,checkable=True) - self.actionShow.setChecked(True) + self.actionShow.setChecked(not shared.config.getboolean('bitmessagesettings', 'startintray')) self.actionShow.triggered.connect(self.appIndicatorShowOrHideWindow) if not sys.platform[0:3] == 'win': - m.addAction(self.actionShow) + m.addAction(self.actionShow) # Send actionSend = QtGui.QAction('Send',m,checkable=False) @@ -535,15 +589,133 @@ class MyForm(QtGui.QMainWindow): m.addAction(actionSeparator) # Quit - m.addAction("Quit", self.close) + m.addAction("Quit", self.quit) + self.tray.setContextMenu(m) self.tray.show() - if shared.config.getboolean('bitmessagesettings', 'startintray'): - #myapp.trayIcon.show()#This option seems to have been obsoleted by https://github.com/Bitmessage/PyBitmessage/pull/133/files - self.actionShow.setChecked(False) - self.appIndicatorShowOrHideWindow() - #if sys.platform[0:3] == 'win': - # myapp.setWindowFlags(Qt.ToolTip) + + # Ubuntu Messaging menu object + mmapp = None + + # is the operating system Ubuntu? + def isUbuntu(self): + for entry in platform.uname(): + if "Ubuntu" in entry: + return True + return False + + # returns the number of unread messages and subscriptions + def getUnread(self): + str_broadcast_subscribers = '[Broadcast subscribers]' + + unreadMessages = 0 + unreadSubscriptions = 0 + + shared.sqlLock.acquire() + shared.sqlSubmitQueue.put('''SELECT msgid, toaddress, read FROM inbox where folder='inbox' ''') + shared.sqlSubmitQueue.put('') + queryreturn = shared.sqlReturnQueue.get() + shared.sqlLock.release() + for row in queryreturn: + msgid, toAddress, read = row + + try: + if toAddress == str_broadcast_subscribers: + toLabel = str_broadcast_subscribers + else: + toLabel = shared.config.get(toAddress, 'label') + except: + toLabel = '' + if toLabel == '': + toLabel = toAddress + + if not read: + if toLabel == str_broadcast_subscribers: + # increment the unread subscriptions + unreadSubscriptions = unreadSubscriptions + 1 + else: + # increment the unread messages + unreadMessages = unreadMessages + 1 + return unreadMessages, unreadSubscriptions + + # show the number of unread messages and subscriptions on the messaging menu + def ubuntuMessagingMenuUnread(self, drawAttention): + unreadMessages, unreadSubscriptions = self.getUnread() + # unread messages + if unreadMessages > 0: + self.mmapp.append_source("Messages", None, "Messages (" + str(unreadMessages) + ")") + if drawAttention: + self.mmapp.draw_attention("Messages") + + # unread subscriptions + if unreadSubscriptions > 0: + self.mmapp.append_source("Subscriptions", None, "Subscriptions (" + str(unreadSubscriptions) + ")") + if drawAttention: + self.mmapp.draw_attention("Subscriptions") + + # initialise the Ubuntu messaging menu + def ubuntuMessagingMenuInit(self): + global withMessagingMenu + + # if this isn't ubuntu then don't do anything + if not self.isUbuntu(): + return + + # has messageing menu been installed + if not withMessagingMenu: + print 'WARNING: MessagingMenu is not available. Is libmessaging-menu-dev installed?' + return + + # create the menu server + if withMessagingMenu: + try: + self.mmapp = MessagingMenu.App(desktop_id='pybitmessage.desktop') + self.mmapp.register() + self.mmapp.connect('activate-source', self.appIndicatorInbox) + self.ubuntuMessagingMenuUnread(True) + except Exception: + withMessagingMenu = False + print 'WARNING: messaging menu disabled' + + # update the Ubuntu messaging menu + def ubuntuMessagingMenuUpdate(self, drawAttention): + global withMessagingMenu + + # if this isn't ubuntu then don't do anything + if not self.isUbuntu(): + return + + # has messageing menu been installed + if not withMessagingMenu: + print 'WARNING: messaging menu disabled or libmessaging-menu-dev not installed' + return + + # Remove previous messages and subscriptions entries, then recreate them + # There might be a better way to do it than this + if self.mmapp.has_source("Messages"): + self.mmapp.remove_source("Messages") + + if self.mmapp.has_source("Subscriptions"): + self.mmapp.remove_source("Subscriptions") + + # update the menu entries + self.ubuntuMessagingMenuUnread(drawAttention) + + # initialise the message notifier + def notifierInit(self): + global withMessagingMenu + if withMessagingMenu: + Notify.init('pybitmessage') + + # shows a notification + def notifierShow(self, title, subtitle): + global withMessagingMenu + if withMessagingMenu: + n = Notify.Notification.new(title, subtitle,'notification-message-email') + n.show() + return + # Show with tray + self.trayIcon.showMessage(title, subtitle, 1, 2000) def tableWidgetInboxKeyPressEvent(self,event): if event.key() == QtCore.Qt.Key_Delete: @@ -598,10 +770,7 @@ class MyForm(QtGui.QMainWindow): if shared.config.getboolean('bitmessagesettings', 'minimizetotray') and not 'darwin' in sys.platform: if event.type() == QtCore.QEvent.WindowStateChange: if self.windowState() & QtCore.Qt.WindowMinimized: - self.hide() - - #self.trayIcon.show() #This may have been obsoleted by https://github.com/Bitmessage/PyBitmessage/issues/135 - #self.hidden = True + self.appIndicatorHide() if 'win32' in sys.platform or 'win64' in sys.platform: self.setWindowFlags(Qt.ToolTip) elif event.oldState() & QtCore.Qt.WindowMinimized: @@ -688,27 +857,47 @@ class MyForm(QtGui.QMainWindow): elif len(shared.connectedHostsList) == 0: self.setStatusIcon('red') + # Indicates whether or not there is a connection to the Bitmessage network + connected = False + def setStatusIcon(self,color): + global withMessagingMenu #print 'setting status icon color' if color == 'red': self.ui.pushButtonStatusIcon.setIcon(QIcon(":/newPrefix/images/redicon.png")) shared.statusIconColor = 'red' - #if self.actionStatus != None: - self.actionStatus.setText('Not Connected') + # if the connection is lost then show a notification + if self.connected: + self.notifierShow('PyBitmessage','Connection lost') + self.connected = False + + if self.actionStatus != None: + self.actionStatus.setText('Not Connected') + self.tray.setIcon(QtGui.QIcon("images/can-icon-24px-red.png")) + self.trayIcon.show() if color == 'yellow': if self.statusBar().currentMessage() == 'Warning: You are currently not connected. Bitmessage will do the work necessary to send the message but it won\'t send until you connect.': self.statusBar().showMessage('') self.ui.pushButtonStatusIcon.setIcon(QIcon(":/newPrefix/images/yellowicon.png")) shared.statusIconColor = 'yellow' - #if self.actionStatus != None: - self.actionStatus.setText('Connected') + # if a new connection has been established then show a notification + if not self.connected: + self.notifierShow('PyBitmessage','Connected') + self.connected = True + + if self.actionStatus != None: + self.actionStatus.setText('Connected') + self.tray.setIcon(QtGui.QIcon("images/can-icon-24px-yellow.png")) if color == 'green': if self.statusBar().currentMessage() == 'Warning: You are currently not connected. Bitmessage will do the work necessary to send the message but it won\'t send until you connect.': self.statusBar().showMessage('') self.ui.pushButtonStatusIcon.setIcon(QIcon(":/newPrefix/images/greenicon.png")) shared.statusIconColor = 'green' - #if self.actionStatus != None: - self.actionStatus.setText('Connected') + self.connected = True + + if self.actionStatus != None: + self.actionStatus.setText('Connected') + self.tray.setIcon(QtGui.QIcon("images/can-icon-24px-green.png")) def updateSentItemStatusByHash(self,toRipe,textToDisplay): for i in range(self.ui.tableWidgetSent.rowCount()): @@ -1072,11 +1261,11 @@ class MyForm(QtGui.QMainWindow): if fromLabel == '': newItem = QtGui.QTableWidgetItem(unicode(fromAddress,'utf-8')) if shared.config.getboolean('bitmessagesettings', 'showtraynotifications'): - self.trayIcon.showMessage('New Message', 'New message from '+ fromAddress, 1, 2000) + self.notifierShow('New Message', 'From '+ fromAddress) else: newItem = QtGui.QTableWidgetItem(unicode(fromLabel,'utf-8')) if shared.config.getboolean('bitmessagesettings', 'showtraynotifications'): - self.trayIcon.showMessage('New Message', 'New message from '+fromLabel, 1, 2000) + self.notifierShow('New Message', 'From ' + fromLabel) newItem.setData(Qt.UserRole,str(fromAddress)) newItem.setFont(font) self.ui.tableWidgetInbox.setItem(0,1,newItem) @@ -1084,7 +1273,7 @@ class MyForm(QtGui.QMainWindow): newItem.setData(Qt.UserRole,unicode(message,'utf-8)')) newItem.setFont(font) self.ui.tableWidgetInbox.setItem(0,2,newItem) - newItem = myTableWidgetItem(unicode(strftime(shared.config.get('bitmessagesettings', 'timeformat'),localtime(int(time.time()))),'utf-8')) + newItem = myTableWidgetItem(unicode(strftime(shared.config.get('bitmessagesettings', 'timeformat'),localtime(int(time.time()))),'utf-8')) newItem.setData(Qt.UserRole,QByteArray(inventoryHash)) newItem.setData(33,int(time.time())) newItem.setFont(font) @@ -1096,6 +1285,7 @@ class MyForm(QtGui.QMainWindow): else: self.ui.textEditInboxMessage.setPlainText(self.ui.tableWidgetInbox.item(0,2).data(Qt.UserRole).toPyObject())""" self.ui.tableWidgetInbox.setSortingEnabled(True) + self.ubuntuMessagingMenuUpdate(True) def click_pushButtonAddAddressBook(self): self.NewSubscriptionDialogInstance = NewSubscriptionDialog(self) @@ -1400,25 +1590,43 @@ class MyForm(QtGui.QMainWindow): else: print 'new address dialog box rejected' - def closeEvent(self, event): + + # Quit selected from menu or application indicator + def quit(self): '''quit_msg = "Are you sure you want to exit Bitmessage?" reply = QtGui.QMessageBox.question(self, 'Message', quit_msg, QtGui.QMessageBox.Yes, QtGui.QMessageBox.No) - if reply == QtGui.QMessageBox.Yes: - event.accept() - else: - event.ignore()''' + if reply is QtGui.QMessageBox.No: + return + ''' shared.doCleanShutdown() - #self.trayIcon.hide() self.tray.hide() + # unregister the messaging system + if self.mmapp is not None: + self.mmapp.unregister() + self.trayIcon.hide() self.statusBar().showMessage('All done. Closing user interface...') - event.accept() - shared.printLock.acquire() - print 'Done. (passed event.accept())' - shared.printLock.release() os._exit(0) + # window close event + def closeEvent(self, event): + self.appIndicatorHide() + minimizeonclose = False + + try: + minimizeonclose = shared.config.getboolean('bitmessagesettings', 'minimizeonclose') + except Exception: + pass + + if minimizeonclose: + # minimize the application + event.ignore() + else: + # quit the application + event.accept() + self.quit() + def on_action_InboxMessageForceHtml(self): currentInboxRow = self.ui.tableWidgetInbox.currentRow() lines = self.ui.tableWidgetInbox.item(currentInboxRow,2).data(Qt.UserRole).toPyObject().split('\n') @@ -2045,12 +2253,21 @@ class UISignaler(QThread): else: sys.stderr.write('Command sent to UISignaler not recognized: %s\n' % command) - def run(): app = QtGui.QApplication(sys.argv) app.setStyleSheet("QStatusBar::item { border: 0px solid black }") myapp = MyForm() - if not shared.config.getboolean('bitmessagesettings', 'startintray'): + + if shared.config.getboolean('bitmessagesettings', 'startintray'): + if not myapp.isUbuntu(): + myapp.trayIcon.show() + if 'win32' in sys.platform or 'win64' in sys.platform: + myapp.setWindowFlags(Qt.ToolTip) + else: myapp.show() - myapp.createAppIndicator(app) + + myapp.appIndicatorInit(app) + myapp.ubuntuMessagingMenuInit() + myapp.notifierInit() + sys.exit(app.exec_()) diff --git a/src/bitmessageui.py b/src/bitmessageui.py index 20e5b973..a7c85206 100644 --- a/src/bitmessageui.py +++ b/src/bitmessageui.py @@ -515,7 +515,7 @@ class Ui_MainWindow(object): self.menuHelp.setTitle(QtGui.QApplication.translate("MainWindow", "Help", None, QtGui.QApplication.UnicodeUTF8)) self.actionImport_keys.setText(QtGui.QApplication.translate("MainWindow", "Import keys", None, QtGui.QApplication.UnicodeUTF8)) self.actionManageKeys.setText(QtGui.QApplication.translate("MainWindow", "Manage keys", None, QtGui.QApplication.UnicodeUTF8)) - self.actionExit.setText(QtGui.QApplication.translate("MainWindow", "Exit", None, QtGui.QApplication.UnicodeUTF8)) + self.actionExit.setText(QtGui.QApplication.translate("MainWindow", "Quit", None, QtGui.QApplication.UnicodeUTF8)) self.actionHelp.setText(QtGui.QApplication.translate("MainWindow", "Help", None, QtGui.QApplication.UnicodeUTF8)) self.actionAbout.setText(QtGui.QApplication.translate("MainWindow", "About", None, QtGui.QApplication.UnicodeUTF8)) self.actionSettings.setText(QtGui.QApplication.translate("MainWindow", "Settings", None, QtGui.QApplication.UnicodeUTF8)) diff --git a/src/bitmessageui.ui b/src/bitmessageui.ui index e1b1e319..cef8cd69 100644 --- a/src/bitmessageui.ui +++ b/src/bitmessageui.ui @@ -979,7 +979,7 @@ p, li { white-space: pre-wrap; } - Exit + Quit diff --git a/src/images/can-icon-24px-green.png b/src/images/can-icon-24px-green.png new file mode 100644 index 00000000..5e7e52ec Binary files /dev/null and b/src/images/can-icon-24px-green.png differ diff --git a/src/images/can-icon-24px-red.png b/src/images/can-icon-24px-red.png new file mode 100644 index 00000000..942daa7e Binary files /dev/null and b/src/images/can-icon-24px-red.png differ diff --git a/src/images/can-icon-24px-yellow.png b/src/images/can-icon-24px-yellow.png new file mode 100644 index 00000000..e7eb0589 Binary files /dev/null and b/src/images/can-icon-24px-yellow.png differ diff --git a/src/singleton.py b/src/singleton.py new file mode 100644 index 00000000..7ecca3b7 --- /dev/null +++ b/src/singleton.py @@ -0,0 +1,61 @@ +#! /usr/bin/env python + +import sys +import os +import errno +import tempfile +from multiprocessing import Process + + +class singleinstance: + """ + Implements a single instance application by creating a lock file based on the full path to the script file. + + This is based upon the singleton class from tendo https://github.com/pycontribs/tendo + which is under the Python Software Foundation License version 2 + """ + def __init__(self, flavor_id=""): + import sys + self.initialized = False + basename = os.path.splitext(os.path.abspath(sys.argv[0]))[0].replace("/", "-").replace(":", "").replace("\\", "-") + '-%s' % flavor_id + '.lock' + self.lockfile = os.path.normpath(tempfile.gettempdir() + '/' + basename) + + if sys.platform == 'win32': + try: + # file already exists, we try to remove (in case previous execution was interrupted) + if os.path.exists(self.lockfile): + os.unlink(self.lockfile) + self.fd = os.open(self.lockfile, os.O_CREAT | os.O_EXCL | os.O_RDWR) + except OSError: + type, e, tb = sys.exc_info() + if e.errno == 13: + print 'Another instance of this application is already running' + sys.exit(-1) + print(e.errno) + raise + else: # non Windows + import fcntl + self.fp = open(self.lockfile, 'w') + try: + fcntl.lockf(self.fp, fcntl.LOCK_EX | fcntl.LOCK_NB) + except IOError: + print 'Another instance of this application is already running' + sys.exit(-1) + self.initialized = True + + def __del__(self): + import sys + if not self.initialized: + return + try: + if sys.platform == 'win32': + if hasattr(self, 'fd'): + os.close(self.fd) + os.unlink(self.lockfile) + else: + import fcntl + fcntl.lockf(self.fp, fcntl.LOCK_UN) + if os.path.isfile(self.lockfile): + os.unlink(self.lockfile) + except Exception, e: + sys.exit(-1)