mirror of
https://github.com/ch11ng/exwm.git
synced 2024-11-23 19:47:58 +01:00
b8d621041a
We were using the Emacs' frame's depth, but not the visual nor colormap. This failed with Emacs 29 and its support for 32-bit depths. We now use the default screen's visual: using a non-default visual in the system tray requires support for embedding icons with different visuals, which is not implemented. We restrict our limited transparency support to Emacs frames with depth equal to the default visual's detph. * exwm-core.el (exwm--get-visual-depth-colormap): New function. * exwm-systemtray.el (exwm-systemtray--init): Use root window's visual, depth and colormap. Reset all attributes that refer (perhaps due to defaults) to the parent window, as it might have a different visual, depth or colormap. (exwm-systemtray--init): Set _NET_SYSTEM_TRAY_VISUAL. (exwm-systemtray-background-color): Emit a warning when transparency is selected but not supported. (exwm-systemtray--set-background-color): New function to set embedder window background. (exwm-systemtray--embedder-window-depth): Add variable. (exwm-systemtray--transparency-supported-p): New function to check whether transparency is supported.
660 lines
29 KiB
EmacsLisp
660 lines
29 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--connection nil "The X connection.")
|
||
|
||
(defvar exwm-systemtray--embedder-window nil "The embedder window.")
|
||
(defvar exwm-systemtray--embedder-window-depth nil
|
||
"The embedder window's depth.")
|
||
|
||
(defcustom exwm-systemtray-background-color
|
||
(if (exwm-systemtray--transparency-supported-p)
|
||
"black"
|
||
'transparent)
|
||
"Background color of systemtray.
|
||
This should be a color, the symbol `workspace-background' for the background
|
||
color of current workspace frame, or the symbol `transparent' for transparent
|
||
background.
|
||
|
||
Transparent background is not yet supported when Emacs uses 32-bit depth
|
||
visual, as reported by `x-display-planes'. The X resource \"Emacs.visualClass:
|
||
TrueColor-24\" can be used to force Emacs to use 24-bit depth."
|
||
:type '(choice (const :tag "Transparent" 'transparent)
|
||
(color))
|
||
:initialize #'custom-initialize-default
|
||
:set (lambda (symbol value)
|
||
(when (and (eq value 'transparent)
|
||
(not (exwm-systemtray--transparency-supported-p)))
|
||
(display-warning 'exwm-systemtray
|
||
"Transparent background is not supported yet when \
|
||
using 32-bit depth. Using black instead.")
|
||
(setq value "black"))
|
||
(set-default symbol value)
|
||
(when (and exwm-systemtray--connection
|
||
exwm-systemtray--embedder-window)
|
||
;; Change the background color for embedder.
|
||
(exwm-systemtray--set-background-color)
|
||
;; Unmap & map to take effect immediately.
|
||
(xcb:+request exwm-systemtray--connection
|
||
(make-instance 'xcb:UnmapWindow
|
||
:window exwm-systemtray--embedder-window))
|
||
(xcb:+request exwm-systemtray--connection
|
||
(make-instance 'xcb:MapWindow
|
||
:window exwm-systemtray--embedder-window))
|
||
(xcb:flush exwm-systemtray--connection))))
|
||
|
||
;; GTK icons require at least 16 pixels to show normally.
|
||
(defconst exwm-systemtray--icon-min-size 16 "Minimum icon size.")
|
||
|
||
(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--set-background-color ()
|
||
"Change the background color of the embedder.
|
||
The color is set according to `exwm-systemtray-background-color'.
|
||
|
||
Note that this function does not change the current contents of the embedder
|
||
window; unmap & map are necessary for the background color to take effect."
|
||
(when (and exwm-systemtray--connection
|
||
exwm-systemtray--embedder-window)
|
||
(let* ((color (cl-case exwm-systemtray-background-color
|
||
((transparent nil) ; nil means transparent as well
|
||
(if (exwm-systemtray--transparency-supported-p)
|
||
nil
|
||
(message "%s" "[EXWM] system tray does not support transparent background; using black instead")
|
||
"black"))
|
||
(t exwm-systemtray-background-color)))
|
||
(background-pixel (exwm--color->pixel color)))
|
||
(xcb:+request exwm-systemtray--connection
|
||
(make-instance 'xcb:ChangeWindowAttributes
|
||
:window exwm-systemtray--embedder-window
|
||
;; Either-or. A `background-pixel' of nil
|
||
;; means simulate transparency. We use
|
||
;; `xcb:CW:BackPixmap' together with
|
||
;; `xcb:BackPixmap:ParentRelative' do that,
|
||
;; but this only works when the parent
|
||
;; window's visual (Emacs') has the same
|
||
;; visual depth.
|
||
:value-mask (if background-pixel
|
||
xcb:CW:BackPixel
|
||
xcb:CW:BackPixmap)
|
||
;; Due to the :value-mask above,
|
||
;; :background-pixmap only takes effect when
|
||
;; `transparent' is requested and supported
|
||
;; (visual depth of Emacs and of system tray
|
||
;; are equal). Setting
|
||
;; `xcb:BackPixmap:ParentRelative' when
|
||
;; that's not the case would produce an
|
||
;; `xcb:Match' error.
|
||
:background-pixmap xcb:BackPixmap:ParentRelative
|
||
:background-pixel background-pixel)))))
|
||
|
||
(defun exwm-systemtray--transparency-supported-p ()
|
||
"Check whether transparent background is supported.
|
||
EXWM system tray supports transparency when the visual depth of the system tray
|
||
window matches that of Emacs. The visual depth of the system tray window is the
|
||
default visual depth of the display.
|
||
|
||
Sections \"Visual and background pixmap handling\" and
|
||
\"_NET_SYSTEM_TRAY_VISUAL\" of the System Tray Protocol Specification
|
||
\(https://specifications.freedesktop.org/systemtray-spec/systemtray-spec-latest.html#visuals)
|
||
indicate how to support actual transparency."
|
||
(let ((planes (x-display-planes)))
|
||
(if exwm-systemtray--embedder-window-depth
|
||
(= planes exwm-systemtray--embedder-window-depth)
|
||
(<= planes 24))))
|
||
|
||
(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))
|
||
frame parent embedder-depth embedder-visual embedder-colormap 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)))
|
||
;; Use default depth, visual and colormap (from root window), instead of
|
||
;; Emacs frame's. See Section "Visual and background pixmap handling" in
|
||
;; "System Tray Protocol Specification 0.3".
|
||
(let* ((vdc (exwm--get-visual-depth-colormap exwm-systemtray--connection
|
||
exwm--root)))
|
||
(setq embedder-visual (car vdc))
|
||
(setq embedder-depth (cadr vdc))
|
||
(setq embedder-colormap (caddr vdc)))
|
||
;; Note down the embedder window's depth. It will be used to check whether
|
||
;; we can use xcb:BackPixmap:ParentRelative to emulate transparency.
|
||
(setq exwm-systemtray--embedder-window-depth embedder-depth)
|
||
(xcb:+request exwm-systemtray--connection
|
||
(make-instance 'xcb:CreateWindow
|
||
:depth embedder-depth
|
||
:wid id
|
||
:parent parent
|
||
:x 0
|
||
:y y
|
||
:width 1
|
||
:height exwm-systemtray-height
|
||
:border-width 0
|
||
:class xcb:WindowClass:InputOutput
|
||
:visual embedder-visual
|
||
:colormap embedder-colormap
|
||
:value-mask (logior xcb:CW:BorderPixel
|
||
xcb:CW:Colormap
|
||
xcb:CW:EventMask)
|
||
:border-pixel 0
|
||
:event-mask xcb:EventMask:SubstructureNotify))
|
||
(exwm-systemtray--set-background-color)
|
||
;; 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"))
|
||
;; Set _NET_SYSTEM_TRAY_VISUAL.
|
||
(xcb:+request exwm-systemtray--connection
|
||
(make-instance 'xcb:xembed:set-_NET_SYSTEM_TRAY_VISUAL
|
||
:window exwm-systemtray--selection-owner-window
|
||
:data embedder-visual)))
|
||
(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
|
||
exwm-systemtray--embedder-window-depth 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
|