about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--README.md5
-rw-r--r--exwm-core.el7
-rw-r--r--exwm-floating.el17
-rw-r--r--exwm-input.el21
-rw-r--r--exwm-layout.el24
-rw-r--r--exwm-manage.el29
-rw-r--r--exwm-randr.el25
-rw-r--r--exwm-systemtray.el388
-rw-r--r--exwm-workspace.el55
-rw-r--r--exwm.el16
10 files changed, 516 insertions, 71 deletions
diff --git a/README.md b/README.md
index 09fe470d392c..7f918bd2975e 100644
--- a/README.md
+++ b/README.md
@@ -3,11 +3,12 @@
 EXWM (Emacs X Window Manager) is a full-featured tiling X window manager for
 Emacs built on top of [XELB](https://github.com/ch11ng/xelb).
 It features:
-+ Fully keyboard-driven operation
++ Fully keyboard-driven operations
 + Hybrid layout modes (tiling & stacking)
 + Workspace support
 + ICCCM/EWMH compliance
-+ Basic RandR support (optional)
++ (Optional) RandR (multi-monitor) support
++ (Optional) system tray
 
 Please check the [User Guide](https://github.com/ch11ng/exwm/wiki)
 for more details.
diff --git a/exwm-core.el b/exwm-core.el
index b09ca52c0c5a..4d936ed75217 100644
--- a/exwm-core.el
+++ b/exwm-core.el
@@ -78,6 +78,9 @@
     (logior xcb:EventMask:StructureNotify xcb:EventMask:PropertyChange))
   "Event mask set on all managed windows.")
 
+(declare-function exwm-input--on-KeyPress-line-mode "exwm-input.el"
+                  (key-press))
+
 ;; Internal variables
 (defvar-local exwm--id nil)               ;window ID
 (defvar-local exwm--container nil)        ;container
@@ -110,7 +113,7 @@
 (defvar-local exwm--normal-hints-max-height nil)
 ;; (defvar-local exwm--normal-hints-win-gravity nil)
 ;; WM_HINTS
-(defvar-local exwm--hints-input nil)    ;FIXME
+(defvar-local exwm--hints-input nil)
 (defvar-local exwm--hints-urgency nil)
 ;; _MOTIF_WM_HINTS
 (defvar-local exwm--mwm-hints nil)
@@ -126,6 +129,8 @@
     map)
   "Keymap for `exwm-mode'.")
 
+(declare-function exwm-manage--kill-buffer-query-function "exwm-manage.el")
+
 (define-derived-mode exwm-mode nil "EXWM"
   "Major mode for managing X windows.
 
diff --git a/exwm-floating.el b/exwm-floating.el
index 82b4487f15c2..209539eb4a6c 100644
--- a/exwm-floating.el
+++ b/exwm-floating.el
@@ -28,7 +28,6 @@
 
 (require 'xcb-cursor)
 (require 'exwm-core)
-(eval-when-compile (require 'exwm-workspace))
 
 (defvar exwm-floating-border-width 1 "Border width of the floating window.")
 (defvar exwm-floating-border-color "navy"
@@ -50,12 +49,17 @@
 (defvar exwm-floating--cursor-bottom-left nil)
 (defvar exwm-floating--cursor-left nil)
 
+(defvar exwm-workspace--current)
+(defvar exwm-workspace--list)
+(defvar exwm-workspace-current-index)
+(defvar exwm-workspace--switch-history-outdated)
+(defvar exwm-workspace-minibuffer-position)
+
 (declare-function exwm-layout--refresh "exwm-layout.el")
+(declare-function exwm-layout--show "exwm-layout.el")
 
-;;;###autoload
 (defun exwm-floating--set-floating (id)
   "Make window ID floating."
-  (interactive)
   (let ((window (get-buffer-window (exwm--id->buffer id))))
     (when window                        ;window in non-floating state
       (set-window-buffer window (other-buffer)))) ;hide it first
@@ -85,7 +89,7 @@
                      (unsplittable . t))))) ;and fix the size later
          (outer-id (string-to-number (frame-parameter frame 'outer-window-id)))
          (container (with-current-buffer (exwm--id->buffer id)
-                          exwm--container))
+                      exwm--container))
          (window (frame-first-window frame)) ;and it's the only window
          (x (slot-value exwm--geometry 'x))
          (y (slot-value exwm--geometry 'y))
@@ -194,10 +198,8 @@
     (select-frame-set-input-focus frame))
   (run-hooks 'exwm-floating-setup-hook))
 
-;;;###autoload
 (defun exwm-floating--unset-floating (id)
   "Make window ID non-floating."
-  (interactive)
   (let ((buffer (exwm--id->buffer id)))
     (with-current-buffer buffer
       ;; Reparent the frame back to the root window.
@@ -257,7 +259,6 @@
 (defvar exwm-floating--moveresize-calculate nil
   "Calculate move/resize parameters [buffer event-mask x y width height].")
 
-;;;###autoload
 (defun exwm-floating--start-moveresize (id &optional type)
   "Start move/resize."
   (let ((buffer (exwm--id->buffer id))
@@ -404,7 +405,6 @@
                              :cursor cursor
                              :time xcb:Time:CurrentTime)))))))
 
-;;;###autoload
 (defun exwm-floating--stop-moveresize (&rest _args)
   "Stop move/resize."
   (xcb:+request exwm--connection
@@ -434,7 +434,6 @@
   (xcb:flush exwm--connection)
   (setq exwm-floating--moveresize-calculate nil))
 
-;;;###autoload
 (defun exwm-floating--do-moveresize (data _synthetic)
   "Perform move/resize."
   (when exwm-floating--moveresize-calculate
diff --git a/exwm-input.el b/exwm-input.el
index 85be1efb2023..5e078030c2ef 100644
--- a/exwm-input.el
+++ b/exwm-input.el
@@ -37,7 +37,6 @@
 
 (require 'xcb-keysyms)
 (require 'exwm-core)
-(eval-when-compile (require 'exwm-workspace))
 
 (defvar exwm-input-move-event 's-down-mouse-1
   "Emacs event to start moving a window.")
@@ -94,6 +93,11 @@ It's updated in several occasions, and only used by `exwm-input--set-focus'.")
             exwm-input--timer
             (run-with-idle-timer 0.01 nil #'exwm-input--update-focus)))))
 
+(defvar exwm-workspace--current)
+(defvar exwm-workspace--switch-history-outdated)
+(defvar exwm-workspace-current-index)
+(defvar exwm-workspace--minibuffer)
+
 (defun exwm-input--update-focus ()
   "Update input focus."
   (when (window-live-p exwm-input--focus-window)
@@ -158,6 +162,11 @@ It's updated in several occasions, and only used by `exwm-input--set-focus'.")
       (setq exwm-input--temp-line-mode nil)
       (exwm-input--release-keyboard))))
 
+(declare-function exwm-floating--start-moveresize "exwm-floating.el"
+                  (id &optional type))
+
+(defvar exwm-workspace--list)
+
 (defun exwm-input--on-ButtonPress (data _synthetic)
   "Handle ButtonPress event."
   (let ((obj (make-instance 'xcb:ButtonPress))
@@ -262,6 +271,7 @@ It's updated in several occasions, and only used by `exwm-input--set-focus'.")
 
 (defun exwm-input-set-key (key command)
   "Set a global key binding."
+  (interactive "KSet key globally: \nCSet key %s to command: ")
   (global-set-key key command)
   (cl-pushnew key exwm-input--global-keys))
 
@@ -273,7 +283,6 @@ It's updated in several occasions, and only used by `exwm-input--set-focus'.")
 (defvar exwm-input--during-command nil
   "Indicate whether between `pre-command-hook' and `post-command-hook'.")
 
-;;;###autoload
 (defun exwm-input--on-KeyPress-line-mode (key-press)
   "Parse X KeyPress event to Emacs key event and then feed the command loop."
   (with-slots (detail state) key-press
@@ -443,12 +452,13 @@ It's updated in several occasions, and only used by `exwm-input--set-focus'.")
 (defun exwm-input-set-simulation-keys (simulation-keys)
   "Set simulation keys.
 
-SIMULATION-KEYS is a list of alist (key-sequence1 . key-sequence2)."
+SIMULATION-KEYS is an alist of the form (original-key . simulated-key)."
   (setq exwm-input--simulation-keys nil)
   (dolist (i simulation-keys)
     (cl-pushnew `(,(vconcat (car i)) . ,(cdr i)) exwm-input--simulation-keys))
   (exwm-input--update-simulation-prefix-keys))
 
+;;;###autoload
 (defun exwm-input-send-simulation-key (times)
   "Fake a key event according to last input key sequence."
   (interactive "p")
@@ -461,6 +471,11 @@ SIMULATION-KEYS is a list of alist (key-sequence1 . key-sequence2)."
         (dolist (j pair)
           (exwm-input--fake-key j))))))
 
+(declare-function exwm-floating--stop-moveresize "exwm-floating.el"
+                  (&rest _args))
+(declare-function exwm-floating--do-moveresize "exwm-floating.el"
+                  (data _synthetic))
+
 (defun exwm-input--init ()
   "Initialize the keyboard module."
   ;; Refresh keyboard mapping
diff --git a/exwm-layout.el b/exwm-layout.el
index 52a84b0fe1fd..c0f3c6147289 100644
--- a/exwm-layout.el
+++ b/exwm-layout.el
@@ -26,7 +26,6 @@
 ;;; Code:
 
 (require 'exwm-core)
-(eval-when-compile (require 'exwm-workspace))
 
 (defvar exwm-floating-border-width)
 
@@ -51,7 +50,6 @@
                                              xcb:ConfigWindow:Height))
                        :width width :height height))))
 
-;;;###autoload
 (defun exwm-layout--show (id &optional window)
   "Show window ID exactly fit in the Emacs window WINDOW."
   (exwm--log "Show #x%x in %s" id window)
@@ -112,7 +110,6 @@
                                exwm--connection))))
   (xcb:flush exwm--connection))
 
-;;;###autoload
 (defun exwm-layout--hide (id)
   "Hide window ID."
   (unless (eq xcb:icccm:WM_STATE:IconicState ;already hidden
@@ -137,6 +134,9 @@
                        :icon xcb:Window:None))
     (xcb:flush exwm--connection)))
 
+(defvar exwm-workspace--current)
+(defvar exwm-workspace--list)
+
 ;;;###autoload
 (defun exwm-layout-set-fullscreen (&optional id)
   "Make window ID fullscreen."
@@ -153,9 +153,8 @@
               (vector (slot-value geometry 'x) (slot-value geometry 'y))))
       (xcb:flush exwm--connection))
     (exwm-layout--resize-container exwm--id exwm--container 0 0
-                                   (frame-pixel-width exwm-workspace--current)
-                                   (frame-pixel-height
-                                    exwm-workspace--current))
+                                   (exwm-workspace--current-width)
+                                   (exwm-workspace--current-height))
     (xcb:+request exwm--connection
         (make-instance 'xcb:ewmh:set-_NET_WM_STATE
                        :window exwm--id
@@ -164,6 +163,7 @@
     (setq exwm--fullscreen t)
     (exwm-input-release-keyboard)))
 
+;;;###autoload
 (defun exwm-layout-unset-fullscreen (&optional id)
   "Restore window from fullscreen state."
   (interactive)
@@ -187,6 +187,9 @@
     (setq exwm--fullscreen nil)
     (exwm-input-grab-keyboard)))
 
+(defvar exwm-layout--fullscreen-frame-count 0
+  "Count the fullscreen workspace frames.")
+
 ;; This function is superficially similar to `exwm-layout-set-fullscreen', but
 ;; they do very different things: `exwm-layout--set-frame-fullscreen' resizes a
 ;; frame to the actual monitor size, `exwm-layout-set-fullscreen' resizes an X
@@ -207,7 +210,8 @@
                  (exwm-workspace--minibuffer-own-frame-p))
         (exwm-workspace--resize-minibuffer-frame width height))
       (exwm-layout--resize-container id workspace x y width height)
-      (xcb:flush exwm--connection))))
+      (xcb:flush exwm--connection)))
+  (cl-incf exwm-layout--fullscreen-frame-count))
 
 (defvar exwm-layout-show-all-buffers nil
   "Non-nil to allow switching to buffers on other workspaces.")
@@ -297,6 +301,7 @@
         (exwm-layout--refresh)
       (run-with-idle-timer 0.01 nil #'exwm-layout--refresh)))) ;FIXME
 
+;;;###autoload
 (defun exwm-layout-enlarge-window (delta &optional horizontal)
   "Make the selected window DELTA pixels taller.
 
@@ -371,6 +376,7 @@ windows."
                            :height height))
         (xcb:flush exwm--connection))))))
 
+;;;###autoload
 (defun exwm-layout-enlarge-window-horizontally (delta)
   "Make the selected window DELTA pixels wider.
 
@@ -378,6 +384,7 @@ See also `exwm-layout-enlarge-window'."
   (interactive "p")
   (exwm-layout-enlarge-window delta t))
 
+;;;###autoload
 (defun exwm-layout-shrink-window (delta)
   "Make the selected window DELTA pixels lower.
 
@@ -385,6 +392,7 @@ See also `exwm-layout-enlarge-window'."
   (interactive "p")
   (exwm-layout-enlarge-window (- delta)))
 
+;;;###autoload
 (defun exwm-layout-shrink-window-horizontally (delta)
   "Make the selected window DELTA pixels narrower.
 
@@ -392,6 +400,7 @@ See also `exwm-layout-enlarge-window'."
   (interactive "p")
   (exwm-layout-enlarge-window (- delta) t))
 
+;;;###autoload
 (defun exwm-layout-hide-mode-line ()
   "Hide mode-line."
   (interactive)
@@ -409,6 +418,7 @@ See also `exwm-layout-enlarge-window'."
                              mode-line-height)
                           nil t)))))
 
+;;;###autoload
 (defun exwm-layout-show-mode-line ()
   "Show mode-line."
   (interactive)
diff --git a/exwm-manage.el b/exwm-manage.el
index 50784ce3016b..224ee16aa890 100644
--- a/exwm-manage.el
+++ b/exwm-manage.el
@@ -27,7 +27,6 @@
 ;;; Code:
 
 (require 'exwm-core)
-(eval-when-compile (require 'exwm-workspace))
 
 (defvar exwm-manage-finish-hook nil
   "Normal hook run after a window is just managed, in the context of the
@@ -59,6 +58,20 @@ corresponding buffer.")
         (when reply
           (setq exwm--mwm-hints (append (slot-value reply 'value) nil)))))))
 
+(defvar exwm-workspace--current)
+(defvar exwm-workspace--switch-history-outdated)
+
+(declare-function exwm--update-window-type "exwm.el" (id &optional force))
+(declare-function exwm--update-class "exwm.el" (id &optional force))
+(declare-function exwm--update-transient-for "exwm.el" (id &optional force))
+(declare-function exwm--update-normal-hints "exwm.el" (id &optional force))
+(declare-function exwm--update-title "exwm.el" (id))
+(declare-function exwm--update-hints "exwm.el" (id &optional force))
+(declare-function exwm--update-protocols "exwm.el" (id &optional force))
+(declare-function exwm--update-state "exwm.el" (id &optional force))
+(declare-function exwm-floating--set-floating "exwm-floating.el" (id))
+(declare-function exwm-floating--unset-floating "exwm-floating.el" (id))
+
 (defun exwm-manage--manage-window (id)
   "Manage window ID."
   (exwm--log "Try to manage #x%x" id)
@@ -130,12 +143,9 @@ corresponding buffer.")
                                :value-mask (eval-when-compile
                                              (logior xcb:ConfigWindow:X
                                                      xcb:ConfigWindow:Y))
-                               :x (/ (- (frame-pixel-width
-                                         exwm-workspace--current)
-                                        width)
+                               :x (/ (- (exwm-workspace--current-width) width)
                                      2)
-                               :y (/ (- (frame-pixel-height
-                                         exwm-workspace--current)
+                               :y (/ (- (exwm-workspace--current-height)
                                         height)
                                      2)))))
         (xcb:flush exwm--connection)
@@ -200,7 +210,6 @@ corresponding buffer.")
       (with-current-buffer (exwm--id->buffer id)
         (run-hooks 'exwm-manage-finish-hook)))))
 
-;;;###autoload
 (defun exwm-manage--unmanage-window (id &optional withdraw-only)
   "Unmanage window ID."
   (let ((buffer (exwm--id->buffer id)))
@@ -284,7 +293,6 @@ corresponding buffer.")
   "Non-nil indicates EXWM is pinging a window.")
 (defvar exwm-manage-ping-timeout 3 "Seconds to wait before killing a client.")
 
-;;;###autoload
 (defun exwm-manage--kill-buffer-query-function ()
   "Run in `kill-buffer-query-functions'."
   (catch 'return
@@ -359,7 +367,6 @@ Would you like to kill it? "
 
 (defun exwm-manage--kill-client (&optional id)
   "Kill an X client."
-  (interactive)
   (unless id (setq id (exwm--buffer->id (current-buffer))))
   (let* ((response (xcb:+request-unchecked+reply exwm--connection
                        (make-instance 'xcb:ewmh:get-_NET_WM_PID :window id)))
@@ -390,8 +397,8 @@ Would you like to kill it? "
             (setq edges
                   (if exwm--fullscreen
                       (list 0 0
-                            (frame-pixel-width exwm-workspace--current)
-                            (frame-pixel-height exwm-workspace--current))
+                            (exwm-workspace--current-width)
+                            (exwm-workspace--current-height))
                     (window-inside-absolute-pixel-edges
                      (get-buffer-window buffer t))))
             (exwm--log "Reply with ConfigureNotify (edges): %s" edges)
diff --git a/exwm-randr.el b/exwm-randr.el
index 716d5218e257..7f9b443c862b 100644
--- a/exwm-randr.el
+++ b/exwm-randr.el
@@ -48,11 +48,19 @@
 
 (require 'xcb-randr)
 (require 'exwm-core)
-(require 'exwm-layout)
-(eval-when-compile (require 'exwm-workspace))
 
 (defvar exwm-randr-workspace-output-plist nil)
 
+(defvar exwm-randr-refresh-hook nil
+  "Normal hook run when the RandR module just refreshed.")
+
+(defvar exwm-workspace-minibuffer-position)
+(defvar exwm-layout--fullscreen-frame-count)
+(defvar exwm-workspace-number)
+(defvar exwm-workspace--list)
+
+(declare-function exwm-layout--set-frame-fullscreen "exwm-layout.el" (frame))
+
 (defun exwm-randr--refresh ()
   "Refresh workspaces according to the updated RandR info."
   (let (output-name geometry output-plist default-geometry workareas
@@ -89,6 +97,7 @@
       (setq workarea-offset (if exwm-workspace-minibuffer-position
                                 0
                               (window-pixel-height (minibuffer-window))))
+      (setq exwm-layout--fullscreen-frame-count 0)
       (dotimes (i exwm-workspace-number)
         (let* ((output (plist-get exwm-randr-workspace-output-plist i))
                (geometry (lax-plist-get output-plist output))
@@ -98,15 +107,8 @@
                   output nil))
           (set-frame-parameter frame 'exwm-randr-output output)
           (set-frame-parameter frame 'exwm-geometry geometry)
+          (exwm-layout--set-frame-fullscreen frame)
           (with-slots (x y width height) geometry
-            (exwm-layout--resize-container (frame-parameter frame
-                                                            'exwm-outer-id)
-                                           (frame-parameter frame
-                                                            'exwm-workspace)
-                                           x y width height)
-            (when (and (eq frame exwm-workspace--current)
-                       (exwm-workspace--minibuffer-own-frame-p))
-              (exwm-workspace--resize-minibuffer-frame width height))
             (setq workareas
                   (nconc workareas (list x y width (- height
                                                       workarea-offset)))
@@ -120,7 +122,8 @@
           (make-instance 'xcb:ewmh:set-_NET_DESKTOP_VIEWPORT
                          :window exwm--root
                          :data (vconcat viewports)))
-      (xcb:flush exwm--connection))))
+      (xcb:flush exwm--connection)
+      (run-hooks 'exwm-randr-refresh-hook))))
 
 (defvar exwm-randr-screen-change-hook nil
   "Normal hook run when screen changes.")
diff --git a/exwm-systemtray.el b/exwm-systemtray.el
new file mode 100644
index 000000000000..e9a974531686
--- /dev/null
+++ b/exwm-systemtray.el
@@ -0,0 +1,388 @@
+;;; exwm-systemtray.el --- System Tray Module for  -*- lexical-binding: t -*-
+;;;                        EXWM
+
+;; Copyright (C) 2016 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-xembed)
+(require 'xcb-systemtray)
+(require 'exwm-core)
+
+(defclass exwm-systemtray--icon ()
+  ((width :initarg :width)
+   (height :initarg :height)
+   (visible :initarg :visible))
+  :documentation "Attributes of a system tray icon.")
+
+;; GTK icons require at least 16 pixels to show normally.
+(defconst exwm-systemtray--icon-min-size 16 "Minimum icon size.")
+
+(defvar exwm-systemtray-height (max exwm-systemtray--icon-min-size
+                                    (line-pixel-height))
+  "System tray height.
+
+You shall use the default value if using auto-hide minibuffer.")
+
+(defvar exwm-systemtray-icon-gap 2 "Gap between icons.")
+
+(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 exwm-systemtray--embedder nil "The embedder window.")
+
+(defun exwm-systemtray--embed (icon)
+  "Embed an icon."
+  (exwm--log "(System Tray) 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 "(System Tray) 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 "(System Tray) Resize from %dx%d to %dx%d"
+                   width height width* height*))
+      ;; Reparent to the embedder.
+      (xcb:+request exwm-systemtray--connection
+          (make-instance 'xcb:ReparentWindow
+                         :window icon
+                         :parent exwm-systemtray--embedder
+                         :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:PropertyChange)))
+      (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 "(System Tray) 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
+                                         :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 "(System Tray) 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."
+  ;; Make sure to redraw the embedder.
+  (xcb:+request exwm-systemtray--connection
+      (make-instance 'xcb:UnmapWindow :window exwm-systemtray--embedder))
+  (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)))
+    (xcb:+request exwm-systemtray--connection
+        (make-instance 'xcb:ConfigureWindow
+                       :window exwm-systemtray--embedder
+                       :value-mask (logior xcb:ConfigWindow:X
+                                           xcb:ConfigWindow:Width)
+                       :x (- (exwm-workspace--current-width) x)
+                       :width x))
+    (when map
+      (xcb:+request exwm-systemtray--connection
+          (make-instance 'xcb:MapWindow :window exwm-systemtray--embedder))))
+  (xcb:flush exwm-systemtray--connection))
+
+(defun exwm-systemtray--on-DestroyNotify (data _synthetic)
+  "Unembed icons on DestroyNotify."
+  (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."
+  (let ((obj (make-instance 'xcb:ReparentNotify)))
+    (xcb:unmarshal obj data)
+    (with-slots (window parent) obj
+      (when (and (/= parent exwm-systemtray--embedder)
+                 (assoc window exwm-systemtray--list))
+        (exwm-systemtray--unembed window)))))
+
+(defun exwm-systemtray--on-ResizeRequest (data _synthetic)
+  "Resize the tray icon on ResizeRequest."
+  (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."
+  (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 "(System Tray) #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))
+        (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 "(System Tray) Unknown opcode message: %s" obj)))))))
+
+(defvar exwm-workspace-minibuffer-position)
+(defvar exwm-workspace--current)
+
+(defun exwm-systemtray--on-workspace-switch ()
+  "Reparent/Refresh the system tray in `exwm-workspace-switch-hook'."
+  (unless exwm-workspace-minibuffer-position
+    (xcb:+request exwm-systemtray--connection
+        (make-instance 'xcb:ReparentWindow
+                       :window exwm-systemtray--embedder
+                       :parent (string-to-number
+                                (frame-parameter exwm-workspace--current
+                                                 'window-id))
+                       :x 0
+                       :y (- (exwm-workspace--current-height)
+                             exwm-systemtray-height))))
+  (exwm-systemtray--refresh))
+
+(defun exwm-systemtray--on-randr-refresh ()
+  "Reposition/Refresh the system tray in `exwm-randr-refresh-hook'."
+  (unless exwm-workspace-minibuffer-position
+    (xcb:+request exwm-systemtray--connection
+        (make-instance 'xcb:ConfigureWindow
+                       :window exwm-systemtray--embedder
+                       :value-mask xcb:ConfigWindow:Y
+                       :y (- (exwm-workspace--current-height)
+                             exwm-systemtray-height))))
+  (exwm-systemtray--refresh))
+
+(defvar xcb:Atom:_NET_SYSTEM_TRAY_S0)
+(defvar exwm-workspace--minibuffer)
+
+(defun exwm-systemtray--init ()
+  "Initialize system tray module."
+  (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))
+  ;; Create a new connection.
+  (setq exwm-systemtray--connection (xcb:connect-to-socket))
+  (set-process-query-on-exit-flag (slot-value exwm-systemtray--connection
+                                              'process)
+                                  nil)
+  ;; Initialize XELB modules.
+  (xcb:xembed:init exwm-systemtray--connection)
+  (xcb:systemtray:init exwm-systemtray--connection)
+  ;; 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)
+      (error "[EXWM] Other system tray detected")))
+  (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))
+    ;; Set _NET_WM_NAME.
+    (xcb:+request exwm-systemtray--connection
+        (make-instance 'xcb:ewmh:set-_NET_WM_NAME
+                       :window id :data "EXWM system tray selection owner"))
+    ;; 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))
+        parent y)
+    (setq exwm-systemtray--embedder id)
+    (xcb:+request exwm-systemtray--connection
+        (make-instance 'xcb:CreateWindow
+                       :depth 0 :wid id :parent exwm--root
+                       :x 0 :y 0 :width 1 :height exwm-systemtray-height
+                       :border-width 0 :class xcb:WindowClass:CopyFromParent
+                       :visual 0 :value-mask xcb:CW:EventMask
+                       :event-mask xcb:EventMask:SubstructureNotify))
+    (if exwm-workspace-minibuffer-position
+        (setq parent (frame-parameter exwm-workspace--minibuffer
+                                      'exwm-container)
+              ;; Vertically centered.
+              y (/ (- (line-pixel-height) exwm-systemtray-height) 2))
+      (setq parent (string-to-number (frame-parameter exwm-workspace--current
+                                                      'window-id))
+            ;; Bottom aligned.
+            y (- (exwm-workspace--current-height) exwm-systemtray-height)))
+    (xcb:+request exwm-systemtray--connection
+        (make-instance 'xcb:ReparentWindow
+                       :window id :parent parent :x 0 :y y))
+    ;; Set _NET_WM_NAME.
+    (xcb:+request exwm-systemtray--connection
+        (make-instance 'xcb:ewmh:set-_NET_WM_NAME
+                       :window id :data "EXWM system tray embedder")))
+  (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)
+  ;; Add hook to move/reparent the embedder.
+  (add-hook 'exwm-workspace-switch-hook #'exwm-systemtray--on-workspace-switch)
+  (add-hook 'exwm-randr-refresh-hook #'exwm-systemtray--on-randr-refresh))
+
+(defun exwm-systemtray-enable ()
+  "Enable system tray support for EXWM."
+  (add-hook 'exwm-init-hook #'exwm-systemtray--init))
+
+
+
+(provide 'exwm-systemtray)
+
+;; exwm-systemtray.el ends here
diff --git a/exwm-workspace.el b/exwm-workspace.el
index 99a7c7bd2bb3..99e3b5510ea5 100644
--- a/exwm-workspace.el
+++ b/exwm-workspace.el
@@ -23,9 +23,6 @@
 
 ;; This module adds workspace support for EXWM.
 
-;; Todo:
-;; + Add system tray support.
-
 ;;; Code:
 
 (require 'exwm-core)
@@ -65,7 +62,6 @@
 (defvar exwm-workspace--switch-history-outdated nil
   "Non-nil to indicate `exwm-workspace--switch-history' is outdated.")
 
-;;;###autoload
 (defun exwm-workspace--update-switch-history ()
   "Update the history for switching workspace to reflect the latest status."
   (when exwm-workspace--switch-history-outdated
@@ -112,6 +108,22 @@ Value nil means to use the default position which is fixed at bottom, while
   "Timer for auto-hiding echo area.")
 
 ;;;###autoload
+(defun exwm-workspace--current-width ()
+  "Return the width of current workspace."
+  (let ((geometry (frame-parameter exwm-workspace--current 'exwm-geometry)))
+    (if geometry
+        (slot-value geometry 'width)
+      (x-display-pixel-width))))
+
+;;;###autoload
+(defun exwm-workspace--current-height ()
+  "Return the height of current workspace."
+  (let ((geometry (frame-parameter exwm-workspace--current 'exwm-geometry)))
+    (if geometry
+        (slot-value geometry 'height)
+      (x-display-pixel-height))))
+
+;;;###autoload
 (defun exwm-workspace--minibuffer-own-frame-p ()
   "Reports whether the minibuffer is displayed in its own frame."
   (memq exwm-workspace-minibuffer-position '(top bottom)))
@@ -125,9 +137,9 @@ workspace frame."
   (cl-assert (exwm-workspace--minibuffer-own-frame-p))
   (let ((y (if (eq exwm-workspace-minibuffer-position 'top)
                0
-             (- (or height (frame-pixel-height exwm-workspace--current))
+             (- (or height (exwm-workspace--current-height))
                 (frame-pixel-height exwm-workspace--minibuffer))))
-        (width (or width (frame-pixel-width exwm-workspace--current)))
+        (width (or width (exwm-workspace--current-width)))
         (container (frame-parameter exwm-workspace--minibuffer
                                     'exwm-container)))
     (xcb:+request exwm--connection
@@ -141,6 +153,9 @@ workspace frame."
                        :stack-mode xcb:StackMode:Above))
     (set-frame-width exwm-workspace--minibuffer width nil t)))
 
+(defvar exwm-workspace-switch-hook nil
+  "Normal hook run after switching workspace.")
+
 ;;;###autoload
 (defun exwm-workspace-switch (index &optional force)
   "Switch to workspace INDEX. Query for INDEX if it's not specified.
@@ -203,7 +218,10 @@ The optional FORCE option is for internal use only."
         (xcb:+request exwm--connection
             (make-instance 'xcb:ewmh:set-_NET_CURRENT_DESKTOP
                            :window exwm--root :data index))
-        (xcb:flush exwm--connection)))))
+        (xcb:flush exwm--connection))
+      (run-hooks 'exwm-workspace-switch-hook))))
+
+(declare-function exwm-layout--hide "exwm-layout.el" (id))
 
 ;;;###autoload
 (defun exwm-workspace-move-window (index &optional id)
@@ -265,6 +283,7 @@ The optional FORCE option is for internal use only."
                              (exwm--id->buffer id)))))
     (setq exwm-workspace--switch-history-outdated t)))
 
+;;;###autoload
 (defun exwm-workspace-switch-to-buffer ()
   "Make the current Emacs window display another buffer."
   (interactive)
@@ -347,10 +366,10 @@ The optional FORCE option is for internal use only."
                 window)
         (when (and (floatp max-mini-window-height)
                    (> height (* max-mini-window-height
-                                (frame-pixel-height exwm-workspace--current))))
+                                (exwm-workspace--current-height))))
           (setq height (floor
                         (* max-mini-window-height
-                           (frame-pixel-height exwm-workspace--current))))
+                           (exwm-workspace--current-height))))
           (xcb:+request exwm--connection
               (make-instance 'xcb:ConfigureWindow
                              :window window
@@ -360,7 +379,7 @@ The optional FORCE option is for internal use only."
             (setq value-mask xcb:ConfigWindow:Height
                   y 0)
           (setq value-mask (logior xcb:ConfigWindow:Y xcb:ConfigWindow:Height)
-                y (- (frame-pixel-height exwm-workspace--current) height)))
+                y (- (exwm-workspace--current-height) height)))
         (xcb:+request exwm--connection
             (make-instance 'xcb:ConfigureWindow
                            :window (frame-parameter exwm-workspace--minibuffer
@@ -456,6 +475,8 @@ This functions is modified from `display-buffer-reuse-window' and
     (cancel-timer exwm-workspace--display-echo-area-timer)
     (setq exwm-workspace--display-echo-area-timer nil)))
 
+(declare-function exwm-manage--unmanage-window "exwm-manage.el")
+
 (defun exwm-workspace--confirm-kill-emacs (prompt)
   "Confirm before exiting Emacs."
   (when (pcase (length exwm--id-buffer-alist)
@@ -610,13 +631,17 @@ This functions is modified from `display-buffer-reuse-window' and
   ;; Switch to the first workspace
   (exwm-workspace-switch 0 t))
 
+(defvar exwm-layout--fullscreen-frame-count)
+
 (defun exwm-workspace--post-init ()
   "The second stage in the initialization of the workspace module."
-  ;; Delay making the workspaces fullscreen until Emacs becomes idle
-  (run-with-idle-timer 0 nil
-                       (lambda ()
-                         (dolist (i exwm-workspace--list)
-                           (set-frame-parameter i 'fullscreen 'fullboth)))))
+  ;; Make the workspaces fullscreen.
+  (dolist (i exwm-workspace--list)
+    (set-frame-parameter i 'fullscreen 'fullboth))
+  ;; Wait until all workspace frames are resized.
+  (with-timeout (1)
+    (while (< exwm-layout--fullscreen-frame-count exwm-workspace-number)
+      (accept-process-output nil 0.1))))
 
 
 
diff --git a/exwm.el b/exwm.el
index 85c905e6efd6..b425acf7b538 100644
--- a/exwm.el
+++ b/exwm.el
@@ -30,11 +30,12 @@
 ;; --------
 ;; EXWM (Emacs X Window Manager) is a full-featured tiling X window manager for
 ;; Emacs built on top of XELB.  It features:
-;; + Fully keyboard-driven operation
+;; + Fully keyboard-driven operations
 ;; + Hybrid layout modes (tiling & stacking)
 ;; + Workspace support
 ;; + ICCCM/EWMH compliance
-;; + Basic RandR support (optional)
+;; ++ (Optional) RandR (multi-monitor) support
+;; ++ (Optional) system tray
 
 ;; Installation & configuration
 ;; ----------------------------
@@ -70,6 +71,7 @@
 (require 'exwm-manage)
 (require 'exwm-input)
 
+;;;###autoload
 (defun exwm-reset ()
   "Reset window to standard state: non-fullscreen, line-mode."
   (interactive)
@@ -80,7 +82,6 @@
       (exwm-layout--refresh)
       (exwm-input-grab-keyboard))))
 
-;;;###autoload
 (defun exwm--update-window-type (id &optional force)
   "Update _NET_WM_WINDOW_TYPE."
   (with-current-buffer (exwm--id->buffer id)
@@ -94,7 +95,6 @@
 (defvar exwm-update-class-hook nil
   "Normal hook run when window class is updated.")
 
-;;;###autoload
 (defun exwm--update-class (id &optional force)
   "Update WM_CLASS."
   (with-current-buffer (exwm--id->buffer id)
@@ -110,7 +110,6 @@
 (defvar exwm-update-title-hook nil
   "Normal hook run when window title is updated.")
 
-;;;###autoload
 (defun exwm--update-utf8-title (id &optional force)
   "Update _NET_WM_NAME."
   (with-current-buffer (exwm--id->buffer id)
@@ -123,7 +122,6 @@
             (setq exwm--title-is-utf8 t)
             (run-hooks 'exwm-update-title-hook)))))))
 
-;;;###autoload
 (defun exwm--update-ctext-title (id &optional force)
   "Update WM_NAME."
   (with-current-buffer (exwm--id->buffer id)
@@ -136,13 +134,11 @@
           (when exwm-title
             (run-hooks 'exwm-update-title-hook)))))))
 
-;;;###autoload
 (defun exwm--update-title (id)
   "Update _NET_WM_NAME or WM_NAME."
   (exwm--update-utf8-title id)
   (exwm--update-ctext-title id))
 
-;;;###autoload
 (defun exwm--update-transient-for (id &optional force)
   "Update WM_TRANSIENT_FOR."
   (with-current-buffer (exwm--id->buffer id)
@@ -153,7 +149,6 @@
         (when reply                     ;nil when destroyed
           (setq exwm-transient-for (slot-value reply 'value)))))))
 
-;;;###autoload
 (defun exwm--update-normal-hints (id &optional force)
   "Update WM_NORMAL_HINTS."
   (with-current-buffer (exwm--id->buffer id)
@@ -201,7 +196,6 @@
                        (= exwm--normal-hints-min-height
                           exwm--normal-hints-max-height)))))))))
 
-;;;###autoload
 (defun exwm--update-hints (id &optional force)
   "Update WM_HINTS."
   (with-current-buffer (exwm--id->buffer id)
@@ -221,7 +215,6 @@
               (set-frame-parameter exwm--frame 'exwm--urgency t)
               (setq exwm-workspace--switch-history-outdated t))))))))
 
-;;;###autoload
 (defun exwm--update-protocols (id &optional force)
   "Update WM_PROTOCOLS."
   (with-current-buffer (exwm--id->buffer id)
@@ -232,7 +225,6 @@
         (when reply                     ;nil when destroyed
           (setq exwm--protocols (append (slot-value reply 'value) nil)))))))
 
-;;;###autoload
 (defun exwm--update-state (id &optional force)
   "Update WM_STATE."
   (with-current-buffer (exwm--id->buffer id)