aboutsummaryrefslogtreecommitdiff
path: root/.emacs.d/init.d/30-elmord-notify.el~
blob: 774bd4d72de85108467617ea00aa94d834c3adba (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
;; -*- lexical-binding: t; -*-

(require 'dbus)

(defvar elmord-notify-last-notifications (make-ring 16))

;; Code adapted from: https://github.com/clarete/eosd/blob/master/eosd-dbus.el
;; Freedesktop.org notification spec: https://developer.gnome.org/notification-spec/

(defun elmord-notify-get-server-information ()
  "Return server info to client."
  '("elmord-notify" "https://elmord.org" "0.0.1" "1"))

(defun elmord-notify-get-capabilities ()
  "Return server capabilities to the client."
  '("body" "body-hyperlinks" "body-images"))

(defconst elmord-notify-notification-parameters
  '(:app-name
    ;; (string) The optional name of the application sending the notification.

    :replaced-id
    ;; (uint32) The optional notification ID that this notification replaces. The
    ;;          server must atomically (ie with no flicker or other visual cues)
    ;;          replace the given notification with this one. This allows clients to
    ;;          effectively modify the notification while it's active. A value of
    ;;          value of 0 means that this notification won't replace any existing
    ;;          notifications.

    :app-icon
    ;; (string) The optional program icon of the calling application.

    :summary
    ;; (string) The summary text briefly describing the notification.

    :body
    ;; (string) The optional detailed body text. Can be empty.

    :actions
    ;; (array) Actions are sent over as a list of pairs. Each even element in the list
    ;;         (starting at index 0) represents the identifier for the action. Each
    ;;         odd element in the list is the localized string that will be displayed
    ;;         to the user.

    :hints
    ;; (dict) Optional hints that can be passed to the server from the client
    ;;        program. Although clients and servers should never assume each other
    ;;        supports any specific hints, they can be used to pass along information,
    ;;        such as the process PID or window ID, that the server may be able to
    ;;        make use of. See Hints. Can be empty.

    :expire-timeout
    ;; (int32) The timeout time in milliseconds since the display of the
    ;;         notification at which the notification should automatically close.
    ;;         If -1, the notification's expiration time is dependent on the
    ;;         notification server's settings, and may vary for the type of
    ;;         notification. If 0, never expire.
    ))

(defun elmord-notify-do-notify (&rest args)
  "Handle the notifications from external services."
  (let* ((input-fields (cl-mapcan #'list elmord-notify-notification-parameters args))
         (fields (cl-list* :id (random (expt 2 32)) input-fields)))
    (elmord-notify-persistent-message
     (apply #'elmord-notify-format-message fields))
    ;; Save the last few messages for debugging and profit.
    (ring-insert elmord-notify-last-notifications fields)
    ;; Return id back to application.
    (plist-get fields :id)))

(cl-defun elmord-notify-format-message (&key app-name summary body &allow-other-keys)
  (cond ((and (equal app-name "Slack")
              (string-match "\\`New message from \\(.*\\)\\'" summary))
         (let ((sender (match-string 1 summary)))
           (format "[%s] %s: %s"
                   app-name
                   (propertize sender 'face '(:foreground "cyan"))
                   body)))
        ((and (equal app-name "Slack")
              (string-match "\\`New message in \\(.*\\)\\'" summary))
         (let ((channel (match-string 1 summary)))
           (if (string-match "\\`\\([^:]*\\): \\(.*\\)\\'" body)
               (let ((sender (match-string 1 body))
                     (content (match-string 2 body)))
                 (format "[%s] %s: %s: %s"
                         app-name
                         (propertize channel 'face '(:foreground "green"))
                         (propertize sender 'face '(:foreground "cyan"))
                         content))
             ;; Fallback for messages not in the expected format.
             (format "[%s] %s: %s" app-name summary body))))
        ((equal app-name "Telegram Desktop")
         (format "[Telegram] %s: %s"
                 (propertize summary 'face '(:foreground "cyan"))
                 body))
        (t
         (format "[%s] %s: %s" app-name summary body))))


(defun elmord-notify-close-notification (id)
  "Close a notification identified by ID."
  ;;(message "CloseNotification: %s" id)
  )

(defun elmord-notify-notification-closed (id reason)
  "Handle the signal `NotificationClosed'.
ID: Identification of the notification that has being closed
REASON: Reason for closing the notification client"
  ;;(message "NotificationClosed: %s" id)
  )

(defun elmord-notify-action-invoked (id action-key)
  "Handle the signal `ActionInvoked'.
ID: Identification of the notification emitting the action
ACTION-KEY: The Key of the action invoked"
  (message "ActionInvoked: %s %s" id action-key))

(defun elmord-notify-start ()
  "Register notification service under `org.freedesktop.Notifications'."
  (dbus-register-method
   :session "org.freedesktop.Notifications"
   "/org/freedesktop/Notifications" "org.freedesktop.Notifications"
   "Notify" 'elmord-notify-do-notify)
  (dbus-register-method
   :session "org.freedesktop.Notifications"
   "/org/freedesktop/Notifications" "org.freedesktop.Notifications"
   "GetServerInformation" 'elmord-notify-get-server-information)
  (dbus-register-method
   :session "org.freedesktop.Notifications"
   "/org/freedesktop/Notifications" "org.freedesktop.Notifications"
   "GetCapabilities" 'elmord-notify-get-capabilities)
  (dbus-register-method
   :session "org.freedesktop.Notifications"
   "/org/freedesktop/Notifications" "org.freedesktop.Notifications"
   "CloseNotification" 'elmord-notify-close-notification)

  (dbus-register-signal
   :session "org.freedesktop.Notifications"
   "/org/freedesktop/Notifications" "org.freedesktop.Notifications"
   "NotificationClosed" 'elmord-notify-notification-closed)
  (dbus-register-signal
   :session "org.freedesktop.Notifications"
   "/org/freedesktop/Notifications" "org.freedesktop.Notifications"
   "ActionInvoked" 'elmord-notify-action-invoked))

(defun elmord-notify-stop ()
  "Unregister notification D-Bus service."
  (dbus-unregister-service
   :session "org.freedesktop.Notifications"))

(elmord-notify-start)


(defvar elmord-notify-persistent-message-time 0)

(defun elmord-notify-persistent-message (text)
  (setq elmord-notify-persistent-message-text text)
  (setq elmord-notify-persistent-message-time (float-time))
  (when (not (minibuffer-window-active-p (selected-window)))
    (message "%s" text)))


(defvar elmord-notify-persistent-message-duration 10)

(add-hook 'post-command-hook
  (defun elmord-notify-restore-persistent-message ()
    (when (and (null (current-message))
               (not (minibuffer-window-active-p (selected-window)))
               (< (- (float-time) elmord-notify-persistent-message-time)
                  elmord-notify-persistent-message-duration))
      (message "%s" elmord-notify-persistent-message-text))))