├── README.md └── pop_report /README.md: -------------------------------------------------------------------------------- 1 | # pop_report 2 | A popup notification tool written in python with Qt. 3 | 4 | The pop_report program is the whole thing, just make it executable and move it somewhere in your PATH to use it. 5 | 6 | Run the program once to create a config file at $HOME/.config/pop_report/style.qss 7 | 8 | Use pop_report -h for help 9 | 10 | Dependencies (install them with pip): 11 | - https://pypi.org/project/PyQt5/ 12 | - https://pypi.org/project/argparse/ 13 | - https://pypi.org/project/inotify/ 14 | 15 | Let me know if i missed anything 16 | -------------------------------------------------------------------------------- /pop_report: -------------------------------------------------------------------------------- 1 | #!/bin/env python3 2 | #from PyQt5.QtGui import * 3 | from PyQt5.QtWidgets import * 4 | from PyQt5 import QtCore 5 | import sys 6 | import argparse 7 | import os 8 | import threading 9 | import inotify.adapters 10 | import inotify.constants 11 | 12 | def parseArgs(): 13 | parser = argparse.ArgumentParser() 14 | parser.add_argument("-m", "--message", type=str, required=True, 15 | help="Message to display in a pop-up") 16 | parser.add_argument("-t", "--topic", type=str, default=None, 17 | help="Topic of the message. " 18 | "New messages with the same topic " 19 | "will override the message in an already open window if available") 20 | parser.add_argument("-d", "--duration", type=int, default=450, 21 | help="Popup window duration in milliseconds (Default: 450)") 22 | parser.add_argument("-o", "--override-style", nargs="+", default=[], 23 | help="Override or add style options. " 24 | "Can take multiple arguments. Example: " 25 | "pop_report -m message -o \"background-color: #ff555555\" \"font-family: CascadiaCode\"") 26 | return parser.parse_args() 27 | 28 | def messageUpdate(topicPath, label): 29 | subscribe = inotify.adapters.Inotify() 30 | subscribe.add_watch(topicPath, inotify.constants.IN_CLOSE_WRITE) 31 | 32 | for event in subscribe.event_gen(yield_nones=False): 33 | if event[1] == ['IN_CLOSE_WRITE']: 34 | with open(topicPath, "r") as topicFile: 35 | label.setText(topicFile.read()) 36 | else: 37 | break 38 | 39 | class MainWindow(QMainWindow): 40 | # Auto-closing window 41 | def __init__(self, parent=None): 42 | # Define window stuff 43 | super(MainWindow, self).__init__(parent) 44 | self.setWindowFlags(QtCore.Qt.X11BypassWindowManagerHint) 45 | self.setAttribute(QtCore.Qt.WA_TranslucentBackground, True) 46 | 47 | def showEvent(self, event): 48 | self.centerWin() 49 | 50 | def centerWin(self): 51 | # Center window once it shows itself 52 | screenWidth = QApplication.primaryScreen().size().width() 53 | screenHeight = QApplication.primaryScreen().size().height() 54 | popupHeight = self.geometry().height() 55 | popupWidth = self.geometry().width() 56 | popupXpos = int((screenWidth-popupWidth)/2) 57 | popupYpos = int((screenHeight-popupHeight)/2) 58 | self.move(popupXpos, popupYpos) 59 | 60 | def closeEvent(self, event): 61 | # Delete object 62 | self.deleteLater() 63 | 64 | 65 | class TimedPopup(QLabel): 66 | def __init__(self, duration, topicPath, mainwin, parent=None): 67 | super(TimedPopup, self).__init__(parent=parent) 68 | self.timer = QtCore.QTimer() 69 | self.topicPath = topicPath 70 | self.duration = duration 71 | self.mainwin = mainwin 72 | 73 | def startTimer(self): 74 | self.timer.timeout.connect(self.close) 75 | self.timer.setInterval(self.duration) 76 | self.timer.start() 77 | 78 | def paintEvent(self, event): 79 | print("Reported message") 80 | self.startTimer() 81 | self.mainwin.centerWin() 82 | QLabel.paintEvent(self, event) 83 | 84 | def closeEvent(self, event): 85 | # Delete topic file 86 | if self.topicPath != None: 87 | if os.path.isfile(self.topicPath): 88 | os.remove(self.topicPath) 89 | # Delete object 90 | self.mainwin.close() 91 | self.deleteLater() 92 | 93 | def main(): 94 | 95 | # Path to stylesheet file 96 | configPath = os.path.expanduser("~/.config/pop_report/") 97 | userConfig = configPath + "style.qss" 98 | 99 | # Parse command line arguments and declare variables 100 | args = parseArgs() 101 | message = args.message 102 | duration = args.duration 103 | styleO = args.override_style 104 | extraStyle = "" 105 | for option in styleO: 106 | extraStyle += option + ";\n" 107 | topicPath = None 108 | 109 | # Check if there's a window already for current topic 110 | topic = args.topic 111 | if topic: 112 | topicPath = os.path.expanduser(str("/tmp/report_" + topic)) 113 | print(topicPath) 114 | if os.path.isfile(topicPath): 115 | print("Topic already has a window, will rewrite contents") 116 | with open(topicPath, "w") as topicFile: 117 | topicFile.write(message) 118 | sys.exit() 119 | with open(topicPath, "w") as topicFile: 120 | topicFile.write(message) 121 | 122 | app = QApplication(sys.argv) 123 | window = MainWindow() 124 | popup = TimedPopup(duration, topicPath, window) 125 | popup.setAlignment(QtCore.Qt.AlignCenter) 126 | popup.setText(str(message)) 127 | window.setCentralWidget(popup) 128 | 129 | # Set the style for the label 130 | defaultStyle = str("border: 5px solid #ffffffff;\n" 131 | "background-color: #dd000000;\n" 132 | "color: #ffffffff;\n" 133 | "font-family: Monospace;\n" 134 | "font-size: 70px;\n" 135 | "padding: 20px;\n") 136 | if not os.path.isdir(configPath): 137 | os.makedirs(configPath) 138 | if not os.path.isfile(userConfig): 139 | with open(userConfig, "w") as userStyleSheet: 140 | userStyleSheet.write(defaultStyle) 141 | with open(userConfig, "r") as userStyleSheet: 142 | popup.setStyleSheet(userStyleSheet.read() + extraStyle) 143 | popup.ensurePolished() 144 | 145 | # Start watch if topic is set 146 | if topic: 147 | waitEdit = threading.Thread(target=messageUpdate, args=(topicPath, popup)) 148 | waitEdit.start() 149 | 150 | # Make the window show itself 151 | window.show() 152 | # Run the app 153 | app.exec() 154 | 155 | 156 | if __name__ == "__main__": 157 | main() 158 | --------------------------------------------------------------------------------