diff options
author | Martin Robinson <mrobinson@igalia.com> | 2023-04-10 14:46:08 +0200 |
---|---|---|
committer | Martin Robinson <mrobinson@igalia.com> | 2023-04-13 10:01:25 +0200 |
commit | 492091e5b0cfbee3d4e988d699bd910e2cd806fa (patch) | |
tree | 7cda5737ab680fe1d4c6ee6c92089fd62ed45cba /python | |
parent | 53218621e934f5cb66681b22d3d91c3bbcb0d4bc (diff) | |
download | servo-492091e5b0cfbee3d4e988d699bd910e2cd806fa.tar.gz servo-492091e5b0cfbee3d4e988d699bd910e2cd806fa.zip |
Use notify-py to send notifications
- Use notify-py to send notifications, but use a custom notifier on
Linux since transient (ie non-sticky) notifications are not supported.
- Add an icon to the notification.
- Don't send a notification after doing `./mach check` because that
can trigger notifications when `rust-analyzer` is working.
Diffstat (limited to 'python')
-rw-r--r-- | python/requirements.txt | 3 | ||||
-rw-r--r-- | python/servo/build_commands.py | 162 | ||||
-rw-r--r-- | python/servo/devenv_commands.py | 7 | ||||
-rw-r--r-- | python/servo/win32_toast.py | 45 |
4 files changed, 56 insertions, 161 deletions
diff --git a/python/requirements.txt b/python/requirements.txt index b58140060e3..bd2c35149ef 100644 --- a/python/requirements.txt +++ b/python/requirements.txt @@ -32,4 +32,7 @@ certifi # For Python3 compatibility six == 1.15 +# For sending build notifications. +notify-py == 0.3.42 + -e python/tidy diff --git a/python/servo/build_commands.py b/python/servo/build_commands.py index a3875e2c679..8bf3d1fa8a1 100644 --- a/python/servo/build_commands.py +++ b/python/servo/build_commands.py @@ -23,6 +23,8 @@ import stat from time import time +import notifypy + from mach.decorators import ( CommandArgument, CommandProvider, @@ -36,113 +38,6 @@ from servo.gstreamer import windows_dlls, windows_plugins, macos_dylibs, macos_p from servo.util import host_triple -def format_duration(seconds): - return str(datetime.timedelta(seconds=int(seconds))) - - -def notify_linux(title, text): - try: - import dbus - bus = dbus.SessionBus() - notify_obj = bus.get_object("org.freedesktop.Notifications", "/org/freedesktop/Notifications") - method = notify_obj.get_dbus_method("Notify", "org.freedesktop.Notifications") - method(title, 0, "", text, "", [], {"transient": True}, -1) - except ImportError: - raise Exception("Optional Python module 'dbus' is not installed.") - - -def notify_win(title, text): - try: - from servo.win32_toast import WindowsToast - w = WindowsToast() - w.balloon_tip(title, text) - except WindowsError: - from ctypes import Structure, windll, POINTER, sizeof - from ctypes.wintypes import DWORD, HANDLE, WINFUNCTYPE, BOOL, UINT - - class FLASHWINDOW(Structure): - _fields_ = [("cbSize", UINT), - ("hwnd", HANDLE), - ("dwFlags", DWORD), - ("uCount", UINT), - ("dwTimeout", DWORD)] - - FlashWindowExProto = WINFUNCTYPE(BOOL, POINTER(FLASHWINDOW)) - FlashWindowEx = FlashWindowExProto(("FlashWindowEx", windll.user32)) - FLASHW_CAPTION = 0x01 - FLASHW_TRAY = 0x02 - FLASHW_TIMERNOFG = 0x0C - - params = FLASHWINDOW(sizeof(FLASHWINDOW), - windll.kernel32.GetConsoleWindow(), - FLASHW_CAPTION | FLASHW_TRAY | FLASHW_TIMERNOFG, 3, 0) - FlashWindowEx(params) - - -def notify_darwin(title, text): - try: - import Foundation - - bundleDict = Foundation.NSBundle.mainBundle().infoDictionary() - bundleIdentifier = 'CFBundleIdentifier' - if bundleIdentifier not in bundleDict: - bundleDict[bundleIdentifier] = 'mach' - - note = Foundation.NSUserNotification.alloc().init() - note.setTitle_(title) - note.setInformativeText_(text) - - now = Foundation.NSDate.dateWithTimeInterval_sinceDate_(0, Foundation.NSDate.date()) - note.setDeliveryDate_(now) - - centre = Foundation.NSUserNotificationCenter.defaultUserNotificationCenter() - centre.scheduleNotification_(note) - except ImportError: - raise Exception("Optional Python module 'pyobjc' is not installed.") - - -def notify_with_command(command): - def notify(title, text): - if call([command, title, text]) != 0: - raise Exception("Could not run '%s'." % command) - return notify - - -def notify_build_done(config, elapsed, success=True): - """Generate desktop notification when build is complete and the - elapsed build time was longer than 30 seconds.""" - if elapsed > 30: - notify(config, "Servo build", - "%s in %s" % ("Completed" if success else "FAILED", format_duration(elapsed))) - - -def notify(config, title, text): - """Generate a desktop notification using appropriate means on - supported platforms Linux, Windows, and Mac OS. On unsupported - platforms, this function acts as a no-op. - - If notify-command is set in the [tools] section of the configuration, - that is used instead.""" - notify_command = config["tools"].get("notify-command") - if notify_command: - func = notify_with_command(notify_command) - else: - platforms = { - "linux": notify_linux, - "linux2": notify_linux, - "win32": notify_win, - "darwin": notify_darwin - } - func = platforms.get(sys.platform) - - if func is not None: - try: - func(title, text) - except Exception as e: - extra = getattr(e, "message", "") - print("[Warning] Could not generate notification! %s" % extra, file=sys.stderr) - - @CommandProvider class MachCommands(CommandBase): @Command('build', @@ -752,9 +647,12 @@ class MachCommands(CommandBase): pass # Generate Desktop Notification if elapsed-time > some threshold value - notify_build_done(self.config, elapsed, status == 0) - print("Build %s in %s" % ("Completed" if status == 0 else "FAILED", format_duration(elapsed))) + elapsed_delta = datetime.timedelta(seconds=int(elapsed)) + build_message = f"{'Succeeded' if status == 0 else 'Failed'} in {elapsed_delta}" + self.notify("Servo build", build_message) + print(build_message) + return status @Command('clean', @@ -814,6 +712,52 @@ class MachCommands(CommandBase): else: os.remove(artifact) + def notify(self, title: str, message: str): + """Generate desktop notification when build is complete and the + elapsed build time was longer than 30 seconds. + + If notify-command is set in the [tools] section of the configuration, + that is used instead.""" + notify_command = self.config["tools"].get("notify-command") + + # notifypy does not know how to send transient notifications, so we use a custom + # notifier on Linux. If transient notifications are not used, then notifications + # pile up in the notification center and must be cleared manually. + class LinuxNotifier(notifypy.BaseNotifier): + def __init__(self, **kwargs): + pass + + def send_notification(self, **kwargs): + try: + import dbus + bus = dbus.SessionBus() + notify_obj = bus.get_object("org.freedesktop.Notifications", "/org/freedesktop/Notifications") + method = notify_obj.get_dbus_method("Notify", "org.freedesktop.Notifications") + method( + kwargs.get("application_name"), + 0, # Don't replace previous notification. + kwargs.get("notification_icon", ""), + kwargs.get("notification_title"), + kwargs.get("notification_subtitle"), + [], # actions + {"transient": True}, # hints + -1 # timeout + ) + except ImportError: + raise Exception("Optional Python module 'dbus' is not installed.") + return True + + if notify_command: + if call([notify_command, title, message]) != 0: + raise Exception("Could not run '%s'." % notify_command) + else: + notifier = LinuxNotifier if sys.platform.startswith("linux") else None + notification = notifypy.Notify(use_custom_notifier=notifier) + notification.title = title + notification.message = message + notification.icon = path.join(self.get_top_dir(), "resources", "servo_64.png") + notification.send(block=False) + def angle_root(target, nuget_env): arch = { diff --git a/python/servo/devenv_commands.py b/python/servo/devenv_commands.py index db44ad2121c..33d8af9f320 100644 --- a/python/servo/devenv_commands.py +++ b/python/servo/devenv_commands.py @@ -9,7 +9,6 @@ from __future__ import print_function, unicode_literals from os import path, listdir, getcwd -from time import time import signal import sys @@ -25,7 +24,6 @@ from mach.decorators import ( ) from servo.command_base import CommandBase, cd, call -from servo.build_commands import notify_build_done from servo.util import get_static_rust_lang_org_dist, get_urlopen_kwargs @@ -53,12 +51,7 @@ class MachCommands(CommandBase): self.ensure_clobbered() env = self.build_env() - build_start = time() status = self.run_cargo_build_like_command("check", params, env=env, features=features, **kwargs) - elapsed = time() - build_start - - notify_build_done(self.config, elapsed, status == 0) - if status == 0: print('Finished checking, binary NOT updated. Consider ./mach build before ./mach run') diff --git a/python/servo/win32_toast.py b/python/servo/win32_toast.py deleted file mode 100644 index 22a7ccd917f..00000000000 --- a/python/servo/win32_toast.py +++ /dev/null @@ -1,45 +0,0 @@ -# Copyright 2013 The Servo Project Developers. See the COPYRIGHT -# file at the top-level directory of this distribution. -# -# Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or -# http://www.apache.org/licenses/LICENSE-2.0> or the MIT license -# <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your -# option. This file may not be copied, modified, or distributed -# except according to those terms. - -from win32api import GetModuleHandle -from win32gui import WNDCLASS, RegisterClass, CreateWindow, UpdateWindow -from win32gui import DestroyWindow, LoadIcon, NIF_ICON, NIF_MESSAGE, NIF_TIP -from win32gui import Shell_NotifyIcon, NIM_ADD, NIM_MODIFY, NIF_INFO, NIIF_INFO -import win32con - - -class WindowsToast: - def __init__(self): - # Register window class; it's okay to do this multiple times - wc = WNDCLASS() - wc.lpszClassName = 'ServoTaskbarNotification' - wc.lpfnWndProc = {win32con.WM_DESTROY: self.OnDestroy, } - self.classAtom = RegisterClass(wc) - self.hinst = wc.hInstance = GetModuleHandle(None) - - def OnDestroy(self, hwnd, msg, wparam, lparam): - # We don't have to Shell_NotifyIcon to delete it, since - # we destroyed - pass - - def balloon_tip(self, title, msg): - style = win32con.WS_OVERLAPPED | win32con.WS_SYSMENU - hwnd = CreateWindow(self.classAtom, "Taskbar", style, 0, 0, - win32con.CW_USEDEFAULT, win32con.CW_USEDEFAULT, - 0, 0, self.hinst, None) - UpdateWindow(hwnd) - - hicon = LoadIcon(0, win32con.IDI_APPLICATION) - - nid = (hwnd, 0, NIF_ICON | NIF_MESSAGE | NIF_TIP, win32con.WM_USER + 20, hicon, 'Tooltip') - Shell_NotifyIcon(NIM_ADD, nid) - nid = (hwnd, 0, NIF_INFO, win32con.WM_USER + 20, hicon, 'Balloon Tooltip', msg, 200, title, NIIF_INFO) - Shell_NotifyIcon(NIM_MODIFY, nid) - - DestroyWindow(hwnd) |