587 lines
		
	
	
	
		
			25 KiB
		
	
	
	
		
			EmacsLisp
		
	
	
	
	
	
			
		
		
	
	
			587 lines
		
	
	
	
		
			25 KiB
		
	
	
	
		
			EmacsLisp
		
	
	
	
	
	
;;; exwm-systemtray.el --- System Tray Module for  -*- lexical-binding: t -*-
 | 
						||
;;;                        EXWM
 | 
						||
 | 
						||
;; Copyright (C) 2016-2021 Free Software Foundation, Inc.
 | 
						||
 | 
						||
;; Author: Chris Feng <chris.w.feng@gmail.com>
 | 
						||
 | 
						||
;; This file is part of GNU Emacs.
 | 
						||
 | 
						||
;; GNU Emacs is free software: you can redistribute it and/or modify
 | 
						||
;; it under the terms of the GNU General Public License as published by
 | 
						||
;; the Free Software Foundation, either version 3 of the License, or
 | 
						||
;; (at your option) any later version.
 | 
						||
 | 
						||
;; GNU Emacs is distributed in the hope that it will be useful,
 | 
						||
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
						||
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
						||
;; GNU General Public License for more details.
 | 
						||
 | 
						||
;; You should have received a copy of the GNU General Public License
 | 
						||
;; along with GNU Emacs.  If not, see <http://www.gnu.org/licenses/>.
 | 
						||
 | 
						||
;;; Commentary:
 | 
						||
 | 
						||
;; This module adds system tray support for EXWM.
 | 
						||
 | 
						||
;; To use this module, load and enable it as follows:
 | 
						||
;;   (require 'exwm-systemtray)
 | 
						||
;;   (exwm-systemtray-enable)
 | 
						||
 | 
						||
;;; Code:
 | 
						||
 | 
						||
(require 'xcb-icccm)
 | 
						||
(require 'xcb-xembed)
 | 
						||
(require 'xcb-systemtray)
 | 
						||
 | 
						||
(require 'exwm-core)
 | 
						||
(require 'exwm-workspace)
 | 
						||
 | 
						||
(defclass exwm-systemtray--icon ()
 | 
						||
  ((width :initarg :width)
 | 
						||
   (height :initarg :height)
 | 
						||
   (visible :initarg :visible))
 | 
						||
  :documentation "Attributes of a system tray icon.")
 | 
						||
 | 
						||
(defclass xcb:systemtray:-ClientMessage
 | 
						||
  (xcb:icccm:--ClientMessage xcb:ClientMessage)
 | 
						||
  ((format :initform 32)
 | 
						||
   (type :initform xcb:Atom:MANAGER)
 | 
						||
   (time :initarg :time :type xcb:TIMESTAMP)      ;new slot
 | 
						||
   (selection :initarg :selection :type xcb:ATOM) ;new slot
 | 
						||
   (owner :initarg :owner :type xcb:WINDOW))      ;new slot
 | 
						||
  :documentation "A systemtray client message.")
 | 
						||
 | 
						||
(defgroup exwm-systemtray nil
 | 
						||
  "System tray."
 | 
						||
  :version "25.3"
 | 
						||
  :group 'exwm)
 | 
						||
 | 
						||
(defcustom exwm-systemtray-height nil
 | 
						||
  "System tray height.
 | 
						||
 | 
						||
You shall use the default value if using auto-hide minibuffer."
 | 
						||
  :type 'integer)
 | 
						||
 | 
						||
(defcustom exwm-systemtray-icon-gap 2
 | 
						||
  "Gap between icons."
 | 
						||
  :type 'integer)
 | 
						||
 | 
						||
(defvar exwm-systemtray--embedder-window nil "The embedder window.")
 | 
						||
 | 
						||
(defcustom exwm-systemtray-background-color nil
 | 
						||
  "Background color of systemtray.
 | 
						||
 | 
						||
This should be a color, or nil for transparent background."
 | 
						||
  :type '(choice (const :tag "Transparent" nil)
 | 
						||
                 (color))
 | 
						||
  :initialize #'custom-initialize-default
 | 
						||
  :set (lambda (symbol value)
 | 
						||
         (set-default symbol value)
 | 
						||
         ;; Change the background color for embedder.
 | 
						||
         (when (and exwm--connection
 | 
						||
                    exwm-systemtray--embedder-window)
 | 
						||
           (let ((background-pixel (exwm--color->pixel value)))
 | 
						||
             (xcb:+request exwm--connection
 | 
						||
                 (make-instance 'xcb:ChangeWindowAttributes
 | 
						||
                                :window exwm-systemtray--embedder-window
 | 
						||
                                :value-mask (logior xcb:CW:BackPixmap
 | 
						||
                                                    (if background-pixel
 | 
						||
                                                        xcb:CW:BackPixel 0))
 | 
						||
                                :background-pixmap
 | 
						||
                                xcb:BackPixmap:ParentRelative
 | 
						||
                                :background-pixel background-pixel))
 | 
						||
             ;; Unmap & map to take effect immediately.
 | 
						||
             (xcb:+request exwm--connection
 | 
						||
                 (make-instance 'xcb:UnmapWindow
 | 
						||
                                :window exwm-systemtray--embedder-window))
 | 
						||
             (xcb:+request exwm--connection
 | 
						||
                 (make-instance 'xcb:MapWindow
 | 
						||
                                :window exwm-systemtray--embedder-window))
 | 
						||
             (xcb:flush exwm--connection)))))
 | 
						||
 | 
						||
;; GTK icons require at least 16 pixels to show normally.
 | 
						||
(defconst exwm-systemtray--icon-min-size 16 "Minimum icon size.")
 | 
						||
 | 
						||
(defvar exwm-systemtray--connection nil "The X connection.")
 | 
						||
 | 
						||
(defvar exwm-systemtray--list nil "The icon list.")
 | 
						||
 | 
						||
(defvar exwm-systemtray--selection-owner-window nil
 | 
						||
  "The selection owner window.")
 | 
						||
 | 
						||
(defvar xcb:Atom:_NET_SYSTEM_TRAY_S0)
 | 
						||
 | 
						||
(defun exwm-systemtray--embed (icon)
 | 
						||
  "Embed an icon."
 | 
						||
  (exwm--log "Try to embed #x%x" icon)
 | 
						||
  (let ((info (xcb:+request-unchecked+reply exwm-systemtray--connection
 | 
						||
                  (make-instance 'xcb:xembed:get-_XEMBED_INFO
 | 
						||
                                 :window icon)))
 | 
						||
        width* height* visible)
 | 
						||
    (when info
 | 
						||
      (exwm--log "Embed #x%x" icon)
 | 
						||
      (with-slots (width height)
 | 
						||
          (xcb:+request-unchecked+reply exwm-systemtray--connection
 | 
						||
              (make-instance 'xcb:GetGeometry :drawable icon))
 | 
						||
        (setq height* exwm-systemtray-height
 | 
						||
              width* (round (* width (/ (float height*) height))))
 | 
						||
        (when (< width* exwm-systemtray--icon-min-size)
 | 
						||
          (setq width* exwm-systemtray--icon-min-size
 | 
						||
                height* (round (* height (/ (float width*) width)))))
 | 
						||
        (exwm--log "Resize from %dx%d to %dx%d"
 | 
						||
                   width height width* height*))
 | 
						||
      ;; Add this icon to save-set.
 | 
						||
      (xcb:+request exwm-systemtray--connection
 | 
						||
          (make-instance 'xcb:ChangeSaveSet
 | 
						||
                         :mode xcb:SetMode:Insert
 | 
						||
                         :window icon))
 | 
						||
      ;; Reparent to the embedder.
 | 
						||
      (xcb:+request exwm-systemtray--connection
 | 
						||
          (make-instance 'xcb:ReparentWindow
 | 
						||
                         :window icon
 | 
						||
                         :parent exwm-systemtray--embedder-window
 | 
						||
                         :x 0
 | 
						||
                         ;; Vertically centered.
 | 
						||
                         :y (/ (- exwm-systemtray-height height*) 2)))
 | 
						||
      ;; Resize the icon.
 | 
						||
      (xcb:+request exwm-systemtray--connection
 | 
						||
          (make-instance 'xcb:ConfigureWindow
 | 
						||
                         :window icon
 | 
						||
                         :value-mask (logior xcb:ConfigWindow:Width
 | 
						||
                                             xcb:ConfigWindow:Height
 | 
						||
                                             xcb:ConfigWindow:BorderWidth)
 | 
						||
                         :width width*
 | 
						||
                         :height height*
 | 
						||
                         :border-width 0))
 | 
						||
      ;; Set event mask.
 | 
						||
      (xcb:+request exwm-systemtray--connection
 | 
						||
          (make-instance 'xcb:ChangeWindowAttributes
 | 
						||
                         :window icon
 | 
						||
                         :value-mask xcb:CW:EventMask
 | 
						||
                         :event-mask (logior xcb:EventMask:ResizeRedirect
 | 
						||
                                             xcb:EventMask:KeyPress
 | 
						||
                                             xcb:EventMask:PropertyChange)))
 | 
						||
      ;; Grab all keys and forward them to Emacs frame.
 | 
						||
      (unless (exwm-workspace--minibuffer-own-frame-p)
 | 
						||
        (xcb:+request exwm-systemtray--connection
 | 
						||
            (make-instance 'xcb:GrabKey
 | 
						||
                           :owner-events 0
 | 
						||
                           :grab-window icon
 | 
						||
                           :modifiers xcb:ModMask:Any
 | 
						||
                           :key xcb:Grab:Any
 | 
						||
                           :pointer-mode xcb:GrabMode:Async
 | 
						||
                           :keyboard-mode xcb:GrabMode:Async)))
 | 
						||
      (setq visible (slot-value info 'flags))
 | 
						||
      (if visible
 | 
						||
          (setq visible
 | 
						||
                (/= 0 (logand (slot-value info 'flags) xcb:xembed:MAPPED)))
 | 
						||
        ;; Default to visible.
 | 
						||
        (setq visible t))
 | 
						||
      (when visible
 | 
						||
        (exwm--log "Map the window")
 | 
						||
        (xcb:+request exwm-systemtray--connection
 | 
						||
            (make-instance 'xcb:MapWindow :window icon)))
 | 
						||
      (xcb:+request exwm-systemtray--connection
 | 
						||
          (make-instance 'xcb:xembed:SendEvent
 | 
						||
                         :destination icon
 | 
						||
                         :event
 | 
						||
                         (xcb:marshal
 | 
						||
                          (make-instance 'xcb:xembed:EMBEDDED-NOTIFY
 | 
						||
                                         :window icon
 | 
						||
                                         :time xcb:Time:CurrentTime
 | 
						||
                                         :embedder
 | 
						||
                                         exwm-systemtray--embedder-window
 | 
						||
                                         :version 0)
 | 
						||
                          exwm-systemtray--connection)))
 | 
						||
      (push `(,icon . ,(make-instance 'exwm-systemtray--icon
 | 
						||
                                      :width width*
 | 
						||
                                      :height height*
 | 
						||
                                      :visible visible))
 | 
						||
            exwm-systemtray--list)
 | 
						||
      (exwm-systemtray--refresh))))
 | 
						||
 | 
						||
(defun exwm-systemtray--unembed (icon)
 | 
						||
  "Unembed an icon."
 | 
						||
  (exwm--log "Unembed #x%x" icon)
 | 
						||
  (xcb:+request exwm-systemtray--connection
 | 
						||
      (make-instance 'xcb:UnmapWindow :window icon))
 | 
						||
  (xcb:+request exwm-systemtray--connection
 | 
						||
      (make-instance 'xcb:ReparentWindow
 | 
						||
                     :window icon
 | 
						||
                     :parent exwm--root
 | 
						||
                     :x 0 :y 0))
 | 
						||
  (setq exwm-systemtray--list
 | 
						||
        (assq-delete-all icon exwm-systemtray--list))
 | 
						||
  (exwm-systemtray--refresh))
 | 
						||
 | 
						||
(defun exwm-systemtray--refresh ()
 | 
						||
  "Refresh the system tray."
 | 
						||
  (exwm--log)
 | 
						||
  ;; Make sure to redraw the embedder.
 | 
						||
  (xcb:+request exwm-systemtray--connection
 | 
						||
      (make-instance 'xcb:UnmapWindow
 | 
						||
                     :window exwm-systemtray--embedder-window))
 | 
						||
  (let ((x exwm-systemtray-icon-gap)
 | 
						||
        map)
 | 
						||
    (dolist (pair exwm-systemtray--list)
 | 
						||
      (when (slot-value (cdr pair) 'visible)
 | 
						||
        (xcb:+request exwm-systemtray--connection
 | 
						||
            (make-instance 'xcb:ConfigureWindow
 | 
						||
                           :window (car pair)
 | 
						||
                           :value-mask xcb:ConfigWindow:X
 | 
						||
                           :x x))
 | 
						||
        (setq x (+ x (slot-value (cdr pair) 'width)
 | 
						||
                   exwm-systemtray-icon-gap))
 | 
						||
        (setq map t)))
 | 
						||
    (let ((workarea (elt exwm-workspace--workareas
 | 
						||
                         exwm-workspace-current-index)))
 | 
						||
      (xcb:+request exwm-systemtray--connection
 | 
						||
          (make-instance 'xcb:ConfigureWindow
 | 
						||
                         :window exwm-systemtray--embedder-window
 | 
						||
                         :value-mask (logior xcb:ConfigWindow:X
 | 
						||
                                             xcb:ConfigWindow:Width)
 | 
						||
                         :x (- (aref workarea 2) x)
 | 
						||
                         :width x)))
 | 
						||
    (when map
 | 
						||
      (xcb:+request exwm-systemtray--connection
 | 
						||
          (make-instance 'xcb:MapWindow
 | 
						||
                         :window exwm-systemtray--embedder-window))))
 | 
						||
  (xcb:flush exwm-systemtray--connection))
 | 
						||
 | 
						||
(defun exwm-systemtray--on-DestroyNotify (data _synthetic)
 | 
						||
  "Unembed icons on DestroyNotify."
 | 
						||
  (exwm--log)
 | 
						||
  (let ((obj (make-instance 'xcb:DestroyNotify)))
 | 
						||
    (xcb:unmarshal obj data)
 | 
						||
    (with-slots (window) obj
 | 
						||
      (when (assoc window exwm-systemtray--list)
 | 
						||
        (exwm-systemtray--unembed window)))))
 | 
						||
 | 
						||
(defun exwm-systemtray--on-ReparentNotify (data _synthetic)
 | 
						||
  "Unembed icons on ReparentNotify."
 | 
						||
  (exwm--log)
 | 
						||
  (let ((obj (make-instance 'xcb:ReparentNotify)))
 | 
						||
    (xcb:unmarshal obj data)
 | 
						||
    (with-slots (window parent) obj
 | 
						||
      (when (and (/= parent exwm-systemtray--embedder-window)
 | 
						||
                 (assoc window exwm-systemtray--list))
 | 
						||
        (exwm-systemtray--unembed window)))))
 | 
						||
 | 
						||
(defun exwm-systemtray--on-ResizeRequest (data _synthetic)
 | 
						||
  "Resize the tray icon on ResizeRequest."
 | 
						||
  (exwm--log)
 | 
						||
  (let ((obj (make-instance 'xcb:ResizeRequest))
 | 
						||
        attr)
 | 
						||
    (xcb:unmarshal obj data)
 | 
						||
    (with-slots (window width height) obj
 | 
						||
      (when (setq attr (cdr (assoc window exwm-systemtray--list)))
 | 
						||
        (with-slots ((width* width)
 | 
						||
                     (height* height))
 | 
						||
            attr
 | 
						||
          (setq height* exwm-systemtray-height
 | 
						||
                width* (round (* width (/ (float height*) height))))
 | 
						||
          (when (< width* exwm-systemtray--icon-min-size)
 | 
						||
            (setq width* exwm-systemtray--icon-min-size
 | 
						||
                  height* (round (* height (/ (float width*) width)))))
 | 
						||
          (xcb:+request exwm-systemtray--connection
 | 
						||
              (make-instance 'xcb:ConfigureWindow
 | 
						||
                             :window window
 | 
						||
                             :value-mask (logior xcb:ConfigWindow:Y
 | 
						||
                                                 xcb:ConfigWindow:Width
 | 
						||
                                                 xcb:ConfigWindow:Height)
 | 
						||
                             ;; Vertically centered.
 | 
						||
                             :y (/ (- exwm-systemtray-height height*) 2)
 | 
						||
                             :width width*
 | 
						||
                             :height height*)))
 | 
						||
        (exwm-systemtray--refresh)))))
 | 
						||
 | 
						||
(defun exwm-systemtray--on-PropertyNotify (data _synthetic)
 | 
						||
  "Map/Unmap the tray icon on PropertyNotify."
 | 
						||
  (exwm--log)
 | 
						||
  (let ((obj (make-instance 'xcb:PropertyNotify))
 | 
						||
        attr info visible)
 | 
						||
    (xcb:unmarshal obj data)
 | 
						||
    (with-slots (window atom state) obj
 | 
						||
      (when (and (eq state xcb:Property:NewValue)
 | 
						||
                 (eq atom xcb:Atom:_XEMBED_INFO)
 | 
						||
                 (setq attr (cdr (assoc window exwm-systemtray--list))))
 | 
						||
        (setq info (xcb:+request-unchecked+reply exwm-systemtray--connection
 | 
						||
                       (make-instance 'xcb:xembed:get-_XEMBED_INFO
 | 
						||
                                      :window window)))
 | 
						||
        (when info
 | 
						||
          (setq visible (/= 0 (logand (slot-value info 'flags)
 | 
						||
                                      xcb:xembed:MAPPED)))
 | 
						||
          (exwm--log "#x%x visible? %s" window visible)
 | 
						||
          (if visible
 | 
						||
              (xcb:+request exwm-systemtray--connection
 | 
						||
                  (make-instance 'xcb:MapWindow :window window))
 | 
						||
            (xcb:+request exwm-systemtray--connection
 | 
						||
                (make-instance 'xcb:UnmapWindow :window window)))
 | 
						||
          (setf (slot-value attr 'visible) visible)
 | 
						||
          (exwm-systemtray--refresh))))))
 | 
						||
 | 
						||
(defun exwm-systemtray--on-ClientMessage (data _synthetic)
 | 
						||
  "Handle client messages."
 | 
						||
  (let ((obj (make-instance 'xcb:ClientMessage))
 | 
						||
        opcode data32)
 | 
						||
    (xcb:unmarshal obj data)
 | 
						||
    (with-slots (window type data) obj
 | 
						||
      (when (eq type xcb:Atom:_NET_SYSTEM_TRAY_OPCODE)
 | 
						||
        (setq data32 (slot-value data 'data32)
 | 
						||
              opcode (elt data32 1))
 | 
						||
        (exwm--log "opcode: %s" opcode)
 | 
						||
        (cond ((= opcode xcb:systemtray:opcode:REQUEST-DOCK)
 | 
						||
               (unless (assoc (elt data32 2) exwm-systemtray--list)
 | 
						||
                 (exwm-systemtray--embed (elt data32 2))))
 | 
						||
              ;; Not implemented (rarely used nowadays).
 | 
						||
              ((or (= opcode xcb:systemtray:opcode:BEGIN-MESSAGE)
 | 
						||
                   (= opcode xcb:systemtray:opcode:CANCEL-MESSAGE)))
 | 
						||
              (t
 | 
						||
               (exwm--log "Unknown opcode message: %s" obj)))))))
 | 
						||
 | 
						||
(defun exwm-systemtray--on-KeyPress (data _synthetic)
 | 
						||
  "Forward all KeyPress events to Emacs frame."
 | 
						||
  (exwm--log)
 | 
						||
  ;; This function is only executed when there's no autohide minibuffer,
 | 
						||
  ;; a workspace frame has the input focus and the pointer is over a
 | 
						||
  ;; tray icon.
 | 
						||
  (let ((dest (frame-parameter (selected-frame) 'exwm-outer-id))
 | 
						||
        (obj (make-instance 'xcb:KeyPress)))
 | 
						||
    (xcb:unmarshal obj data)
 | 
						||
    (setf (slot-value obj 'event) dest)
 | 
						||
    (xcb:+request exwm-systemtray--connection
 | 
						||
        (make-instance 'xcb:SendEvent
 | 
						||
                       :propagate 0
 | 
						||
                       :destination dest
 | 
						||
                       :event-mask xcb:EventMask:NoEvent
 | 
						||
                       :event (xcb:marshal obj exwm-systemtray--connection))))
 | 
						||
  (xcb:flush exwm-systemtray--connection))
 | 
						||
 | 
						||
(defun exwm-systemtray--on-workspace-switch ()
 | 
						||
  "Reparent/Refresh the system tray in `exwm-workspace-switch-hook'."
 | 
						||
  (exwm--log)
 | 
						||
  (unless (exwm-workspace--minibuffer-own-frame-p)
 | 
						||
    (exwm-workspace--update-offsets)
 | 
						||
    (xcb:+request exwm-systemtray--connection
 | 
						||
        (make-instance 'xcb:ReparentWindow
 | 
						||
                       :window exwm-systemtray--embedder-window
 | 
						||
                       :parent (string-to-number
 | 
						||
                                (frame-parameter exwm-workspace--current
 | 
						||
                                                 'window-id))
 | 
						||
                       :x 0
 | 
						||
                       :y (- (elt (elt exwm-workspace--workareas
 | 
						||
                                       exwm-workspace-current-index)
 | 
						||
                                  3)
 | 
						||
                             exwm-workspace--frame-y-offset
 | 
						||
                             exwm-systemtray-height))))
 | 
						||
  (exwm-systemtray--refresh))
 | 
						||
 | 
						||
(defun exwm-systemtray--refresh-all ()
 | 
						||
  "Reposition/Refresh the system tray."
 | 
						||
  (exwm--log)
 | 
						||
  (unless (exwm-workspace--minibuffer-own-frame-p)
 | 
						||
    (exwm-workspace--update-offsets)
 | 
						||
    (xcb:+request exwm-systemtray--connection
 | 
						||
        (make-instance 'xcb:ConfigureWindow
 | 
						||
                       :window exwm-systemtray--embedder-window
 | 
						||
                       :value-mask xcb:ConfigWindow:Y
 | 
						||
                       :y (- (elt (elt exwm-workspace--workareas
 | 
						||
                                       exwm-workspace-current-index)
 | 
						||
                                  3)
 | 
						||
                             exwm-workspace--frame-y-offset
 | 
						||
                             exwm-systemtray-height))))
 | 
						||
  (exwm-systemtray--refresh))
 | 
						||
 | 
						||
(cl-defun exwm-systemtray--init ()
 | 
						||
  "Initialize system tray module."
 | 
						||
  (exwm--log)
 | 
						||
  (cl-assert (not exwm-systemtray--connection))
 | 
						||
  (cl-assert (not exwm-systemtray--list))
 | 
						||
  (cl-assert (not exwm-systemtray--selection-owner-window))
 | 
						||
  (cl-assert (not exwm-systemtray--embedder-window))
 | 
						||
  (unless exwm-systemtray-height
 | 
						||
    (setq exwm-systemtray-height (max exwm-systemtray--icon-min-size
 | 
						||
                                      (line-pixel-height))))
 | 
						||
  ;; Create a new connection.
 | 
						||
  (setq exwm-systemtray--connection (xcb:connect))
 | 
						||
  (set-process-query-on-exit-flag (slot-value exwm-systemtray--connection
 | 
						||
                                              'process)
 | 
						||
                                  nil)
 | 
						||
  ;; Initialize XELB modules.
 | 
						||
  (xcb:xembed:init exwm-systemtray--connection t)
 | 
						||
  (xcb:systemtray:init exwm-systemtray--connection t)
 | 
						||
  ;; Acquire the manager selection _NET_SYSTEM_TRAY_S0.
 | 
						||
  (with-slots (owner)
 | 
						||
      (xcb:+request-unchecked+reply exwm-systemtray--connection
 | 
						||
          (make-instance 'xcb:GetSelectionOwner
 | 
						||
                         :selection xcb:Atom:_NET_SYSTEM_TRAY_S0))
 | 
						||
    (when (/= owner xcb:Window:None)
 | 
						||
      (xcb:disconnect exwm-systemtray--connection)
 | 
						||
      (setq exwm-systemtray--connection nil)
 | 
						||
      (warn "[EXWM] Other system tray detected")
 | 
						||
      (cl-return-from exwm-systemtray--init)))
 | 
						||
  (let ((id (xcb:generate-id exwm-systemtray--connection)))
 | 
						||
    (setq exwm-systemtray--selection-owner-window id)
 | 
						||
    (xcb:+request exwm-systemtray--connection
 | 
						||
        (make-instance 'xcb:CreateWindow
 | 
						||
                       :depth 0
 | 
						||
                       :wid id
 | 
						||
                       :parent exwm--root
 | 
						||
                       :x 0
 | 
						||
                       :y 0
 | 
						||
                       :width 1
 | 
						||
                       :height 1
 | 
						||
                       :border-width 0
 | 
						||
                       :class xcb:WindowClass:InputOnly
 | 
						||
                       :visual 0
 | 
						||
                       :value-mask xcb:CW:OverrideRedirect
 | 
						||
                       :override-redirect 1))
 | 
						||
    ;; Get the selection ownership.
 | 
						||
    (xcb:+request exwm-systemtray--connection
 | 
						||
        (make-instance 'xcb:SetSelectionOwner
 | 
						||
                       :owner id
 | 
						||
                       :selection xcb:Atom:_NET_SYSTEM_TRAY_S0
 | 
						||
                       :time xcb:Time:CurrentTime))
 | 
						||
    ;; Send a client message to announce the selection.
 | 
						||
    (xcb:+request exwm-systemtray--connection
 | 
						||
        (make-instance 'xcb:SendEvent
 | 
						||
                       :propagate 0
 | 
						||
                       :destination exwm--root
 | 
						||
                       :event-mask xcb:EventMask:StructureNotify
 | 
						||
                       :event (xcb:marshal
 | 
						||
                               (make-instance 'xcb:systemtray:-ClientMessage
 | 
						||
                                              :window exwm--root
 | 
						||
                                              :time xcb:Time:CurrentTime
 | 
						||
                                              :selection
 | 
						||
                                              xcb:Atom:_NET_SYSTEM_TRAY_S0
 | 
						||
                                              :owner id)
 | 
						||
                               exwm-systemtray--connection)))
 | 
						||
    ;; Set _NET_WM_NAME.
 | 
						||
    (xcb:+request exwm-systemtray--connection
 | 
						||
        (make-instance 'xcb:ewmh:set-_NET_WM_NAME
 | 
						||
                       :window id
 | 
						||
                       :data "EXWM: exwm-systemtray--selection-owner-window"))
 | 
						||
    ;; Set the _NET_SYSTEM_TRAY_ORIENTATION property.
 | 
						||
    (xcb:+request exwm-systemtray--connection
 | 
						||
        (make-instance 'xcb:xembed:set-_NET_SYSTEM_TRAY_ORIENTATION
 | 
						||
                       :window id
 | 
						||
                       :data xcb:systemtray:ORIENTATION:HORZ)))
 | 
						||
  ;; Create the embedder.
 | 
						||
  (let ((id (xcb:generate-id exwm-systemtray--connection))
 | 
						||
        (background-pixel (exwm--color->pixel exwm-systemtray-background-color))
 | 
						||
        frame parent depth y)
 | 
						||
    (setq exwm-systemtray--embedder-window id)
 | 
						||
    (if (exwm-workspace--minibuffer-own-frame-p)
 | 
						||
        (setq frame exwm-workspace--minibuffer
 | 
						||
              y (if (>= (line-pixel-height) exwm-systemtray-height)
 | 
						||
                    ;; Bottom aligned.
 | 
						||
                    (- (line-pixel-height) exwm-systemtray-height)
 | 
						||
                  ;; Vertically centered.
 | 
						||
                  (/ (- (line-pixel-height) exwm-systemtray-height) 2)))
 | 
						||
      (exwm-workspace--update-offsets)
 | 
						||
      (setq frame exwm-workspace--current
 | 
						||
            ;; Bottom aligned.
 | 
						||
            y (- (elt (elt exwm-workspace--workareas
 | 
						||
                           exwm-workspace-current-index)
 | 
						||
                      3)
 | 
						||
                 exwm-workspace--frame-y-offset
 | 
						||
                 exwm-systemtray-height)))
 | 
						||
    (setq parent (string-to-number (frame-parameter frame 'window-id))
 | 
						||
          depth (slot-value (xcb:+request-unchecked+reply
 | 
						||
                                exwm-systemtray--connection
 | 
						||
                                (make-instance 'xcb:GetGeometry
 | 
						||
                                               :drawable parent))
 | 
						||
                            'depth))
 | 
						||
    (xcb:+request exwm-systemtray--connection
 | 
						||
        (make-instance 'xcb:CreateWindow
 | 
						||
                       :depth depth
 | 
						||
                       :wid id
 | 
						||
                       :parent parent
 | 
						||
                       :x 0
 | 
						||
                       :y y
 | 
						||
                       :width 1
 | 
						||
                       :height exwm-systemtray-height
 | 
						||
                       :border-width 0
 | 
						||
                       :class xcb:WindowClass:InputOutput
 | 
						||
                       :visual 0
 | 
						||
                       :value-mask (logior xcb:CW:BackPixmap
 | 
						||
                                           (if background-pixel
 | 
						||
                                               xcb:CW:BackPixel 0)
 | 
						||
                                           xcb:CW:EventMask)
 | 
						||
                       :background-pixmap xcb:BackPixmap:ParentRelative
 | 
						||
                       :background-pixel background-pixel
 | 
						||
                       :event-mask xcb:EventMask:SubstructureNotify))
 | 
						||
    ;; Set _NET_WM_NAME.
 | 
						||
    (xcb:+request exwm-systemtray--connection
 | 
						||
        (make-instance 'xcb:ewmh:set-_NET_WM_NAME
 | 
						||
                       :window id
 | 
						||
                       :data "EXWM: exwm-systemtray--embedder-window")))
 | 
						||
  (xcb:flush exwm-systemtray--connection)
 | 
						||
  ;; Attach event listeners.
 | 
						||
  (xcb:+event exwm-systemtray--connection 'xcb:DestroyNotify
 | 
						||
              #'exwm-systemtray--on-DestroyNotify)
 | 
						||
  (xcb:+event exwm-systemtray--connection 'xcb:ReparentNotify
 | 
						||
              #'exwm-systemtray--on-ReparentNotify)
 | 
						||
  (xcb:+event exwm-systemtray--connection 'xcb:ResizeRequest
 | 
						||
              #'exwm-systemtray--on-ResizeRequest)
 | 
						||
  (xcb:+event exwm-systemtray--connection 'xcb:PropertyNotify
 | 
						||
              #'exwm-systemtray--on-PropertyNotify)
 | 
						||
  (xcb:+event exwm-systemtray--connection 'xcb:ClientMessage
 | 
						||
              #'exwm-systemtray--on-ClientMessage)
 | 
						||
  (unless (exwm-workspace--minibuffer-own-frame-p)
 | 
						||
    (xcb:+event exwm-systemtray--connection 'xcb:KeyPress
 | 
						||
                #'exwm-systemtray--on-KeyPress))
 | 
						||
  ;; Add hook to move/reparent the embedder.
 | 
						||
  (add-hook 'exwm-workspace-switch-hook #'exwm-systemtray--on-workspace-switch)
 | 
						||
  (add-hook 'exwm-workspace--update-workareas-hook
 | 
						||
            #'exwm-systemtray--refresh-all)
 | 
						||
  (add-hook 'menu-bar-mode-hook #'exwm-systemtray--refresh-all)
 | 
						||
  (add-hook 'tool-bar-mode-hook #'exwm-systemtray--refresh-all)
 | 
						||
  (when (boundp 'exwm-randr-refresh-hook)
 | 
						||
    (add-hook 'exwm-randr-refresh-hook #'exwm-systemtray--refresh-all))
 | 
						||
  ;; The struts can be updated already.
 | 
						||
  (when exwm-workspace--workareas
 | 
						||
    (exwm-systemtray--refresh-all)))
 | 
						||
 | 
						||
(defun exwm-systemtray--exit ()
 | 
						||
  "Exit the systemtray module."
 | 
						||
  (exwm--log)
 | 
						||
  (when exwm-systemtray--connection
 | 
						||
    ;; Hide & reparent out the embedder before disconnection to prevent
 | 
						||
    ;; embedded icons from being reparented to an Emacs frame (which is the
 | 
						||
    ;; parent of the embedder).
 | 
						||
    (xcb:+request exwm-systemtray--connection
 | 
						||
        (make-instance 'xcb:UnmapWindow
 | 
						||
                       :window exwm-systemtray--embedder-window))
 | 
						||
    (xcb:+request exwm-systemtray--connection
 | 
						||
        (make-instance 'xcb:ReparentWindow
 | 
						||
                       :window exwm-systemtray--embedder-window
 | 
						||
                       :parent exwm--root
 | 
						||
                       :x 0
 | 
						||
                       :y 0))
 | 
						||
    (xcb:disconnect exwm-systemtray--connection)
 | 
						||
    (setq exwm-systemtray--connection nil
 | 
						||
          exwm-systemtray--list nil
 | 
						||
          exwm-systemtray--selection-owner-window nil
 | 
						||
          exwm-systemtray--embedder-window nil)
 | 
						||
    (remove-hook 'exwm-workspace-switch-hook
 | 
						||
                 #'exwm-systemtray--on-workspace-switch)
 | 
						||
    (remove-hook 'exwm-workspace--update-workareas-hook
 | 
						||
                 #'exwm-systemtray--refresh-all)
 | 
						||
    (remove-hook 'menu-bar-mode-hook #'exwm-systemtray--refresh-all)
 | 
						||
    (remove-hook 'tool-bar-mode-hook #'exwm-systemtray--refresh-all)
 | 
						||
    (when (boundp 'exwm-randr-refresh-hook)
 | 
						||
      (remove-hook 'exwm-randr-refresh-hook #'exwm-systemtray--refresh-all))))
 | 
						||
 | 
						||
(defun exwm-systemtray-enable ()
 | 
						||
  "Enable system tray support for EXWM."
 | 
						||
  (exwm--log)
 | 
						||
  (add-hook 'exwm-init-hook #'exwm-systemtray--init)
 | 
						||
  (add-hook 'exwm-exit-hook #'exwm-systemtray--exit))
 | 
						||
 | 
						||
 | 
						||
 | 
						||
(provide 'exwm-systemtray)
 | 
						||
 | 
						||
;;; exwm-systemtray.el ends here
 |