mirror of
https://github.com/servo/servo.git
synced 2025-06-06 16:45:39 +00:00
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. -->
This commit is contained in:
commit
15f966bde5
4 changed files with 56 additions and 161 deletions
|
@ -33,4 +33,7 @@ certifi
|
|||
# For Python3 compatibility
|
||||
six == 1.15
|
||||
|
||||
# For sending build notifications.
|
||||
notify-py == 0.3.42
|
||||
|
||||
-e python/tidy
|
||||
|
|
|
@ -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 = {
|
||||
|
|
|
@ -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')
|
||||
|
||||
|
|
|
@ -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)
|
Loading…
Add table
Add a link
Reference in a new issue