Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ if __name__ == "__main__":
title="Meeting starts now!",
subtitle="Team Standup",
icon="/Users/jorrick/zoom.png",
sound="Frog",
action_button_str="Join zoom meeting",
action_callback=partial(join_zoom_meeting, conf_number=zoom_conf_number)
)
Expand All @@ -63,7 +64,7 @@ A simple example. Please look [in the docs](https://jorricks.github.io/macos-not

## Why did you create this library?
I wanted a library that did not depend on any non-python tools (so you had to go around and install that). Instead, I wanted a library where you install the pip packages, and you are done.
Later I realised how hard it was to integrate correctly with PyOBJC. Also, I had a hard time finding any examples on how to easily integrate this in a non-blocking fashion with my tool.
Later I realised how hard it was to integrate correctly with PyOBJC. Also, I had a hard time finding any examples on how to easily integrate this in a non-blocking fashion with my tool.
Hence, I figured I should set it up to be as user-friendly as possible and share it with the world ;)!


Expand All @@ -72,4 +73,4 @@ Although there are some limitations, there is no reason to not use it now :v:.
- You need to keep your application running while waiting for the callback to happen.
- We do not support raising notifications from anything but the main thread. If you wish to raise it from other threads, you need to set up a communication channel with the main thread, which in turn than raises the notification.
- Currently, we are only supporting the old deprecated [user notifications](https://developer.apple.com/documentation/foundation/nsusernotification). If you wish to use the new implementation, please feel free to propose an MR.
- You can not change the main image of the notification to be project specific. You can only change the Python interpreter image, but that would impact all notifications send by Python.
- You can not change the main image of the notification to be project specific. You can only change the Python interpreter image, but that would impact all notifications send by Python.
1 change: 1 addition & 0 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ if __name__ == "__main__":
title="Meeting starts now!",
subtitle="Team Standup",
icon="/Users/jorrick/zoom.png",
sound="Frog",
action_button_str="Join zoom meeting",
action_callback=partial(join_zoom_meeting, conf_number=zoom_conf_number)
)
Expand Down
2 changes: 2 additions & 0 deletions src/mac_notifications/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ def create_notification(
subtitle: str | None = None,
text: str | None = None,
icon: str | Path | None = None,
sound: str | None = None,
delay: timedelta = timedelta(),
action_button_str: str | None = None,
action_callback: Callable[[], None] | None = None,
Expand All @@ -56,6 +57,7 @@ def create_notification(
subtitle=subtitle,
text=text,
icon=(str(icon.resolve()) if isinstance(icon, Path) else icon) if icon else None,
sound=sound,
delay=delay,
action_button_str=action_button_str,
action_callback=action_callback,
Expand Down
29 changes: 0 additions & 29 deletions src/mac_notifications/listener_process.py

This file was deleted.

32 changes: 13 additions & 19 deletions src/mac_notifications/manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@
from threading import Event, Thread
from typing import Dict, List

from mac_notifications.listener_process import NotificationProcess
from mac_notifications.notification_config import NotificationConfig
from mac_notifications.notification_sender import cancel_notification, create_notification
from mac_notifications.singleton import Singleton
from mac_notifications.notification_sender import cancel_notification


"""
This is the module responsible for managing the notifications over time & enabling callbacks to be executed.
Expand Down Expand Up @@ -48,13 +48,14 @@ class NotificationManager(metaclass=Singleton):
"""

def __init__(self):
self._callback_queue: SimpleQueue = SimpleQueue()
self._callback_queue: SimpleQueue | None = None
self._callback_executor_event: Event = Event()
self._callback_executor_thread: CallbackExecutorThread | None = None
self._callback_listener_process: NotificationProcess | None = None
# Specify that once we stop our application, self.cleanup should run
atexit.register(self.cleanup)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Jorricks why do you need both atexit() and custom SIGINT handling that do the exact same thing (call self.cleanup())? Seems like just atexit() should be sufficient to accomplish what you are going for?

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I honestly do not recall.
I think we can indeed remove the catch_keyboard_interrupt but let's save that for another MR.


# Specify that when we get a keyboard interrupt, this function should handle it
self.original_sigint_handler = signal.getsignal(signal.SIGINT)
signal.signal(signal.SIGINT, handler=self.catch_keyboard_interrupt)

def create_callback_executor_thread(self) -> None:
Expand All @@ -73,20 +74,15 @@ def create_notification(self, notification_config: NotificationConfig) -> Notifi
:param notification_config: The configuration for the notification.
"""
json_config = notification_config.to_json_notification()
if not notification_config.contains_callback or self._callback_listener_process is not None:
# We can send it directly and kill the process after as we don't need to listen for callbacks.
new_process = NotificationProcess(json_config, None)
new_process.start()
new_process.join(timeout=5)
else:
# We need to also start a listener, so we send the json through a separate process.
self._callback_listener_process = NotificationProcess(json_config, self._callback_queue)
self._callback_listener_process.start()
self.create_callback_executor_thread()

if notification_config.contains_callback:
# We need to also start a listener, so we send the json through a separate process.
self._callback_queue = self._callback_queue or SimpleQueue()
self.create_callback_executor_thread()
_FIFO_LIST.append(notification_config.uid)
_NOTIFICATION_MAP[notification_config.uid] = notification_config

create_notification(json_config, None)
self.clear_old_notifications()
return Notification(notification_config.uid)

Expand All @@ -108,24 +104,22 @@ def get_active_running_notifications() -> int:
def catch_keyboard_interrupt(self, *args) -> None:
"""We catch the keyboard interrupt but also pass it onto the user program."""
self.cleanup()
sys.exit(signal.SIGINT)
signal.signal(signal.SIGINT, handler=self.original_sigint_handler)
signal.raise_signal(signal.SIGINT)

def cleanup(self) -> None:
"""Stop all processes related to the Notification callback handling."""
if self._callback_executor_thread:
self._callback_executor_event.clear()
self._callback_executor_thread.join()
if self._callback_listener_process:
self._callback_listener_process.kill()
self._callback_executor_thread = None
self._callback_listener_process = None
_NOTIFICATION_MAP.clear()
_FIFO_LIST.clear()


class CallbackExecutorThread(Thread):
"""
Background threat that checks each 0.1 second whether there are any callbacks that it should execute.
Background thread that checks each 0.1 second whether there are any callbacks that it should execute.
"""

def __init__(self, keep_running: Event, callback_queue: SimpleQueue):
Expand Down
3 changes: 3 additions & 0 deletions src/mac_notifications/notification_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ class NotificationConfig:
subtitle: str | None
text: str | None
icon: str | None
sound: str | None
delay: timedelta
action_button_str: str | None
action_callback: Callable[[], None] | None
Expand All @@ -42,6 +43,7 @@ def to_json_notification(self) -> "JSONNotificationConfig":
subtitle=NotificationConfig.c_compliant(self.subtitle),
text=NotificationConfig.c_compliant(self.text),
icon=self.icon,
sound=self.sound,
delay_in_seconds=(self.delay or timedelta()).total_seconds(),
action_button_str=NotificationConfig.c_compliant(self.action_button_str),
action_callback_present=bool(self.action_callback),
Expand Down Expand Up @@ -70,6 +72,7 @@ class JSONNotificationConfig:
subtitle: str | None
text: str | None
icon: str | None
sound: str | None
delay_in_seconds: float
action_button_str: str | None
action_callback_present: bool
Expand Down
Loading