diff options
-rw-r--r-- | README.md | 5 | ||||
-rw-r--r-- | exwm-core.el | 7 | ||||
-rw-r--r-- | exwm-floating.el | 17 | ||||
-rw-r--r-- | exwm-input.el | 21 | ||||
-rw-r--r-- | exwm-layout.el | 24 | ||||
-rw-r--r-- | exwm-manage.el | 29 | ||||
-rw-r--r-- | exwm-randr.el | 25 | ||||
-rw-r--r-- | exwm-systemtray.el | 388 | ||||
-rw-r--r-- | exwm-workspace.el | 55 | ||||
-rw-r--r-- | exwm.el | 16 |
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) |