Created
January 3, 2026 21:45
-
-
Save stuartlangridge/e008dfb7fec3905e2c73ae81cb71ccc0 to your computer and use it in GitHub Desktop.
Tiny calculator as a Search Action for XFCE whisker menu search box
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env python3 | |
| """ | |
| A tiny calculator for the XFCE whisker menu search box | |
| "Install" by right-clicking the XFCE top-left ("whisker") menu | |
| and saying Properties. Then, in "Search Actions", add a new | |
| Search Action with Name: calculator, Pattern: ^[0-9+/* -]+$ | |
| Command: python path/to/this/file/whisker-calculator.py | |
| and tick "Regular expression". | |
| You should now be able to type "2 + 3+4" into the search box | |
| and get the answer in a notification. | |
| """ | |
| import sys, json, time | |
| import ast | |
| import operator | |
| import gi | |
| gi.require_version('Notify', '0.7') | |
| from gi.repository import GObject | |
| from gi.repository import Notify | |
| def calc(expr: str): | |
| return compute(ast.parse(expr, mode="eval").body) | |
| # calculator from https://github.com/theRealProHacker/MathEvaluator | |
| op_map = { | |
| # binary | |
| ast.Add: operator.__add__, | |
| ast.Sub: operator.__sub__, | |
| ast.Div: operator.__truediv__, | |
| ast.Mult: operator.__mul__, | |
| # unary | |
| ast.UAdd: operator.__pos__, | |
| ast.USub: operator.__neg__, | |
| } | |
| allowed_types = {int, float} | |
| def compute(expr): | |
| match expr: | |
| case ast.Constant(value=value): | |
| if type(value) not in allowed_types: | |
| raise SyntaxError( | |
| f"Not a number {value!r}" | |
| ) | |
| return value | |
| case ast.UnaryOp(op=op, operand=value): | |
| try: | |
| return op_map[type(op)](compute(value)) # type: ignore | |
| except KeyError: | |
| raise SyntaxError(f"Unknown operation {ast.unparse(expr)}") | |
| case ast.BinOp(op=op, left=left, right=right): | |
| try: | |
| return op_map[type(op)](compute(left), compute(right)) # type: ignore | |
| except KeyError: | |
| raise SyntaxError(f"Unknown operation {ast.unparse(expr)}") | |
| case x: | |
| raise SyntaxError(f"Invalid Node {ast.dump(x)}") | |
| class NotifyClass(GObject.Object): | |
| def __init__(self): | |
| super(NotifyClass, self).__init__() | |
| # lets initialise with the application name | |
| Notify.init("whisker-calculator") | |
| def send_notification(self, title, text, file_path_to_icon=""): | |
| n = Notify.Notification.new(title, text, file_path_to_icon) | |
| n.set_timeout(5000) | |
| n.show() | |
| equation = " ".join(sys.argv[1:]) | |
| my = NotifyClass() | |
| my.send_notification(f"Result of {equation}", f"{calc(equation)}", | |
| "/usr/share/icons/Humanity/apps/48/gnome-calculator.svg") | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment