mirror of
https://github.com/servo/servo.git
synced 2025-06-06 16:45:39 +00:00
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.
This commit is contained in:
parent
53218621e9
commit
492091e5b0
4 changed files with 56 additions and 161 deletions
|
@ -32,4 +32,7 @@ certifi
|
||||||
# For Python3 compatibility
|
# For Python3 compatibility
|
||||||
six == 1.15
|
six == 1.15
|
||||||
|
|
||||||
|
# For sending build notifications.
|
||||||
|
notify-py == 0.3.42
|
||||||
|
|
||||||
-e python/tidy
|
-e python/tidy
|
||||||
|
|
|
@ -23,6 +23,8 @@ import stat
|
||||||
|
|
||||||
from time import time
|
from time import time
|
||||||
|
|
||||||
|
import notifypy
|
||||||
|
|
||||||
from mach.decorators import (
|
from mach.decorators import (
|
||||||
CommandArgument,
|
CommandArgument,
|
||||||
CommandProvider,
|
CommandProvider,
|
||||||
|
@ -36,113 +38,6 @@ from servo.gstreamer import windows_dlls, windows_plugins, macos_dylibs, macos_p
|
||||||
from servo.util import host_triple
|
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
|
@CommandProvider
|
||||||
class MachCommands(CommandBase):
|
class MachCommands(CommandBase):
|
||||||
@Command('build',
|
@Command('build',
|
||||||
|
@ -752,9 +647,12 @@ class MachCommands(CommandBase):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
# Generate Desktop Notification if elapsed-time > some threshold value
|
# 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
|
return status
|
||||||
|
|
||||||
@Command('clean',
|
@Command('clean',
|
||||||
|
@ -814,6 +712,52 @@ class MachCommands(CommandBase):
|
||||||
else:
|
else:
|
||||||
os.remove(artifact)
|
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):
|
def angle_root(target, nuget_env):
|
||||||
arch = {
|
arch = {
|
||||||
|
|
|
@ -9,7 +9,6 @@
|
||||||
|
|
||||||
from __future__ import print_function, unicode_literals
|
from __future__ import print_function, unicode_literals
|
||||||
from os import path, listdir, getcwd
|
from os import path, listdir, getcwd
|
||||||
from time import time
|
|
||||||
|
|
||||||
import signal
|
import signal
|
||||||
import sys
|
import sys
|
||||||
|
@ -25,7 +24,6 @@ from mach.decorators import (
|
||||||
)
|
)
|
||||||
|
|
||||||
from servo.command_base import CommandBase, cd, call
|
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
|
from servo.util import get_static_rust_lang_org_dist, get_urlopen_kwargs
|
||||||
|
|
||||||
|
|
||||||
|
@ -53,12 +51,7 @@ class MachCommands(CommandBase):
|
||||||
self.ensure_clobbered()
|
self.ensure_clobbered()
|
||||||
env = self.build_env()
|
env = self.build_env()
|
||||||
|
|
||||||
build_start = time()
|
|
||||||
status = self.run_cargo_build_like_command("check", params, env=env, features=features, **kwargs)
|
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:
|
if status == 0:
|
||||||
print('Finished checking, binary NOT updated. Consider ./mach build before ./mach run')
|
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