[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[elpa] externals/exwm fdfdabf 4/4: Merge branch 'feat/systemtray' into e
From: |
Chris Feng |
Subject: |
[elpa] externals/exwm fdfdabf 4/4: Merge branch 'feat/systemtray' into externals/exwm |
Date: |
Sat, 20 Feb 2016 01:07:25 +0000 |
branch: externals/exwm
commit fdfdabf95ae75a2f7af2758594b5d0246882f5a0
Merge: 3f77220 08bf970
Author: Chris Feng <address@hidden>
Commit: Chris Feng <address@hidden>
Merge branch 'feat/systemtray' into externals/exwm
A simple system tray based on the X11 'System Tray' and XEmbed protocol.
---
README.md | 5 +-
exwm-core.el | 7 +-
exwm-floating.el | 17 +--
exwm-input.el | 21 +++-
exwm-layout.el | 24 +++-
exwm-manage.el | 29 +++--
exwm-randr.el | 25 ++--
exwm-systemtray.el | 388 ++++++++++++++++++++++++++++++++++++++++++++++++++++
exwm-workspace.el | 55 ++++++--
exwm.el | 16 +--
10 files changed, 516 insertions(+), 71 deletions(-)
diff --git a/README.md b/README.md
index 09fe470..7f918bd 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 b09ca52..4d936ed 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 82b4487..209539e 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 85be1ef..5e07803 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 52a84b0..c0f3c61 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 50784ce..224ee16 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 716d521..7f9b443 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 0000000..e9a9745
--- /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 <address@hidden>
+
+;; 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 99a7c7b..99e3b55 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 85c905e..b425acf 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)