aboutsummaryrefslogtreecommitdiffstats
path: root/python
diff options
context:
space:
mode:
authorbors-servo <infra@servo.org>2023-04-13 10:02:35 +0200
committerGitHub <noreply@github.com>2023-04-13 10:02:35 +0200
commit15f966bde5985d02b4a35b1078d44a3023dcf382 (patch)
treeae3aeaba1d7576c63da0f569ba251747fb62bcb2 /python
parent7ab48556b04e327939df8a066de10de68cdd4bd5 (diff)
parent492091e5b0cfbee3d4e988d699bd910e2cd806fa (diff)
downloadservo-15f966bde5985d02b4a35b1078d44a3023dcf382.tar.gz
servo-15f966bde5985d02b4a35b1078d44a3023dcf382.zip
Auto merge of #29610 - mrobinson:notify, r=mukilan
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. --- <!-- Thank you for contributing to Servo! Please replace each `[ ]` by `[X]` when the step is complete, and replace `___` with appropriate data: --> - [x] `./mach build -d` does not report any errors - [x] `./mach test-tidy` does not report any errors - [x] These changes do not require tests because they just change build notifications. <!-- Also, please make sure that "Allow edits from maintainers" checkbox is checked, so that we can help you if you get stuck somewhere along the way.--> <!-- Pull requests that do not address these steps are welcome, but they will require additional verification as part of the review process. -->
Diffstat (limited to 'python')
-rw-r--r--python/requirements.txt3
-rw-r--r--python/servo/build_commands.py162
-rw-r--r--python/servo/devenv_commands.py7
-rw-r--r--python/servo/win32_toast.py45
4 files changed, 56 insertions, 161 deletions
diff --git a/python/requirements.txt b/python/requirements.txt
index ce46d904b83..7df9b1978db 100644
--- a/python/requirements.txt
+++ b/python/requirements.txt
@@ -33,4 +33,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 1a9ee339b1a..c0c62fb2d1a 100644
--- a/python/servo/build_commands.py
+++ b/python/servo/build_commands.py
@@ -23,6 +23,8 @@ import zipfile
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 5570aae50b7..47df6599b92 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 json
import signal
@@ -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)