├── .gitignore ├── README.md ├── res ├── mail-black.png ├── mail.png ├── popup.wav ├── popup2.wav └── thunderbird.png ├── settings.ini ├── tbtray.py ├── tbtrayui.py └── tbtrayui.ui /.gitignore: -------------------------------------------------------------------------------- 1 | log.txt 2 | .idea/ 3 | .vscode/ 4 | __pycache__/ -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # TBtray # 2 | 3 | ## A Thunderbird tray addon for linux built with PyQt5 ## 4 | 5 | I only built it because we did'nt have any TB tray integration after Thunderbird 60+. 6 | Keep in mind im just a hobbyist, Im doing this to try and learn a little about using git and github. 7 | 8 | 9 | ## Installation ## 10 | 11 | you will need to install these packages on your system first 12 | 13 | python 3.6+ 14 | 15 | xdotool 16 | wmctrl [these 2 control the window manager] 17 | python3-pyqt5.qtmultimedia (or qt5-multimedia) [One of these are for the sound] 18 | 19 | Then clone the repo 20 | git clone https://github.com/JackDinn/tbtray.git 21 | 22 | Run with 23 | tbtray/tbtray.py 24 | 25 | 26 | 27 | After installing and running TBtray select your INBOX.msf files for your accounts. 28 | Find (or manually enter) the path to your INBOX.msf files in the top bar of the settings and click "add" to put them 29 | into your profile list box. 30 | 31 | example of INBOX.msf :- 32 | /home/user/.thunderbird/tzvg3gbn.default/ImapMail/imap.gmail.com/INBOX.msf 33 | 34 | 35 | #### **_You can not use the unified (Smart-mail) inbox._** #### 36 | This does not mean you can not use the unified mail box with Thunderbird, just that you will need to setup each account individually in TBtray. 37 | 38 | 39 | #### Features :- #### 40 | 41 | * Minimize to tray 42 | * Click tray icon to show/hide TB 43 | * Show unread count on tray icon 44 | * set icons (one for default (no unread), another for the notification 45 | * Allow TBtray to take over the popup notification & sound from TB 46 | * You can also click on the popup notification to show the TB window 47 | * You can also use the shortcut launch icon to hide/show TB (if you have made one) 48 | * Set opacity of popup 49 | * popup duration control 50 | * Popup option to Show favicons alongside individual emails (these are scraped once & then cached locally) 51 | * Very low idle CPU & Memory. 52 | 53 | 54 | 55 | ![Tray icon](https://i.imgur.com/Kocpyo8.png) 56 | 57 | ![Popup window](https://i.imgur.com/0AnneUK.png) 58 | 59 | ![Basic Settings](https://i.imgur.com/pDZED9v.png) 60 | 61 | 62 | ### General usage ### 63 | TBtray executes TB so i advise creating a launcher that runs TBtray to replace your TB launcher. 64 | 65 | It can sometimes take upto 1 second for TBtray to hide TB, this is unavoidable. 66 | 67 | You can close both TBtray and TB together via the tray icon. 68 | 69 | You can run TBtray after TB is already running and it will work but you may need to "synchronize" it by clicking the tray icon. 70 | 71 | I can not figure a way to intercept the TB close signal so i can not have TBtray minimize TB when you click close on TB. The best i can do is to close both TB and TBtray if you close TB. 72 | 73 | TBtray (PyQt5) does not seem to work well on Wayland. 74 | 75 | 76 | ### Removal of TBtray ### 77 | just delete the tbtray folder and the settings folder found at ~/.config/tbtray 78 | -------------------------------------------------------------------------------- /res/mail-black.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JackDinn/tbtray/21ce358b46ffcea9d42838481f3646e09df5dcdc/res/mail-black.png -------------------------------------------------------------------------------- /res/mail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JackDinn/tbtray/21ce358b46ffcea9d42838481f3646e09df5dcdc/res/mail.png -------------------------------------------------------------------------------- /res/popup.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JackDinn/tbtray/21ce358b46ffcea9d42838481f3646e09df5dcdc/res/popup.wav -------------------------------------------------------------------------------- /res/popup2.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JackDinn/tbtray/21ce358b46ffcea9d42838481f3646e09df5dcdc/res/popup2.wav -------------------------------------------------------------------------------- /res/thunderbird.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JackDinn/tbtray/21ce358b46ffcea9d42838481f3646e09df5dcdc/res/thunderbird.png -------------------------------------------------------------------------------- /settings.ini: -------------------------------------------------------------------------------- 1 | [popup] 2 | opacity = 100 3 | on = 1 4 | soundpath = res/popup.wav 5 | soundon = 1 6 | x = 1875 7 | favicons = 1 8 | duration = 10 9 | fixedwidth = 1 10 | top = 1 11 | 12 | [ticks] 13 | minimizetotray = 1 14 | showcount = 1 15 | 16 | [icons] 17 | default = res/thunderbird.png 18 | notify = res/mail.png 19 | colour = #ffffff 20 | 21 | [profiles] 22 | 23 | 24 | -------------------------------------------------------------------------------- /tbtray.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import configparser 3 | import getpass 4 | import mailbox 5 | import os 6 | import re 7 | import subprocess 8 | import sys 9 | import urllib.request 10 | from email.header import decode_header, make_header 11 | from pathlib import Path 12 | from shutil import copyfile 13 | from time import strftime 14 | 15 | from PyQt5 import QtCore, QtGui, QtWidgets 16 | from PyQt5.QtCore import Qt, QTimer 17 | from PyQt5.QtGui import QColor, QFont, QFontMetrics, QPainter, QPixmap 18 | from PyQt5.QtMultimedia import QSound 19 | from PyQt5.QtWidgets import QAction, QColorDialog, QFileDialog, QMenu, QSystemTrayIcon 20 | 21 | import tbtrayui 22 | 23 | 24 | def close(): 25 | os.system('pkill thunderbird') 26 | sys.exit(0) 27 | 28 | 29 | def checkvisable(): 30 | winname = ' - Mozilla Thunderbird' 31 | try: 32 | # check 3 times because sometimes when there are lots of child windows open it misses the fact that the TB window is actually visible. 33 | for two in range(3): 34 | out = subprocess.run( 35 | ["xdotool", "search", "--all", "--onlyvisible", "--maxdepth", "3", "--limit", "1", "--name", winname], 36 | stdout=subprocess.PIPE).stdout.decode('UTF-8') 37 | log('check is TB window visable ' + out) 38 | if out: return True 39 | except: 40 | pass 41 | return False 42 | 43 | 44 | def log(tex=''): 45 | try: 46 | tail = subprocess.run(["tail", "-n", "500", "log.txt"], stdout=subprocess.PIPE).stdout.decode('UTF-8') 47 | with open('log.txt', 'w+') as xx: 48 | xx.write(tail) 49 | tim = strftime("%y-%m-%d %H:%M:%S") 50 | with open('log.txt', 'a+') as qq: 51 | qq.write(tim + ' ' + tex + '\n') 52 | except: 53 | pass 54 | 55 | 56 | def getfavicon(url): 57 | path = str(Path.home()) + '/.config/tbtray/icons/' 58 | iconpath = path + url + '.ico' 59 | if Path.is_file(Path(iconpath)): 60 | log('Local icon ' + iconpath) 61 | return iconpath 62 | try: 63 | log('favicon URL https://www.google.com/s2/favicons?domain=' + url) 64 | data = urllib.request.urlopen('https://www.google.com/s2/favicons?domain=' + url) 65 | icon = data.read() 66 | if icon == b'\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x10\x00\x00\x00\x10\x08\x06\x00\x00\x00\x1f' \ 67 | b'\xf3\xffa\x00\x00\x01\xb3IDATx\x01b\xf8\xff\xff?E\x18\xce\xf0\x8b\x9c\xc7\xe9\xe8;C\xb6\xaai[PI\xed\xe6\xbd\xc9\xb9' \ 68 | b'\xab>\x86\xc4\x03\xe8\xa6\x86\xed\xda\x82(\xf81\xefc\xde\xf4b\x18\xdb\xb6\xedd\x1a\xdb\xb6m\xdb\xb6m\';]\x83\xee\xd5W{' \ 69 | b'\xad\xe3\xaa\xda\xaaS\xf8\x83\xc3\xd2\xb9\xf2\xc1+\xb8\xa9\'\x80}\x03\x06X\xce\x13\xe4\xff\xaa\xac\x7f.~\xf5\xf1\x16N\x15' \ 70 | b'\xab1\xc9\xdd\xd7E\xb5\xb3\x94S1E\xb8\x0eO\x1fPX\\\xc7\xb5\x99C\xf9\xaa\x8bo]<\xb0\xe0\x08\x01\xa8\xba\xf8\xd6\xc7[\xb9T\x1d' \ 71 | b'\xa6d\x0cRm\xfb\nu\x0cn\xd1\xd5\xdd\x1b\xed\x9f<\x10\xe2\xf4\xf2\x85\xf0\x8e\x89\x1c:\xfb\xd4\xc5\x83#\x04P\x9a\x99c\xf9*2\x81' \ 72 | b'\x8c\xa3\xa9g\x1d\x87\x10\xf8\xfe!\xea\x1d\xdb\xa5>v\x98:\x94\xad\x82#\x04\xd0\x1fJ\x04\x08d\xf9xx\xfe"\x04\xaex\x9e_=\xa3\xe0' \ 73 | b'\xe8\xf6k\xcf\xa0\xc6\x1e!\x80!!;@\xfa\xc8\xbc\x82\x95\xad+\xc2{`\xcd\x1d+\x1e\x84\x80\xda\xb4\xe0gk\xff\x8e\x10\xe8\x13@N\x96' \ 74 | b'\x03-@\x00X\x95q\xfe\x8f\x86\x00\x86\xc4K\xe5\x03\xd4\'\x80\xb9@@)\x0b\xb0\xd5\xa1\x05\x92\x03\xcf\xfc\xe0\xe2\x08&\x8cw\xd8\xc6' \ 75 | b'\x83\xc6\x10\xc3\xd9\x10%\xbe\xce<&\x16N\x88\x87_x\xcb\xb5G`c\x8f\xbcF%L\x02eC\x0280H`L\xec\xd9\x1a#Z\x95\x1aF\x82\xc3\x98\x17\x0ee' \ 76 | b'\x11\xf4\xca\xc9\xb8\x1f\x06\xd9\xaeL\xd7H\xdc\xca\xceL\x04&a\xff\xc35\xc00\r\xec\x9cV4N\x81\x91m\xd7\xc8\x0c\xb2\x8e\x95\xe5' \ 77 | b'\x9f\t\xa5\xc1$\xd8\xb3\xca\xa4\xe0\x07\xd3\xfe\x1b(\xc0\x80b{\xaa\x9b\xb7\x078a\xc9L\x14a\x00BM\xf5\xdf\xed\xe70\xb1\x00\x00' \ 78 | b'\x00\x00IEND\xaeB`\x82': 79 | os.symlink(os.getcwd() + '/res/thunderbird.png', iconpath) 80 | return 'res/thunderbird.png' 81 | with open(iconpath, "wb") as f: 82 | f.write(icon) 83 | return iconpath 84 | except: 85 | log('Failed google scrape ' + url) 86 | return 'res/thunderbird.png' 87 | 88 | 89 | def checksettings(): 90 | my_dir = Path(str(Path.home()) + '/.config/tbtray') 91 | if not my_dir.is_dir(): my_dir.mkdir() 92 | if not Path(str(Path.home()) + '/.config/tbtray/icons').is_dir(): Path.mkdir( 93 | Path(str(Path.home()) + '/.config/tbtray/icons')) 94 | my_file = Path(str(Path.home()) + '/.config/tbtray/settings.ini') 95 | if not my_file.is_file(): copyfile('settings.ini', str(my_file)) 96 | config_new = configparser.ConfigParser() 97 | config_old = configparser.ConfigParser() 98 | config_new.read('settings.ini') 99 | config_old.read(str(my_file)) 100 | for xx in config_new.sections(): 101 | if xx == 'profiles': continue 102 | if not config_old.__contains__(xx): 103 | config_old.add_section(xx) 104 | for hh in config_new[xx]: 105 | if not config_old[xx].__contains__(hh): 106 | config_old[xx][hh] = config_new[xx][hh] 107 | with open(my_file, 'w') as configfile: 108 | config_old.write(configfile) 109 | configfile.close() 110 | 111 | 112 | def readmessage(path): 113 | from_text = [] 114 | subject_text = [] 115 | date_text = [] 116 | messageid_text = [] 117 | for gg in path: 118 | try: 119 | if not os.path.isfile(gg): continue 120 | text = subprocess.run(["tail", "-n", "10000", gg], stdout=subprocess.PIPE).stdout.decode('UTF-8', "ignore") 121 | with open('/tmp/tbtraydata', 'w+') as xyz: 122 | xyz.write(text) 123 | fr = mailbox.mbox('/tmp/tbtraydata') 124 | if os.path.isfile('/tmp/tbtraydata'): os.remove('/tmp/tbtraydata') 125 | for q in fr: 126 | try: 127 | messageid_text.append(str(make_header(decode_header(q['message-ID'])))) 128 | date_text.append(str(make_header(decode_header(q['date'])))) 129 | from_text.append( 130 | str(make_header(decode_header(q['from']))).replace('<', '<').replace('>', '>')) 131 | subject_text.append(str(make_header(decode_header(q['subject']))).replace('\r\n', '
')) 132 | except: 133 | continue 134 | except: 135 | continue 136 | return {'from': from_text, 'subject': subject_text, 'date': date_text, 'messageid': messageid_text} 137 | 138 | 139 | class TextBrowser(QtWidgets.QTextBrowser): 140 | windowid: int 141 | 142 | def __init__(self, parent=None): 143 | super(TextBrowser, self).__init__(parent) 144 | self.windowid = 0 145 | self.INTRAY = False 146 | self.hideme = False 147 | self.height = 100 148 | self.width = 100 149 | self.fixedwidth = True 150 | self.document().contentsChanged.connect(self.sizechange) 151 | 152 | def sizechange(self): 153 | docwidth = 300 154 | docheight = self.document().size().height() 155 | if self.fixedwidth: docwidth = 300 156 | if not self.fixedwidth: docwidth = self.document().size().width() 157 | self.height = int(docheight) 158 | self.width = int(docwidth) 159 | self.setGeometry(5, 5, self.width + 10, self.height + 5) 160 | 161 | def mouseReleaseEvent(self, event): 162 | subprocess.run(["xdotool", "windowmap", self.windowid]) 163 | subprocess.run(['wmctrl', '-i', '-r', str(self.windowid), '-b', 'remove,skip_taskbar']) 164 | subprocess.run(["xdotool", "windowactivate", self.windowid]) 165 | self.hideme = True 166 | self.INTRAY = True 167 | 168 | 169 | class Popup(QtWidgets.QDialog): 170 | 171 | # noinspection PyArgumentList 172 | def __init__(self): 173 | super(self.__class__, self).__init__() 174 | self.setObjectName("formpopup") 175 | self.setGeometry(1185, 40, 430, 993) 176 | self.setMinimumSize(QtCore.QSize(0, 0)) 177 | self.setStatusTip("") 178 | self.setWindowTitle("formpopup") 179 | self.setObjectName('formpopup') 180 | self.textBrowser = TextBrowser(self) 181 | self.textBrowser.setGeometry(5, 5, 150, 100) 182 | self.textBrowser.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff) 183 | self.textBrowser.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) 184 | self.closebutton = QtWidgets.QPushButton(self) 185 | self.closebutton.setText('X') 186 | self.closebutton.setGeometry(404, 8, 20, 20) 187 | self.closebutton.setStyleSheet('color:red;') 188 | self.closebutton.clicked.connect(self.clicked) 189 | self.screenheight = getscreenheight() 190 | self.top = True 191 | self.duration = 10 192 | self.browsertext = '' 193 | self.favicons = True 194 | self.popupon = True 195 | self.sound = QSound("res/popup.wav") 196 | self.soundon = True 197 | self.popup_timer = QTimer(self) 198 | self.popup_timer.setSingleShot(True) 199 | self.popup_timer.timeout.connect(self.timer) 200 | self.popup_timer2 = QTimer(self) 201 | self.popup_timer2.setInterval(1000) 202 | self.popup_timer2.timeout.connect(self.timer2) 203 | self.setWindowFlags(Qt.FramelessWindowHint) 204 | self.setWindowFlags(Qt.X11BypassWindowManagerHint) 205 | self.setWindowOpacity(0.90) 206 | self.xpos = 1485 207 | self.shownmessages = [] 208 | 209 | def fire(self, profiles, firstrun=False): 210 | if self.popupon or self.soundon: 211 | popprofiles = [] 212 | fileexists = False 213 | for ss in range(len(profiles)): 214 | popprofiles.append(profiles[ss].replace('INBOX.msf', 'INBOX')) 215 | if os.path.isfile(popprofiles[ss]): fileexists = True 216 | if fileexists: 217 | mailinfo = readmessage(popprofiles) 218 | up = 0 219 | for mc in range(len(mailinfo['messageid'])): 220 | if self.shownmessages.__contains__(mailinfo['messageid'][up]): 221 | mailinfo['from'].pop(up) 222 | mailinfo['subject'].pop(up) 223 | mailinfo['date'].pop(up) 224 | mailinfo['messageid'].pop(up) 225 | else: 226 | up += 1 227 | for fr in mailinfo['messageid']: self.shownmessages.append(fr) 228 | if not firstrun and len(mailinfo['messageid']) > 0: 229 | if not self.isVisible(): 230 | self.browsertext = '' 231 | for x in range(len(mailinfo['messageid'])): 232 | log('parsed from ' + mailinfo['from'][x - 1]) 233 | log('parsed subj ' + mailinfo['subject'][x - 1]) 234 | fromx = mailinfo['from'][x - 1] 235 | subject = mailinfo['subject'][x - 1] 236 | if self.favicons: 237 | fromxy = fromx + '&' 238 | log('fromxy ' + fromxy) 239 | fav = re.findall('@\S*?\.?([\w|-]*(\.\w{2,3})?\.\w{2,3})&', fromxy) 240 | if len(fav) > 0: 241 | log('get favicon ' + fav[0][0]) 242 | icon = getfavicon(fav[0][0]) 243 | else: 244 | icon = 'res/thunderbird.png' 245 | self.browsertext += '

  ' + fromx + '

' + subject 246 | else: 247 | self.browsertext += '

' + fromx + '

' + subject 248 | if self.popupon: self.show() 249 | if self.soundon: self.sound.play() 250 | self.textBrowser.clear() 251 | self.textBrowser.setText(self.browsertext) 252 | if self.top: 253 | self.setGeometry(self.xpos - self.textBrowser.width, 40, self.textBrowser.width + 20, 254 | self.textBrowser.height + 15) 255 | else: 256 | self.setGeometry(self.xpos - self.textBrowser.width, 257 | self.screenheight - 55 - self.textBrowser.height, self.textBrowser.width + 20, 258 | self.textBrowser.height + 15) 259 | self.closebutton.setGeometry(self.textBrowser.width - 4, 8, 20, 20) 260 | self.popup_timer.start(self.duration * 1000) 261 | self.popup_timer2.start() 262 | 263 | def timer2(self): 264 | if self.textBrowser.hideme: 265 | self.popup_timer2.stop() 266 | self.textBrowser.hideme = False 267 | self.hide() 268 | 269 | def timer(self): 270 | self.popup_timer2.stop() 271 | self.hide() 272 | 273 | def clicked(self): 274 | self.popup_timer2.stop() 275 | self.hide() 276 | 277 | 278 | def getscreenheight(): 279 | out = subprocess.run(["xrandr"], stdout=subprocess.PIPE).stdout.decode('UTF-8') 280 | matches = re.findall('\d*x(\d*).*?\*', out) 281 | if matches: 282 | log('get screen height ' + matches[0]) 283 | return int(matches[0]) 284 | else: 285 | log('get screen height ERROR') 286 | return 1080 287 | 288 | 289 | class MainApp(QtWidgets.QDialog, tbtrayui.Ui_Form): 290 | 291 | def __init__(self): 292 | super(self.__class__, self).__init__() 293 | os.system('thunderbird > /dev/null 2>&1 &') 294 | stdout = subprocess.run(["pgrep", "-fc", "tbtray.py"], stdout=subprocess.PIPE).stdout.decode('UTF-8') 295 | if int(stdout) > 1: 296 | bob = open('/tmp/tbpassover', 'x') 297 | bob.close() 298 | log('make tmp passover file & close') 299 | sys.exit(0) 300 | os.chdir(os.path.dirname(os.path.realpath(__file__))) 301 | self.my_settings_file = Path(str(Path.home()) + '/.config/tbtray/settings.ini') 302 | log('') 303 | log('TBtray started ################################################ ') 304 | self.matches = 0 305 | self.lastmtime = 0 306 | self.timetriggercheck = QTimer(self) 307 | self.tray_icon = QSystemTrayIcon(self) 308 | self.INTRAY = False 309 | self.windowid = 0 310 | self.setupUi(self) 311 | self.profiles = [] 312 | self.badprofile = True 313 | self.defaulticon = self.lineedit_defulticon.text() 314 | self.notifyicon = self.lineedit_notifyicon.text() 315 | checksettings() 316 | config = configparser.ConfigParser() 317 | config.read(self.my_settings_file) 318 | self.radioButton_top.setChecked(bool(int(config['popup']['top']))) 319 | self.radioButton_bottom.setChecked(not bool(int(config['popup']['top']))) 320 | self.checkBox_fixedwidth.setChecked(bool(int(config['popup']['fixedwidth']))) 321 | self.spinBox_displaytime.setValue(int(config['popup']['duration'])) 322 | self.checkBox_favicons.setChecked(bool(int(config['popup']['favicons']))) 323 | self.horizontalSlider_opacity.setValue(int(config['popup']['opacity'])) 324 | self.checkBox_popup.setChecked(bool(int(config['popup']['on']))) 325 | self.lineEdit_notifysound.setText(config['popup']['soundpath']) 326 | self.checkBox_notifysound.setChecked(bool(int(config['popup']['soundon']))) 327 | self.spinBox_xpos.setValue(int(config['popup']['x'])) 328 | self.colour = config['icons']['colour'] 329 | self.colour_pre = config['icons']['colour'] 330 | self.label_colour.setStyleSheet('color: ' + self.colour) 331 | self.checkbox_showcount.setChecked(bool(int(config['ticks']['showcount']))) 332 | self.checkbox_minimizetotray.setChecked(bool(int(config['ticks']['minimizetotray']))) 333 | self.defaulticon = config['icons']['default'] 334 | self.lineedit_defulticon.setText(config['icons']['default']) 335 | self.notifyicon = config['icons']['notify'] 336 | self.lineedit_notifyicon.setText(config['icons']['notify']) 337 | for value in config['profiles']: 338 | self.profiles.append(config['profiles'][str(value)]) 339 | self.listWidget.addItem(config['profiles'][str(value)]) 340 | self.testforprofile() 341 | self.popup = Popup() 342 | self.popup_test = Popup() 343 | self.actionsetup() 344 | self.timersetup() 345 | 346 | def actionsetup(self): 347 | self.label_accountwarrning.setText('') 348 | self.popup.top = self.radioButton_top.isChecked() 349 | self.popup.textBrowser.fixedwidth = self.checkBox_fixedwidth.isChecked() 350 | if self.popup.textBrowser.fixedwidth: self.popup.textBrowser.setLineWrapMode(QtWidgets.QTextBrowser.WidgetWidth) 351 | if not self.popup.textBrowser.fixedwidth: self.popup.textBrowser.setLineWrapMode(QtWidgets.QTextBrowser.NoWrap) 352 | self.popup.duration = self.spinBox_displaytime.value() 353 | self.popup.favicons = self.checkBox_favicons.isChecked() 354 | self.popup.setWindowOpacity(float(self.horizontalSlider_opacity.value() / 100)) 355 | self.popup.xpos = self.spinBox_xpos.value() 356 | self.popup.popupon = self.checkBox_popup.isChecked() 357 | self.popup.soundon = self.checkBox_notifysound.isChecked() 358 | self.popup.sound = QSound(self.lineEdit_notifysound.text()) 359 | self.tray_icon.setIcon(QtGui.QIcon(self.defaulticon)) 360 | self.tray_icon.setToolTip('TBtray') 361 | action_hideshow = QAction("Hide/Show", self) 362 | action_settings = QAction("Settings", self) 363 | action = QAction("Exit", self) 364 | tray_menu = QMenu() 365 | tray_menu.addAction(action_hideshow) 366 | tray_menu.addAction(action_settings) 367 | tray_menu.addAction(action) 368 | self.tray_icon.setContextMenu(tray_menu) 369 | action.triggered.connect(close) 370 | action_hideshow.triggered.connect(self.iconmenushowhide) 371 | action_settings.triggered.connect(self.settings) 372 | self.toolButton_firepopup.clicked.connect(self.func_toolbutton_firepopup) 373 | self.toolButton_notifysound.clicked.connect(self.func_toolbutton_notifysound) 374 | self.tray_icon.activated.connect(self.iconclick) 375 | self.pushButton_cancel.clicked.connect(self.cancel) 376 | self.pushButton_ok.clicked.connect(self.ok) 377 | self.toolButton_profilepath.clicked.connect(self.selectfile) 378 | self.pushButton_add.clicked.connect(self.func_pushbutton_add) 379 | self.pushButton_remove.clicked.connect(self.func_pushbutton_remove) 380 | self.checkbox_minimizetotray.clicked.connect(self.func_minimizetotrayclicked) 381 | self.toolButton_defaulticon.clicked.connect(self.func_defaulticon) 382 | self.toolButton_notifyicon.clicked.connect(self.func_notifyicon) 383 | self.pushButton_colourpicker.clicked.connect(self.func_colourpicker) 384 | self.tray_icon.show() 385 | if self.badprofile: self.tray_icon.showMessage('TBtray Profile Warning', 'Please setup account profiles', 386 | QSystemTrayIcon.Critical) 387 | self.popup.fire(self.profiles, True) 388 | 389 | def func_toolbutton_firepopup(self): 390 | self.popup_test.textBrowser.windowid = self.windowid 391 | if not self.popup_test.isVisible(): 392 | self.popup_test.browsertext = '' 393 | icon = 'res/thunderbird.png' 394 | self.popup_test.sound = QSound(self.lineEdit_notifysound.text()) 395 | if self.checkBox_favicons.isChecked(): 396 | self.popup_test.browsertext += '

  Mail Info:- From address

This is a test message. Lorem ipsum dolor sit amet, vivamus platea faucibus sed per penatibus.' 397 | else: 398 | self.popup_test.browsertext += '

Mail Info:- From address

This is a test message. Lorem ipsum dolor sit amet, vivamus platea faucibus sed per penatibus.' 399 | if self.checkBox_popup.isChecked(): self.popup_test.show() 400 | self.popup_test.setWindowOpacity(float(self.horizontalSlider_opacity.value() / 100)) 401 | self.popup_test.textBrowser.fixedwidth = self.checkBox_fixedwidth.isChecked() 402 | if self.popup_test.textBrowser.fixedwidth: self.popup_test.textBrowser.setLineWrapMode( 403 | QtWidgets.QTextBrowser.WidgetWidth) 404 | if not self.popup_test.textBrowser.fixedwidth: self.popup_test.textBrowser.setLineWrapMode( 405 | QtWidgets.QTextBrowser.NoWrap) 406 | self.popup_test.textBrowser.clear() 407 | self.popup_test.textBrowser.setText(self.popup_test.browsertext) 408 | if self.radioButton_top.isChecked(): 409 | self.popup_test.setGeometry(self.spinBox_xpos.value() - self.popup_test.textBrowser.width, 40, 410 | self.popup_test.textBrowser.width + 20, self.popup_test.textBrowser.height + 15) 411 | else: 412 | self.popup_test.setGeometry(self.spinBox_xpos.value() - self.popup_test.textBrowser.width, 413 | self.popup_test.screenheight - 55 - self.popup_test.textBrowser.height, 414 | self.popup_test.textBrowser.width + 20, self.popup_test.textBrowser.height + 15) 415 | self.popup_test.closebutton.setGeometry(self.popup_test.textBrowser.width - 4, 8, 20, 20) 416 | self.popup_test.popup_timer.start(self.spinBox_displaytime.value() * 1000) 417 | self.popup_test.popup_timer2.start() 418 | if self.checkBox_notifysound.isChecked(): self.popup_test.sound.play() 419 | 420 | def func_toolbutton_notifysound(self): 421 | x = QFileDialog.getOpenFileName(self, 'Select Notify Sound File', '/home/' + getpass.getuser())[0] 422 | if x: self.lineEdit_notifysound.setText(x) 423 | 424 | def func_colourpicker(self): 425 | x = QColorDialog.getColor(QColor(self.colour)) 426 | if x.isValid(): 427 | self.label_colour.setStyleSheet('color: ' + x.name()) 428 | self.colour_pre = x.name() 429 | 430 | def func_defaulticon(self): 431 | x = \ 432 | QFileDialog.getOpenFileName(self, 'Select Default Icon', 'res/', 433 | "Icons .png .ico .svg (*.png *.ico *.svg)")[0] 434 | if x: self.lineedit_defulticon.setText(x) 435 | 436 | def func_notifyicon(self): 437 | x = QFileDialog.getOpenFileName(self, 'Select Notify Icon', 'res/', "Icons .png .ico .svg (*.png *.ico *.svg)")[ 438 | 0] 439 | if x: self.lineedit_notifyicon.setText(x) 440 | 441 | def func_minimizetotrayclicked(self): 442 | if not self.checkbox_minimizetotray.isChecked(): 443 | subprocess.run(['wmctrl', '-i', '-r', str(self.windowid), '-b', 'remove,skip_taskbar']) 444 | 445 | def func_pushbutton_add(self): 446 | self.listWidget.addItem(self.editline_profilepath.text()) 447 | self.testforprofile() 448 | 449 | def func_pushbutton_remove(self): 450 | self.listWidget.takeItem(self.listWidget.currentRow()) 451 | self.testforprofile() 452 | 453 | def testforprofile(self): 454 | try: 455 | if self.listWidget.count() == 0: raise Exception() 456 | for value in range(self.listWidget.count()): 457 | vv = open(self.listWidget.item(value).text(), 'r') 458 | vv.close() 459 | self.lastmtime = 0 460 | self.label_accountwarrning.hide() 461 | self.badprofile = False 462 | except: 463 | self.label_accountwarrning.setText('ERROR! Please Fix Account List') 464 | self.label_accountwarrning.setStyleSheet('color: red') 465 | self.tabWidget.setCurrentIndex(1) 466 | self.label_accountwarrning.show() 467 | self.badprofile = True 468 | 469 | def timersetup(self): 470 | self.timetriggercheck.timeout.connect(self.fire) 471 | self.timetriggercheck.start(1000) 472 | 473 | def selectfile(self): 474 | x = \ 475 | QFileDialog.getOpenFileName(self, 'Select Profile .msf File', 476 | '/home/' + getpass.getuser() + '/.thunderbird/', 477 | "INBOX.msf(INBOX.msf)")[0] 478 | if x: self.editline_profilepath.setText(x) 479 | 480 | def cancel(self): 481 | self.testforprofile() 482 | self.hide() 483 | self.checkBox_fixedwidth.setChecked(self.popup.textBrowser.fixedwidth) 484 | if self.popup.textBrowser.fixedwidth: 485 | self.popup.textBrowser.setLineWrapMode(QtWidgets.QTextBrowser.WidgetWidth) 486 | if not self.popup.textBrowser.fixedwidth: 487 | self.popup.textBrowser.setLineWrapMode(QtWidgets.QTextBrowser.NoWrap) 488 | self.checkBox_favicons.setChecked(self.popup.favicons) 489 | self.label_colour.setStyleSheet('color: ' + self.colour) 490 | self.radioButton_top.setChecked(self.popup.top) 491 | self.radioButton_bottom.setChecked(not self.popup.top) 492 | self.spinBox_xpos.setValue(self.popup.xpos) 493 | self.checkBox_popup.setChecked(self.popup.popupon) 494 | self.lineEdit_notifysound.setText(self.popup.sound.fileName()) 495 | self.checkBox_notifysound.setChecked(self.popup.soundon) 496 | self.horizontalSlider_opacity.setValue(int(self.popup.windowOpacity() * 100)) 497 | self.spinBox_displaytime.setValue(self.popup.duration) 498 | self.timetriggercheck.start(1000) 499 | 500 | def ok(self): 501 | config = configparser.ConfigParser() 502 | config['popup'] = {} 503 | config['popup']['top'] = str(int(self.radioButton_top.isChecked())) 504 | self.popup.top = self.radioButton_top.isChecked() 505 | config['popup']['fixedwidth'] = str(int(self.checkBox_fixedwidth.isChecked())) 506 | self.popup.textBrowser.fixedwidth = self.checkBox_fixedwidth.isChecked() 507 | if self.popup.textBrowser.fixedwidth: self.popup.textBrowser.setLineWrapMode(QtWidgets.QTextBrowser.WidgetWidth) 508 | if not self.popup.textBrowser.fixedwidth: self.popup.textBrowser.setLineWrapMode(QtWidgets.QTextBrowser.NoWrap) 509 | config['popup']['duration'] = str(self.spinBox_displaytime.value()) 510 | self.popup.duration = self.spinBox_displaytime.value() 511 | config['popup']['favicons'] = str(int(self.checkBox_favicons.isChecked())) 512 | self.popup.favicons = self.checkBox_favicons.isChecked() 513 | config['popup']['opacity'] = str(int(self.horizontalSlider_opacity.value())) 514 | self.popup.setWindowOpacity(float(self.horizontalSlider_opacity.value() / 100)) 515 | config['popup']['on'] = str(int(self.checkBox_popup.isChecked())) 516 | self.popup.popupon = self.checkBox_popup.isChecked() 517 | config['popup']['soundpath'] = self.lineEdit_notifysound.text() 518 | self.popup.sound = QSound(self.lineEdit_notifysound.text()) 519 | config['popup']['soundon'] = str(int(self.checkBox_notifysound.isChecked())) 520 | self.popup.soundon = self.checkBox_notifysound.isChecked() 521 | config['popup']['x'] = str(self.spinBox_xpos.value()) 522 | self.popup.xpos = self.spinBox_xpos.value() 523 | config['ticks'] = {} 524 | config['ticks']['minimizetotray'] = str(int(self.checkbox_minimizetotray.isChecked())) 525 | config['ticks']['showcount'] = str(int(self.checkbox_showcount.isChecked())) 526 | config['icons'] = {} 527 | config['icons']['default'] = self.lineedit_defulticon.text() 528 | self.defaulticon = self.lineedit_defulticon.text() 529 | config['icons']['notify'] = self.lineedit_notifyicon.text() 530 | self.notifyicon = self.lineedit_notifyicon.text() 531 | self.colour = self.colour_pre 532 | config['icons']['colour'] = self.colour 533 | config['profiles'] = {} 534 | self.profiles.clear() 535 | for x in range(self.listWidget.count()): 536 | self.profiles.append(self.listWidget.item(x).text()) 537 | config['profiles'][str(x)] = self.listWidget.item(x).text() 538 | with open(self.my_settings_file, 'w') as configfile: 539 | config.write(configfile) 540 | configfile.close() 541 | self.hide() 542 | self.tray_icon.setIcon(QtGui.QIcon(self.defaulticon)) 543 | self.timetriggercheck.start(1000) 544 | self.lastmtime = 0 545 | self.popup.fire(self.profiles, False) 546 | 547 | def settings(self): 548 | self.show() 549 | 550 | def iconclick(self): 551 | if self.checkbox_minimizetotray.isChecked() and not self.badprofile: 552 | log('self.windowid = ' + str(self.windowid)) 553 | log('self.INTRAY = ' + str(self.INTRAY)) 554 | self.timetriggercheck.stop() 555 | if checkvisable(): 556 | subprocess.run(["xdotool", "windowunmap", self.windowid]) 557 | self.INTRAY = True 558 | elif self.winId(): 559 | subprocess.run(["xdotool", "windowmap", self.windowid]) 560 | subprocess.run(['wmctrl', '-i', '-r', str(self.windowid), '-b', 'remove,skip_taskbar']) 561 | subprocess.run(["xdotool", "windowactivate", self.windowid]) 562 | self.INTRAY = False 563 | self.timetriggercheck.start(1000) 564 | 565 | def iconmenushowhide(self): 566 | if not self.badprofile: 567 | self.timetriggercheck.stop() 568 | if checkvisable(): 569 | subprocess.run(["xdotool", "windowunmap", self.windowid]) 570 | self.INTRAY = True 571 | elif self.winId(): 572 | subprocess.run(["xdotool", "windowmap", self.windowid]) 573 | subprocess.run(['wmctrl', '-i', '-r', str(self.windowid), '-b', 'remove,skip_taskbar']) 574 | subprocess.run(["xdotool", "windowactivate", self.windowid]) 575 | self.INTRAY = False 576 | self.timetriggercheck.start(1000) 577 | 578 | def fire(self): 579 | if self.checkbox_minimizetotray.isChecked() and os.path.isfile('/tmp/tbpassover'): 580 | if self.INTRAY: 581 | self.INTRAY = False 582 | self.popup.textBrowser.INTRAY = False 583 | self.popup_test.textBrowser.INTRAY = False 584 | subprocess.run(['wmctrl', '-i', '-r', str(self.windowid), '-b', 'remove,skip_taskbar']) 585 | elif not self.INTRAY: 586 | subprocess.run(["xdotool", "windowunmap", self.windowid]) 587 | self.INTRAY = True 588 | os.remove('/tmp/tbpassover') 589 | log('passover') 590 | stdout = subprocess.run(["pgrep", "-i", "thunderbird"], stdout=subprocess.PIPE).stdout.decode('UTF-8') 591 | if not stdout: sys.exit() 592 | if self.badprofile: 593 | self.label_accountwarrning.setText('ERROR! Please Fix Account List') 594 | self.label_accountwarrning.setStyleSheet('color: red') 595 | self.label_accountwarrning.show() 596 | self.tabWidget.setCurrentIndex(1) 597 | self.tray_icon.showMessage('TBtray Profile Warning', 'Please setup account profiles', 598 | QSystemTrayIcon.Critical) 599 | self.timetriggercheck.start(15000) 600 | return 601 | self.timetriggercheck.stop() 602 | if self.popup.textBrowser.INTRAY or self.popup_test.textBrowser.INTRAY: 603 | self.INTRAY = False 604 | self.popup.textBrowser.INTRAY = False 605 | self.popup_test.textBrowser.INTRAY = False 606 | if not self.windowid: 607 | stdout = subprocess.run(["wmctrl", '-lx'], stdout=subprocess.PIPE).stdout.decode('UTF-8') 608 | idx = re.findall('(\dx\w+)..0 Mail\.thunderbird', str(stdout)) 609 | if idx: 610 | self.windowid = idx[0] 611 | self.popup.textBrowser.windowid = self.windowid 612 | log('grabbed window ' + idx[0]) 613 | subprocess.run(["xdotool", "windowunmap", self.windowid]) 614 | self.INTRAY = True 615 | if self.checkbox_minimizetotray.isChecked() and not self.INTRAY: 616 | if not checkvisable() and self.windowid: 617 | subprocess.run(['wmctrl', '-i', '-r', str(self.windowid), '-b', 'add,skip_taskbar']) 618 | self.INTRAY = True 619 | for profile in self.profiles: 620 | if os.path.getmtime(profile) > self.lastmtime: 621 | self.lastmtime = os.path.getmtime(profile) 622 | self.matches = 0 623 | for profile2 in self.profiles: 624 | if not os.path.isfile(profile2): continue 625 | filetext = subprocess.run(["tail", "-n", "2000", profile2], stdout=subprocess.PIPE).stdout.decode( 626 | 'UTF-8') 627 | matchesx = re.findall('\^A2=(\w+)', filetext) 628 | if matchesx: self.matches += int(matchesx[-1], 16) 629 | if self.matches > 0: 630 | if self.checkbox_showcount.isChecked(): 631 | iconpixmap = QtGui.QPixmap(self.notifyicon) 632 | count = str(self.matches) 633 | pixmap = QPixmap(iconpixmap.width(), iconpixmap.height()) 634 | fontsize = self.findfontsize(count, pixmap) 635 | font = QFont("Arial", fontsize) 636 | painter = QPainter() 637 | pixmap.fill(Qt.transparent) 638 | painter.begin(pixmap) 639 | painter.setFont(font) 640 | painter.setOpacity(0.3) 641 | painter.drawPixmap(iconpixmap.rect(), iconpixmap) 642 | painter.setOpacity(1.0) 643 | painter.setPen(QColor(self.colour)) 644 | fm = QFontMetrics(font) 645 | painter.drawText(int((pixmap.width() - fm.width(count)) / 2), 646 | int((pixmap.height() - fm.height()) / 2 + fm.ascent() + 1), count) 647 | painter.end() 648 | self.tray_icon.setIcon(QtGui.QIcon(pixmap)) 649 | else: 650 | self.tray_icon.setIcon(QtGui.QIcon(self.notifyicon)) 651 | if not self.badprofile: self.popup.fire(self.profiles) 652 | else: 653 | self.tray_icon.setIcon(QtGui.QIcon(self.defaulticon)) 654 | break 655 | self.timetriggercheck.start(1000) 656 | 657 | @staticmethod 658 | def findfontsize(text, pixmap): 659 | x = 4 660 | for x in range(4, 200): 661 | font = QFont("Arial", x) 662 | fm = QFontMetrics(font) 663 | if fm.width(text) > pixmap.width() or fm.height() > pixmap.height(): 664 | break 665 | return x 666 | 667 | 668 | def main(): 669 | app = QtWidgets.QApplication(sys.argv) 670 | ui = MainApp() 671 | ui.hide() 672 | sys.exit(app.exec_()) 673 | 674 | 675 | if __name__ == '__main__': 676 | main() 677 | -------------------------------------------------------------------------------- /tbtrayui.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # Form implementation generated from reading ui file '/home/greg/PycharmProjects/tbtray/tbtrayui.ui' 4 | # 5 | # Created by: PyQt5 UI code generator 5.11.3 6 | # 7 | # WARNING! All changes made in this file will be lost! 8 | 9 | from PyQt5 import QtCore, QtGui, QtWidgets 10 | 11 | class Ui_Form(object): 12 | def setupUi(self, Form): 13 | Form.setObjectName("Form") 14 | Form.resize(674, 492) 15 | icon = QtGui.QIcon.fromTheme("thunderbird") 16 | Form.setWindowIcon(icon) 17 | Form.setLayoutDirection(QtCore.Qt.LeftToRight) 18 | self.verticalLayout = QtWidgets.QVBoxLayout(Form) 19 | self.verticalLayout.setObjectName("verticalLayout") 20 | self.tabWidget = QtWidgets.QTabWidget(Form) 21 | self.tabWidget.setObjectName("tabWidget") 22 | self.tab = QtWidgets.QWidget() 23 | self.tab.setObjectName("tab") 24 | self.verticalLayout_3 = QtWidgets.QVBoxLayout(self.tab) 25 | self.verticalLayout_3.setObjectName("verticalLayout_3") 26 | self.groupBox_6 = QtWidgets.QGroupBox(self.tab) 27 | self.groupBox_6.setMinimumSize(QtCore.QSize(0, 160)) 28 | self.groupBox_6.setMaximumSize(QtCore.QSize(16777215, 160)) 29 | self.groupBox_6.setObjectName("groupBox_6") 30 | self.verticalLayout_5 = QtWidgets.QVBoxLayout(self.groupBox_6) 31 | self.verticalLayout_5.setObjectName("verticalLayout_5") 32 | self.horizontalLayout_4 = QtWidgets.QHBoxLayout() 33 | self.horizontalLayout_4.setObjectName("horizontalLayout_4") 34 | self.label_5 = QtWidgets.QLabel(self.groupBox_6) 35 | self.label_5.setMinimumSize(QtCore.QSize(0, 36)) 36 | self.label_5.setObjectName("label_5") 37 | self.horizontalLayout_4.addWidget(self.label_5) 38 | self.lineedit_defulticon = QtWidgets.QLineEdit(self.groupBox_6) 39 | self.lineedit_defulticon.setMinimumSize(QtCore.QSize(400, 0)) 40 | self.lineedit_defulticon.setObjectName("lineedit_defulticon") 41 | self.horizontalLayout_4.addWidget(self.lineedit_defulticon) 42 | self.toolButton_defaulticon = QtWidgets.QToolButton(self.groupBox_6) 43 | self.toolButton_defaulticon.setObjectName("toolButton_defaulticon") 44 | self.horizontalLayout_4.addWidget(self.toolButton_defaulticon) 45 | self.verticalLayout_5.addLayout(self.horizontalLayout_4) 46 | self.horizontalLayout_5 = QtWidgets.QHBoxLayout() 47 | self.horizontalLayout_5.setObjectName("horizontalLayout_5") 48 | self.label_6 = QtWidgets.QLabel(self.groupBox_6) 49 | self.label_6.setMinimumSize(QtCore.QSize(0, 36)) 50 | self.label_6.setObjectName("label_6") 51 | self.horizontalLayout_5.addWidget(self.label_6) 52 | self.lineedit_notifyicon = QtWidgets.QLineEdit(self.groupBox_6) 53 | self.lineedit_notifyicon.setMinimumSize(QtCore.QSize(400, 0)) 54 | self.lineedit_notifyicon.setObjectName("lineedit_notifyicon") 55 | self.horizontalLayout_5.addWidget(self.lineedit_notifyicon) 56 | self.toolButton_notifyicon = QtWidgets.QToolButton(self.groupBox_6) 57 | self.toolButton_notifyicon.setObjectName("toolButton_notifyicon") 58 | self.horizontalLayout_5.addWidget(self.toolButton_notifyicon) 59 | self.verticalLayout_5.addLayout(self.horizontalLayout_5) 60 | self.horizontalLayout_6 = QtWidgets.QHBoxLayout() 61 | self.horizontalLayout_6.setObjectName("horizontalLayout_6") 62 | self.checkbox_minimizetotray = QtWidgets.QCheckBox(self.groupBox_6) 63 | self.checkbox_minimizetotray.setMinimumSize(QtCore.QSize(0, 0)) 64 | self.checkbox_minimizetotray.setMaximumSize(QtCore.QSize(16777215, 16777215)) 65 | self.checkbox_minimizetotray.setObjectName("checkbox_minimizetotray") 66 | self.horizontalLayout_6.addWidget(self.checkbox_minimizetotray) 67 | self.checkbox_showcount = QtWidgets.QCheckBox(self.groupBox_6) 68 | self.checkbox_showcount.setObjectName("checkbox_showcount") 69 | self.horizontalLayout_6.addWidget(self.checkbox_showcount) 70 | self.pushButton_colourpicker = QtWidgets.QPushButton(self.groupBox_6) 71 | self.pushButton_colourpicker.setObjectName("pushButton_colourpicker") 72 | self.horizontalLayout_6.addWidget(self.pushButton_colourpicker) 73 | self.label_colour = QtWidgets.QLabel(self.groupBox_6) 74 | self.label_colour.setObjectName("label_colour") 75 | self.horizontalLayout_6.addWidget(self.label_colour) 76 | spacerItem = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) 77 | self.horizontalLayout_6.addItem(spacerItem) 78 | self.verticalLayout_5.addLayout(self.horizontalLayout_6) 79 | self.verticalLayout_3.addWidget(self.groupBox_6) 80 | self.groupBox_4 = QtWidgets.QGroupBox(self.tab) 81 | self.groupBox_4.setMinimumSize(QtCore.QSize(0, 60)) 82 | self.groupBox_4.setMaximumSize(QtCore.QSize(16777215, 60)) 83 | self.groupBox_4.setObjectName("groupBox_4") 84 | self.horizontalLayout_8 = QtWidgets.QHBoxLayout(self.groupBox_4) 85 | self.horizontalLayout_8.setObjectName("horizontalLayout_8") 86 | self.checkBox_notifysound = QtWidgets.QCheckBox(self.groupBox_4) 87 | self.checkBox_notifysound.setObjectName("checkBox_notifysound") 88 | self.horizontalLayout_8.addWidget(self.checkBox_notifysound) 89 | self.lineEdit_notifysound = QtWidgets.QLineEdit(self.groupBox_4) 90 | self.lineEdit_notifysound.setObjectName("lineEdit_notifysound") 91 | self.horizontalLayout_8.addWidget(self.lineEdit_notifysound) 92 | self.toolButton_notifysound = QtWidgets.QToolButton(self.groupBox_4) 93 | self.toolButton_notifysound.setObjectName("toolButton_notifysound") 94 | self.horizontalLayout_8.addWidget(self.toolButton_notifysound) 95 | self.verticalLayout_3.addWidget(self.groupBox_4) 96 | self.groupBox_5 = QtWidgets.QGroupBox(self.tab) 97 | self.groupBox_5.setMinimumSize(QtCore.QSize(0, 150)) 98 | self.groupBox_5.setMaximumSize(QtCore.QSize(16777215, 150)) 99 | self.groupBox_5.setFlat(False) 100 | self.groupBox_5.setObjectName("groupBox_5") 101 | self.verticalLayout_4 = QtWidgets.QVBoxLayout(self.groupBox_5) 102 | self.verticalLayout_4.setObjectName("verticalLayout_4") 103 | self.horizontalLayout_13 = QtWidgets.QHBoxLayout() 104 | self.horizontalLayout_13.setObjectName("horizontalLayout_13") 105 | self.checkBox_popup = QtWidgets.QCheckBox(self.groupBox_5) 106 | self.checkBox_popup.setMinimumSize(QtCore.QSize(0, 0)) 107 | self.checkBox_popup.setMaximumSize(QtCore.QSize(120, 16777215)) 108 | self.checkBox_popup.setObjectName("checkBox_popup") 109 | self.horizontalLayout_13.addWidget(self.checkBox_popup) 110 | self.checkBox_fixedwidth = QtWidgets.QCheckBox(self.groupBox_5) 111 | self.checkBox_fixedwidth.setObjectName("checkBox_fixedwidth") 112 | self.horizontalLayout_13.addWidget(self.checkBox_fixedwidth) 113 | self.label_4 = QtWidgets.QLabel(self.groupBox_5) 114 | self.label_4.setObjectName("label_4") 115 | self.horizontalLayout_13.addWidget(self.label_4) 116 | self.spinBox_displaytime = QtWidgets.QSpinBox(self.groupBox_5) 117 | self.spinBox_displaytime.setMinimum(1) 118 | self.spinBox_displaytime.setProperty("value", 10) 119 | self.spinBox_displaytime.setObjectName("spinBox_displaytime") 120 | self.horizontalLayout_13.addWidget(self.spinBox_displaytime) 121 | self.label_3 = QtWidgets.QLabel(self.groupBox_5) 122 | self.label_3.setObjectName("label_3") 123 | self.horizontalLayout_13.addWidget(self.label_3) 124 | self.spinBox_xpos = QtWidgets.QSpinBox(self.groupBox_5) 125 | self.spinBox_xpos.setMinimumSize(QtCore.QSize(99, 0)) 126 | self.spinBox_xpos.setMaximum(10000) 127 | self.spinBox_xpos.setProperty("value", 1585) 128 | self.spinBox_xpos.setObjectName("spinBox_xpos") 129 | self.horizontalLayout_13.addWidget(self.spinBox_xpos) 130 | spacerItem1 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) 131 | self.horizontalLayout_13.addItem(spacerItem1) 132 | self.verticalLayout_4.addLayout(self.horizontalLayout_13) 133 | self.horizontalLayout_12 = QtWidgets.QHBoxLayout() 134 | self.horizontalLayout_12.setObjectName("horizontalLayout_12") 135 | self.radioButton_top = QtWidgets.QRadioButton(self.groupBox_5) 136 | font = QtGui.QFont() 137 | font.setKerning(True) 138 | self.radioButton_top.setFont(font) 139 | self.radioButton_top.setObjectName("radioButton_top") 140 | self.horizontalLayout_12.addWidget(self.radioButton_top) 141 | self.radioButton_bottom = QtWidgets.QRadioButton(self.groupBox_5) 142 | self.radioButton_bottom.setObjectName("radioButton_bottom") 143 | self.horizontalLayout_12.addWidget(self.radioButton_bottom) 144 | self.checkBox_favicons = QtWidgets.QCheckBox(self.groupBox_5) 145 | self.checkBox_favicons.setMinimumSize(QtCore.QSize(0, 0)) 146 | self.checkBox_favicons.setObjectName("checkBox_favicons") 147 | self.horizontalLayout_12.addWidget(self.checkBox_favicons) 148 | self.label = QtWidgets.QLabel(self.groupBox_5) 149 | self.label.setObjectName("label") 150 | self.horizontalLayout_12.addWidget(self.label) 151 | self.horizontalSlider_opacity = QtWidgets.QSlider(self.groupBox_5) 152 | self.horizontalSlider_opacity.setMinimumSize(QtCore.QSize(210, 0)) 153 | self.horizontalSlider_opacity.setMaximum(100) 154 | self.horizontalSlider_opacity.setOrientation(QtCore.Qt.Horizontal) 155 | self.horizontalSlider_opacity.setTickPosition(QtWidgets.QSlider.NoTicks) 156 | self.horizontalSlider_opacity.setObjectName("horizontalSlider_opacity") 157 | self.horizontalLayout_12.addWidget(self.horizontalSlider_opacity) 158 | spacerItem2 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) 159 | self.horizontalLayout_12.addItem(spacerItem2) 160 | self.verticalLayout_4.addLayout(self.horizontalLayout_12) 161 | self.toolButton_firepopup = QtWidgets.QPushButton(self.groupBox_5) 162 | self.toolButton_firepopup.setMinimumSize(QtCore.QSize(0, 0)) 163 | self.toolButton_firepopup.setMaximumSize(QtCore.QSize(84, 16777215)) 164 | self.toolButton_firepopup.setObjectName("toolButton_firepopup") 165 | self.verticalLayout_4.addWidget(self.toolButton_firepopup) 166 | self.verticalLayout_3.addWidget(self.groupBox_5) 167 | spacerItem3 = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding) 168 | self.verticalLayout_3.addItem(spacerItem3) 169 | self.tabWidget.addTab(self.tab, "") 170 | self.tab_2 = QtWidgets.QWidget() 171 | self.tab_2.setObjectName("tab_2") 172 | self.verticalLayout_2 = QtWidgets.QVBoxLayout(self.tab_2) 173 | self.verticalLayout_2.setObjectName("verticalLayout_2") 174 | self.groupBox = QtWidgets.QGroupBox(self.tab_2) 175 | self.groupBox.setObjectName("groupBox") 176 | self.horizontalLayout = QtWidgets.QHBoxLayout(self.groupBox) 177 | self.horizontalLayout.setObjectName("horizontalLayout") 178 | self.editline_profilepath = QtWidgets.QLineEdit(self.groupBox) 179 | self.editline_profilepath.setObjectName("editline_profilepath") 180 | self.horizontalLayout.addWidget(self.editline_profilepath) 181 | self.toolButton_profilepath = QtWidgets.QToolButton(self.groupBox) 182 | self.toolButton_profilepath.setObjectName("toolButton_profilepath") 183 | self.horizontalLayout.addWidget(self.toolButton_profilepath) 184 | self.verticalLayout_2.addWidget(self.groupBox) 185 | self.frame_2 = QtWidgets.QFrame(self.tab_2) 186 | self.frame_2.setFrameShape(QtWidgets.QFrame.StyledPanel) 187 | self.frame_2.setFrameShadow(QtWidgets.QFrame.Raised) 188 | self.frame_2.setObjectName("frame_2") 189 | self.horizontalLayout_3 = QtWidgets.QHBoxLayout(self.frame_2) 190 | self.horizontalLayout_3.setObjectName("horizontalLayout_3") 191 | self.label_2 = QtWidgets.QLabel(self.frame_2) 192 | font = QtGui.QFont() 193 | font.setPointSize(10) 194 | font.setBold(True) 195 | font.setWeight(75) 196 | self.label_2.setFont(font) 197 | self.label_2.setObjectName("label_2") 198 | self.horizontalLayout_3.addWidget(self.label_2) 199 | spacerItem4 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) 200 | self.horizontalLayout_3.addItem(spacerItem4) 201 | self.label_accountwarrning = QtWidgets.QLabel(self.frame_2) 202 | font = QtGui.QFont() 203 | font.setPointSize(20) 204 | self.label_accountwarrning.setFont(font) 205 | self.label_accountwarrning.setText("") 206 | self.label_accountwarrning.setScaledContents(True) 207 | self.label_accountwarrning.setObjectName("label_accountwarrning") 208 | self.horizontalLayout_3.addWidget(self.label_accountwarrning) 209 | spacerItem5 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) 210 | self.horizontalLayout_3.addItem(spacerItem5) 211 | self.pushButton_add = QtWidgets.QPushButton(self.frame_2) 212 | self.pushButton_add.setObjectName("pushButton_add") 213 | self.horizontalLayout_3.addWidget(self.pushButton_add) 214 | self.pushButton_remove = QtWidgets.QPushButton(self.frame_2) 215 | self.pushButton_remove.setObjectName("pushButton_remove") 216 | self.horizontalLayout_3.addWidget(self.pushButton_remove) 217 | self.verticalLayout_2.addWidget(self.frame_2) 218 | self.listWidget = QtWidgets.QListWidget(self.tab_2) 219 | self.listWidget.setObjectName("listWidget") 220 | self.verticalLayout_2.addWidget(self.listWidget) 221 | self.tabWidget.addTab(self.tab_2, "") 222 | self.verticalLayout.addWidget(self.tabWidget) 223 | self.frame = QtWidgets.QFrame(Form) 224 | self.frame.setFrameShape(QtWidgets.QFrame.StyledPanel) 225 | self.frame.setFrameShadow(QtWidgets.QFrame.Raised) 226 | self.frame.setObjectName("frame") 227 | self.horizontalLayout_2 = QtWidgets.QHBoxLayout(self.frame) 228 | self.horizontalLayout_2.setObjectName("horizontalLayout_2") 229 | spacerItem6 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) 230 | self.horizontalLayout_2.addItem(spacerItem6) 231 | self.pushButton_cancel = QtWidgets.QPushButton(self.frame) 232 | self.pushButton_cancel.setObjectName("pushButton_cancel") 233 | self.horizontalLayout_2.addWidget(self.pushButton_cancel) 234 | self.pushButton_ok = QtWidgets.QPushButton(self.frame) 235 | self.pushButton_ok.setObjectName("pushButton_ok") 236 | self.horizontalLayout_2.addWidget(self.pushButton_ok) 237 | self.verticalLayout.addWidget(self.frame) 238 | 239 | self.retranslateUi(Form) 240 | self.tabWidget.setCurrentIndex(0) 241 | QtCore.QMetaObject.connectSlotsByName(Form) 242 | 243 | def retranslateUi(self, Form): 244 | _translate = QtCore.QCoreApplication.translate 245 | Form.setWindowTitle(_translate("Form", "TBTray Settings")) 246 | self.groupBox_6.setTitle(_translate("Form", "Tray Icon")) 247 | self.label_5.setText(_translate("Form", "Default Icon Path")) 248 | self.toolButton_defaulticon.setText(_translate("Form", "...")) 249 | self.label_6.setText(_translate("Form", "Notify Icon Path")) 250 | self.toolButton_notifyicon.setText(_translate("Form", "...")) 251 | self.checkbox_minimizetotray.setText(_translate("Form", "Minimize to tray")) 252 | self.checkbox_showcount.setText(_translate("Form", "Show unread Count")) 253 | self.pushButton_colourpicker.setText(_translate("Form", "Colour Picker")) 254 | self.label_colour.setText(_translate("Form", "Colour of unread count font")) 255 | self.groupBox_4.setTitle(_translate("Form", "Notify Sound")) 256 | self.checkBox_notifysound.setText(_translate("Form", "On/Off")) 257 | self.toolButton_notifysound.setText(_translate("Form", "...")) 258 | self.groupBox_5.setTitle(_translate("Form", "Popup")) 259 | self.checkBox_popup.setText(_translate("Form", "ON/OFF")) 260 | self.checkBox_fixedwidth.setToolTip(_translate("Form", "Fixed or variable width popup")) 261 | self.checkBox_fixedwidth.setText(_translate("Form", "Fixed Width")) 262 | self.label_4.setText(_translate("Form", " Display Time")) 263 | self.spinBox_displaytime.setSuffix(_translate("Form", " sec\'s")) 264 | self.label_3.setText(_translate("Form", "Popup X position")) 265 | self.spinBox_xpos.setSuffix(_translate("Form", " px")) 266 | self.radioButton_top.setText(_translate("Form", "&Top-Right")) 267 | self.radioButton_bottom.setText(_translate("Form", "Bottom-Right")) 268 | self.checkBox_favicons.setText(_translate("Form", "Show Mail Favicons")) 269 | self.label.setText(_translate("Form", " Opacity")) 270 | self.toolButton_firepopup.setText(_translate("Form", "Popup Test")) 271 | self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab), _translate("Form", "General")) 272 | self.groupBox.setTitle(_translate("Form", "Profile Path selector")) 273 | self.toolButton_profilepath.setText(_translate("Form", "...")) 274 | self.label_2.setText(_translate("Form", "Profile List ( .msf files)")) 275 | self.pushButton_add.setText(_translate("Form", "Add")) 276 | self.pushButton_remove.setText(_translate("Form", "Remove")) 277 | self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab_2), _translate("Form", "Accounts")) 278 | self.pushButton_cancel.setText(_translate("Form", "Cancel")) 279 | self.pushButton_ok.setText(_translate("Form", "OK")) 280 | 281 | -------------------------------------------------------------------------------- /tbtrayui.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | Form 4 | 5 | 6 | 7 | 0 8 | 0 9 | 674 10 | 492 11 | 12 | 13 | 14 | TBTray Settings 15 | 16 | 17 | 18 | .. 19 | 20 | 21 | Qt::LeftToRight 22 | 23 | 24 | 25 | 26 | 27 | 0 28 | 29 | 30 | 31 | General 32 | 33 | 34 | 35 | 36 | 37 | 38 | 0 39 | 160 40 | 41 | 42 | 43 | 44 | 16777215 45 | 160 46 | 47 | 48 | 49 | Tray Icon 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 0 59 | 36 60 | 61 | 62 | 63 | Default Icon Path 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 400 72 | 0 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | ... 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 0 93 | 36 94 | 95 | 96 | 97 | Notify Icon Path 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 400 106 | 0 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | ... 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 0 127 | 0 128 | 129 | 130 | 131 | 132 | 16777215 133 | 16777215 134 | 135 | 136 | 137 | Minimize to tray 138 | 139 | 140 | 141 | 142 | 143 | 144 | Show unread Count 145 | 146 | 147 | 148 | 149 | 150 | 151 | Colour Picker 152 | 153 | 154 | 155 | 156 | 157 | 158 | Colour of unread count font 159 | 160 | 161 | 162 | 163 | 164 | 165 | Qt::Horizontal 166 | 167 | 168 | 169 | 40 170 | 20 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 0 185 | 60 186 | 187 | 188 | 189 | 190 | 16777215 191 | 60 192 | 193 | 194 | 195 | Notify Sound 196 | 197 | 198 | 199 | 200 | 201 | On/Off 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | ... 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 0 223 | 150 224 | 225 | 226 | 227 | 228 | 16777215 229 | 150 230 | 231 | 232 | 233 | Popup 234 | 235 | 236 | false 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 0 246 | 0 247 | 248 | 249 | 250 | 251 | 120 252 | 16777215 253 | 254 | 255 | 256 | ON/OFF 257 | 258 | 259 | 260 | 261 | 262 | 263 | Fixed or variable width popup 264 | 265 | 266 | Fixed Width 267 | 268 | 269 | 270 | 271 | 272 | 273 | Display Time 274 | 275 | 276 | 277 | 278 | 279 | 280 | sec's 281 | 282 | 283 | 1 284 | 285 | 286 | 10 287 | 288 | 289 | 290 | 291 | 292 | 293 | Popup X position 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 99 302 | 0 303 | 304 | 305 | 306 | px 307 | 308 | 309 | 10000 310 | 311 | 312 | 1585 313 | 314 | 315 | 316 | 317 | 318 | 319 | Qt::Horizontal 320 | 321 | 322 | 323 | 40 324 | 20 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 | true 338 | 339 | 340 | 341 | &Top-Right 342 | 343 | 344 | 345 | 346 | 347 | 348 | Bottom-Right 349 | 350 | 351 | 352 | 353 | 354 | 355 | 356 | 0 357 | 0 358 | 359 | 360 | 361 | Show Mail Favicons 362 | 363 | 364 | 365 | 366 | 367 | 368 | Opacity 369 | 370 | 371 | 372 | 373 | 374 | 375 | 376 | 210 377 | 0 378 | 379 | 380 | 381 | 100 382 | 383 | 384 | Qt::Horizontal 385 | 386 | 387 | QSlider::NoTicks 388 | 389 | 390 | 391 | 392 | 393 | 394 | Qt::Horizontal 395 | 396 | 397 | 398 | 40 399 | 20 400 | 401 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | 410 | 0 411 | 0 412 | 413 | 414 | 415 | 416 | 84 417 | 16777215 418 | 419 | 420 | 421 | Popup Test 422 | 423 | 424 | 425 | 426 | 427 | 428 | 429 | 430 | 431 | Qt::Vertical 432 | 433 | 434 | 435 | 20 436 | 40 437 | 438 | 439 | 440 | 441 | 442 | 443 | 444 | 445 | Accounts 446 | 447 | 448 | 449 | 450 | 451 | Profile Path selector 452 | 453 | 454 | 455 | 456 | 457 | 458 | 459 | 460 | ... 461 | 462 | 463 | 464 | 465 | 466 | 467 | 468 | 469 | 470 | QFrame::StyledPanel 471 | 472 | 473 | QFrame::Raised 474 | 475 | 476 | 477 | 478 | 479 | 480 | 10 481 | 75 482 | true 483 | 484 | 485 | 486 | Profile List ( .msf files) 487 | 488 | 489 | 490 | 491 | 492 | 493 | Qt::Horizontal 494 | 495 | 496 | 497 | 40 498 | 20 499 | 500 | 501 | 502 | 503 | 504 | 505 | 506 | 507 | 20 508 | 509 | 510 | 511 | 512 | 513 | 514 | true 515 | 516 | 517 | 518 | 519 | 520 | 521 | Qt::Horizontal 522 | 523 | 524 | 525 | 40 526 | 20 527 | 528 | 529 | 530 | 531 | 532 | 533 | 534 | Add 535 | 536 | 537 | 538 | 539 | 540 | 541 | Remove 542 | 543 | 544 | 545 | 546 | 547 | 548 | 549 | 550 | 551 | 552 | 553 | 554 | 555 | 556 | 557 | 558 | QFrame::StyledPanel 559 | 560 | 561 | QFrame::Raised 562 | 563 | 564 | 565 | 566 | 567 | Qt::Horizontal 568 | 569 | 570 | 571 | 40 572 | 20 573 | 574 | 575 | 576 | 577 | 578 | 579 | 580 | Cancel 581 | 582 | 583 | 584 | 585 | 586 | 587 | OK 588 | 589 | 590 | 591 | 592 | 593 | 594 | 595 | 596 | 597 | 598 | 599 | --------------------------------------------------------------------------------