diff options
-rw-r--r-- | README.md | 3 | ||||
-rw-r--r-- | exwm-cm.el | 2 | ||||
-rw-r--r-- | exwm-config.el | 62 | ||||
-rw-r--r-- | exwm-core.el | 11 | ||||
-rw-r--r-- | exwm-floating.el | 13 | ||||
-rw-r--r-- | exwm-input.el | 74 | ||||
-rw-r--r-- | exwm-layout.el | 39 | ||||
-rw-r--r-- | exwm-manage.el | 91 | ||||
-rw-r--r-- | exwm-randr.el | 237 | ||||
-rw-r--r-- | exwm-systemtray.el | 35 | ||||
-rw-r--r-- | exwm-workspace.el | 51 | ||||
-rw-r--r-- | exwm-xim.el | 781 | ||||
-rw-r--r-- | exwm.el | 55 | ||||
-rw-r--r-- | xinitrc | 19 |
14 files changed, 1234 insertions, 239 deletions
diff --git a/README.md b/README.md index 103948c63379..6d7e0dd1ff17 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,8 @@ It features: + Dynamic workspace support + ICCCM/EWMH compliance + (Optional) RandR (multi-monitor) support -+ (Optional) Built-in system tray ++ (Optional) Builtin system tray ++ (Optional) Builtin input method Please check out the [screenshots](https://github.com/ch11ng/exwm/wiki/Screenshots) diff --git a/exwm-cm.el b/exwm-cm.el index ff556fb21921..1c33f875a677 100644 --- a/exwm-cm.el +++ b/exwm-cm.el @@ -1,6 +1,6 @@ ;;; exwm-cm.el --- Compositing Manager for EXWM -*- lexical-binding: t -*- -;; Copyright (C) 2016-2018 Free Software Foundation, Inc. +;; Copyright (C) 2016-2019 Free Software Foundation, Inc. ;; Author: Chris Feng <chris.w.feng@gmail.com> diff --git a/exwm-config.el b/exwm-config.el index 89320bc1e6eb..6635e4345db3 100644 --- a/exwm-config.el +++ b/exwm-config.el @@ -1,6 +1,6 @@ ;;; exwm-config.el --- Predefined configurations -*- lexical-binding: t -*- -;; Copyright (C) 2015-2018 Free Software Foundation, Inc. +;; Copyright (C) 2015-2019 Free Software Foundation, Inc. ;; Author: Chris Feng <chris.w.feng@gmail.com> @@ -31,38 +31,44 @@ (defun exwm-config-default () "Default configuration of EXWM." ;; Set the initial workspace number. - (setq exwm-workspace-number 4) + (unless (get 'exwm-workspace-number 'saved-value) + (setq exwm-workspace-number 4)) ;; Make class name the buffer name (add-hook 'exwm-update-class-hook (lambda () (exwm-workspace-rename-buffer exwm-class-name))) - ;; 's-r': Reset - (exwm-input-set-key (kbd "s-r") #'exwm-reset) - ;; 's-w': Switch workspace - (exwm-input-set-key (kbd "s-w") #'exwm-workspace-switch) - ;; 's-N': Switch to certain workspace - (dotimes (i 10) - (exwm-input-set-key (kbd (format "s-%d" i)) - `(lambda () - (interactive) - (exwm-workspace-switch-create ,i)))) - ;; 's-&': Launch application - (exwm-input-set-key (kbd "s-&") - (lambda (command) - (interactive (list (read-shell-command "$ "))) - (start-process-shell-command command nil command))) + ;; Global keybindings. + (unless (get 'exwm-input-global-keys 'saved-value) + (setq exwm-input-global-keys + `( + ;; 's-r': Reset (to line-mode). + ([?\s-r] . exwm-reset) + ;; 's-w': Switch workspace. + ([?\s-w] . exwm-workspace-switch) + ;; 's-&': Launch application. + ([?\s-&] . (lambda (command) + (interactive (list (read-shell-command "$ "))) + (start-process-shell-command command nil command))) + ;; 's-N': Switch to certain workspace. + ,@(mapcar (lambda (i) + `(,(kbd (format "s-%d" i)) . + (lambda () + (interactive) + (exwm-workspace-switch-create ,i)))) + (number-sequence 0 9))))) ;; Line-editing shortcuts - (setq exwm-input-simulation-keys - '(([?\C-b] . [left]) - ([?\C-f] . [right]) - ([?\C-p] . [up]) - ([?\C-n] . [down]) - ([?\C-a] . [home]) - ([?\C-e] . [end]) - ([?\M-v] . [prior]) - ([?\C-v] . [next]) - ([?\C-d] . [delete]) - ([?\C-k] . [S-end delete]))) + (unless (get 'exwm-input-simulation-keys 'saved-value) + (setq exwm-input-simulation-keys + '(([?\C-b] . [left]) + ([?\C-f] . [right]) + ([?\C-p] . [up]) + ([?\C-n] . [down]) + ([?\C-a] . [home]) + ([?\C-e] . [end]) + ([?\M-v] . [prior]) + ([?\C-v] . [next]) + ([?\C-d] . [delete]) + ([?\C-k] . [S-end delete])))) ;; Enable EXWM (exwm-enable) ;; Configure Ido diff --git a/exwm-core.el b/exwm-core.el index 1f939491c5ef..9b6877b83f11 100644 --- a/exwm-core.el +++ b/exwm-core.el @@ -1,6 +1,6 @@ ;;; exwm-core.el --- Core definitions -*- lexical-binding: t -*- -;; Copyright (C) 2015-2018 Free Software Foundation, Inc. +;; Copyright (C) 2015-2019 Free Software Foundation, Inc. ;; Author: Chris Feng <chris.w.feng@gmail.com> @@ -131,6 +131,15 @@ Nil can be passed as placeholder." (if height xcb:ConfigWindow:Height 0)) :x x :y y :width width :height height))) +(defun exwm--intern-atom (atom) + "Intern X11 ATOM." + (slot-value (xcb:+request-unchecked+reply exwm--connection + (make-instance 'xcb:InternAtom + :only-if-exists 0 + :name-len (length atom) + :name atom)) + 'atom)) + (defmacro exwm--defer (secs function &rest args) "Defer the execution of FUNCTION. diff --git a/exwm-floating.el b/exwm-floating.el index aa2f98822a2e..b7430d719e17 100644 --- a/exwm-floating.el +++ b/exwm-floating.el @@ -1,6 +1,6 @@ ;;; exwm-floating.el --- Floating Module for EXWM -*- lexical-binding: t -*- -;; Copyright (C) 2015-2018 Free Software Foundation, Inc. +;; Copyright (C) 2015-2019 Free Software Foundation, Inc. ;; Author: Chris Feng <chris.w.feng@gmail.com> @@ -83,6 +83,7 @@ This is also used by X window containers.") (defun exwm-floating--set-allowed-actions (id tilling) "Set _NET_WM_ALLOWED_ACTIONS." + (exwm--log "#x%x" id) (xcb:+request exwm--connection (make-instance 'xcb:ewmh:set-_NET_WM_ALLOWED_ACTIONS :window id @@ -326,6 +327,7 @@ This is also used by X window containers.") (defun exwm-floating--unset-floating (id) "Make window ID non-floating." + (exwm--log "#x%x" id) (let ((buffer (exwm--id->buffer id))) (with-current-buffer buffer (when exwm--floating-frame @@ -400,6 +402,7 @@ This is also used by X window containers.") (cl-defun exwm-floating-toggle-floating () "Toggle the current window between floating and non-floating states." (interactive) + (exwm--log) (unless (derived-mode-p 'exwm-mode) (cl-return-from exwm-floating-toggle-floating)) (with-current-buffer (window-buffer) @@ -411,6 +414,7 @@ This is also used by X window containers.") (defun exwm-floating-hide () "Hide the current floating X window (which would show again when selected)." (interactive) + (exwm--log) (when (and (derived-mode-p 'exwm-mode) exwm--floating-frame) (exwm-layout--hide exwm--id) @@ -418,6 +422,7 @@ This is also used by X window containers.") (defun exwm-floating--start-moveresize (id &optional type) "Start move/resize." + (exwm--log "#x%x" id) (let ((buffer-or-id (or (exwm--id->buffer id) id)) frame container-or-id x y width height cursor) (if (bufferp buffer-or-id) @@ -581,6 +586,7 @@ This is also used by X window containers.") (defun exwm-floating--stop-moveresize (&rest _args) "Stop move/resize." + (exwm--log) (xcb:+request exwm--connection (make-instance 'xcb:UngrabPointer :time xcb:Time:CurrentTime)) (when exwm-floating--moveresize-calculate @@ -641,6 +647,7 @@ This is also used by X window containers.") "Move a floating window right by DELTA-X pixels and down by DELTA-Y pixels. Both DELTA-X and DELTA-Y default to 1. This command should be bound locally." + (exwm--log "delta-x: %s, delta-y: %s" delta-x delta-y) (unless (and (derived-mode-p 'exwm-mode) exwm--floating-frame) (user-error "[EXWM] `exwm-floating-move' is only for floating X windows")) (unless delta-x (setq delta-x 1)) @@ -663,6 +670,7 @@ Both DELTA-X and DELTA-Y default to 1. This command should be bound locally." (defun exwm-floating--init () "Initialize floating module." + (exwm--log) ;; Check border width. (unless (and (integerp exwm-floating-border-width) (> exwm-floating-border-width 0)) @@ -708,7 +716,8 @@ Both DELTA-X and DELTA-Y default to 1. This command should be bound locally." (xcb:cursor:load-cursor exwm--connection "left_side"))) (defun exwm-floating--exit () - "Exit the floating module.") + "Exit the floating module." + (exwm--log)) diff --git a/exwm-input.el b/exwm-input.el index a7fb16a1755f..d0ae4ad81250 100644 --- a/exwm-input.el +++ b/exwm-input.el @@ -1,6 +1,6 @@ ;;; exwm-input.el --- Input Module for EXWM -*- lexical-binding: t -*- -;; Copyright (C) 2015-2018 Free Software Foundation, Inc. +;; Copyright (C) 2015-2019 Free Software Foundation, Inc. ;; Author: Chris Feng <chris.w.feng@gmail.com> @@ -117,6 +117,9 @@ defined in `exwm-mode-map' here." (defvar exwm-input--simulation-keys nil "Simulation keys in line-mode.") +(defvar exwm-input--skip-buffer-list-update nil + "Skip the upcoming 'buffer-list-update'.") + (defvar exwm-input--temp-line-mode nil "Non-nil indicates it's in temporary line-mode for char-mode.") @@ -224,7 +227,7 @@ ARGS are additional arguments to CALLBACK." (with-slots (time root event root-x root-y event-x event-y state) evt (setq buffer (exwm--id->buffer event) window (get-buffer-window buffer t)) - (exwm--log "EnterNotify: buffer=%s; window=%s" buffer window) + (exwm--log "buffer=%s; window=%s" buffer window) (when (and buffer window (not (eq window (selected-window)))) (setq frame (window-frame window) frame-xid (frame-parameter frame 'exwm-id)) @@ -260,12 +263,14 @@ ARGS are additional arguments to CALLBACK." (xcb:flush exwm--connection))))) (defun exwm-input--on-keysyms-update () + (exwm--log) (let ((exwm-input--global-prefix-keys nil)) (exwm-input--update-global-prefix-keys))) (defun exwm-input--on-buffer-list-update () "Run in `buffer-list-update-hook' to track input focus." (when (and (not (eq this-command #'handle-switch-frame)) + (not exwm-input--skip-buffer-list-update) (not (exwm-workspace--client-p)) ;; The following conditions filter out events relating to temp ;; buffers. @@ -380,6 +385,7 @@ ARGS are additional arguments to CALLBACK." buffer (exwm--id->buffer event) window (get-buffer-window buffer t)) (cond ((and (eq button-event exwm-input-move-event) + buffer ;; Either an undecorated or a floating X window. (with-current-buffer buffer (or (not (derived-mode-p 'exwm-mode)) @@ -388,12 +394,13 @@ ARGS are additional arguments to CALLBACK." (exwm-floating--start-moveresize event xcb:ewmh:_NET_WM_MOVERESIZE_MOVE)) ((and (eq button-event exwm-input-resize-event) + buffer (with-current-buffer buffer (or (not (derived-mode-p 'exwm-mode)) exwm--floating-frame))) ;; Resize (exwm-floating--start-moveresize event)) - (t + (buffer ;; Click to focus (unless (eq window (selected-window)) (setq frame (window-frame window)) @@ -412,13 +419,18 @@ ARGS are additional arguments to CALLBACK." (select-window window) (setq window (get-buffer-window buffer t)) (when window (select-window window)))) + ;; Also process keybindings. (with-current-buffer buffer (when (derived-mode-p 'exwm-mode) (cl-case exwm--input-mode (line-mode - (setq mode (exwm-input--on-ButtonPress-line-mode buffer button-event))) + (setq mode (exwm-input--on-ButtonPress-line-mode + buffer button-event))) (char-mode - (setq mode (exwm-input--on-ButtonPress-char-mode))))))))) + (setq mode (exwm-input--on-ButtonPress-char-mode))))))) + (t + ;; Replay this event by default. + (setq mode xcb:Allow:ReplayPointer)))) (xcb:+request exwm--connection (make-instance 'xcb:AllowEvents :mode mode :time xcb:Time:CurrentTime)) (xcb:flush exwm--connection)) @@ -491,6 +503,7 @@ ARGS are additional arguments to CALLBACK." (xcb:flush exwm--connection))) (defun exwm-input--set-key (key command) + (exwm--log "key: %s, command: %s" key command) (global-set-key key command) (cl-pushnew key exwm-input--global-keys)) @@ -534,9 +547,9 @@ instead." (exwm-input--update-global-prefix-keys))) ;; Putting (t . EVENT) into `unread-command-events' does not really work -;; as documented for Emacs < 27. +;; as documented for Emacs < 26.2. (eval-and-compile - (if (< emacs-major-version 27) + (if (string-version-lessp emacs-version "26.2") (defsubst exwm-input--unread-event (event) (setq unread-command-events (append unread-command-events (list event)))) @@ -570,8 +583,9 @@ instead." (cl-return-from exwm-input--translate translation))))) key) -(defun exwm-input--cache-event (event) +(defun exwm-input--cache-event (event &optional temp-line-mode) "Cache EVENT." + (exwm--log "%s" event) (setq exwm-input--line-mode-cache (vconcat exwm-input--line-mode-cache (vector event))) ;; Attempt to translate this key sequence. @@ -580,8 +594,12 @@ instead." ;; When the key sequence is complete (not a keymap). ;; Note that `exwm-input--line-mode-cache' might get translated to nil, for ;; example 'mouse--down-1-maybe-follows-link' does this. - (unless (and exwm-input--line-mode-cache - (keymapp (key-binding exwm-input--line-mode-cache))) + (if (and exwm-input--line-mode-cache + (keymapp (key-binding exwm-input--line-mode-cache))) + ;; Grab keyboard temporarily to intercept the complete key sequence. + (when temp-line-mode + (setq exwm-input--temp-line-mode t) + (exwm-input--grab-keyboard)) (setq exwm-input--line-mode-cache nil) (when exwm-input--temp-line-mode (setq exwm-input--temp-line-mode nil) @@ -657,10 +675,7 @@ Current buffer must be an `exwm-mode' buffer." (setq event (exwm-input--mimic-read-event raw-event))) (if (not (derived-mode-p 'exwm-mode)) (exwm-input--unread-event raw-event) - ;; Grab keyboard temporarily. - (setq exwm-input--temp-line-mode t) - (exwm-input--grab-keyboard) - (exwm-input--cache-event event) + (exwm-input--cache-event event t) (exwm-input--unread-event raw-event))))) (xcb:+request exwm--connection (make-instance 'xcb:AllowEvents @@ -677,6 +692,7 @@ The return value is used as event_mode to release the original button event." (with-current-buffer buffer (let ((read-event (exwm-input--mimic-read-event button-event))) + (exwm--log "%s" read-event) (if (and read-event (exwm-input--event-passthrough-p read-event)) ;; The event should be forwarded to emacs @@ -691,10 +707,12 @@ button event." "Handle button events in char-mode. The return value is used as event_mode to release the original button event." + (exwm--log) xcb:Allow:ReplayPointer) (defun exwm-input--update-mode-line (id) "Update the propertized `mode-line-process' for window ID." + (exwm--log "#x%x" id) (let (help-echo cmd mode) (cl-case exwm--input-mode (line-mode @@ -826,6 +844,7 @@ button event." EXWM will prompt for the key to send. This command can be prefixed to send multiple keys." (interactive "p") + (exwm--log) (unless (derived-mode-p 'exwm-mode) (cl-return-from exwm-input-send-next-key)) (when (> times 12) (setq times 12)) @@ -844,6 +863,7 @@ multiple keys." (defun exwm-input--set-simulation-keys (simulation-keys &optional no-refresh) "Set simulation keys." + (exwm--log "%s" simulation-keys) (unless no-refresh ;; Unbind simulation keys. (let ((hash (buffer-local-value 'exwm-input--simulation-keys @@ -894,14 +914,15 @@ Notes: * Setting the value directly (rather than customizing it) after EXWM finishes initialization has no effect. * Original-keys consist of multiple key events are only supported in Emacs - 27 and later. + 26.2 and later. * A minority of applications do not accept simulated keys by default. It's required to customize them to accept events sent by SendEvent. * The predefined examples in the Customize interface are not guaranteed to work for all applications. This can be tweaked on a per application basis with `exwm-input-set-local-simulation-keys'." - :type '(alist :key-type (choice (key-sequence :tag "Original")) - :value-type (choice (key-sequence :tag "Move left" [left]) + :type '(alist :key-type (key-sequence :tag "Original") + :value-type (choice (key-sequence :tag "User-defined") + (key-sequence :tag "Move left" [left]) (key-sequence :tag "Move right" [right]) (key-sequence :tag "Move up" [up]) (key-sequence :tag "Move down" [down]) @@ -913,8 +934,7 @@ Notes: (key-sequence :tag "Paste" [C-v]) (key-sequence :tag "Delete" [delete]) (key-sequence :tag "Delete to EOL" - [S-end delete]) - (key-sequence :tag "User-defined"))) + [S-end delete]))) :set (lambda (symbol value) (set symbol value) (exwm-input--set-simulation-keys value))) @@ -945,6 +965,7 @@ ends unless it's specifically saved in the Customize interface for (format "Simulate %s as" (key-description original)) ?\C-g))) (list original simulated))) + (exwm--log "original: %s, simulated: %s" original-key simulated-key) (when (and original-key simulated-key) (let ((entry `((,original-key . ,simulated-key)))) (setq exwm-input-simulation-keys (append exwm-input-simulation-keys @@ -953,6 +974,7 @@ ends unless it's specifically saved in the Customize interface for (defun exwm-input--unset-simulation-keys () "Clear simulation keys and key bindings defined." + (exwm--log) (when (hash-table-p exwm-input--simulation-keys) (maphash (lambda (key _value) (when (sequencep key) @@ -965,6 +987,7 @@ ends unless it's specifically saved in the Customize interface for SIMULATION-KEYS is an alist of the form (original-key . simulated-key), where both ORIGINAL-KEY and SIMULATED-KEY are key sequences." + (exwm--log) (make-local-variable 'exwm-input--simulation-keys) (use-local-map (copy-keymap exwm-mode-map)) (let ((exwm-input--local-simulation-keys t)) @@ -974,6 +997,7 @@ where both ORIGINAL-KEY and SIMULATED-KEY are key sequences." (cl-defun exwm-input-send-simulation-key (times) "Fake a key event according to the last input key sequence." (interactive "p") + (exwm--log) (unless (derived-mode-p 'exwm-mode) (cl-return-from exwm-input-send-simulation-key)) (let ((keys (gethash (this-single-command-keys) @@ -993,6 +1017,7 @@ where both ORIGINAL-KEY and SIMULATED-KEY are key sequences." (defun exwm-input--init () "Initialize the keyboard module." + (exwm--log) ;; Refresh keyboard mapping (xcb:keysyms:init exwm--connection #'exwm-input--on-keysyms-update) ;; Create the X window and intern the atom used to fetch timestamp. @@ -1015,14 +1040,7 @@ where both ORIGINAL-KEY and SIMULATED-KEY are key sequences." (make-instance 'xcb:ewmh:set-_NET_WM_NAME :window exwm-input--timestamp-window :data "EXWM: exwm-input--timestamp-window")) - (let ((atom "_TIME")) - (setq exwm-input--timestamp-atom - (slot-value (xcb:+request-unchecked+reply exwm--connection - (make-instance 'xcb:InternAtom - :only-if-exists 0 - :name-len (length atom) - :name atom)) - 'atom))) + (setq exwm-input--timestamp-atom (exwm--intern-atom "_TIME")) ;; Initialize global keys. (dolist (i exwm-input-global-keys) (exwm-input--set-key (car i) (cdr i))) @@ -1050,10 +1068,12 @@ where both ORIGINAL-KEY and SIMULATED-KEY are key sequences." (defun exwm-input--post-init () "The second stage in the initialization of the input module." + (exwm--log) (exwm-input--update-global-prefix-keys)) (defun exwm-input--exit () "Exit the input module." + (exwm--log) (exwm-input--unset-simulation-keys) (remove-hook 'pre-command-hook #'exwm-input--on-pre-command) (remove-hook 'post-command-hook #'exwm-input--on-post-command) diff --git a/exwm-layout.el b/exwm-layout.el index bee6901f8291..52fd08e15274 100644 --- a/exwm-layout.el +++ b/exwm-layout.el @@ -1,6 +1,6 @@ ;;; exwm-layout.el --- Layout Module for EXWM -*- lexical-binding: t -*- -;; Copyright (C) 2015-2018 Free Software Foundation, Inc. +;; Copyright (C) 2015-2019 Free Software Foundation, Inc. ;; Author: Chris Feng <chris.w.feng@gmail.com> @@ -32,6 +32,10 @@ :version "25.3" :group 'exwm) +(defcustom exwm-layout-auto-iconify t + "Non-nil to automatically iconify unused X windows when possible." + :type 'boolean) + (defcustom exwm-layout-show-all-buffers nil "Non-nil to allow switching to buffers on other workspaces." :type 'boolean) @@ -76,6 +80,20 @@ (when (derived-mode-p 'exwm-mode) (memq xcb:Atom:_NET_WM_STATE_FULLSCREEN exwm--ewmh-state))) +(defun exwm-layout--auto-iconify () + (when (and exwm-layout-auto-iconify + (not exwm-transient-for)) + (let ((xwin exwm--id) + (state exwm-state)) + (dolist (pair exwm--id-buffer-alist) + (with-current-buffer (cdr pair) + (when (and exwm--floating-frame + (eq exwm-transient-for xwin) + (not (eq exwm-state state))) + (if (eq state xcb:icccm:WM_STATE:NormalState) + (exwm-layout--refresh-floating exwm--floating-frame) + (exwm-layout--hide exwm--id)))))))) + (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) @@ -111,7 +129,8 @@ height height*))) (exwm--set-geometry id x y width height) (xcb:+request exwm--connection (make-instance 'xcb:MapWindow :window id)) - (exwm-layout--set-state id xcb:icccm:WM_STATE:NormalState))) + (exwm-layout--set-state id xcb:icccm:WM_STATE:NormalState) + (exwm-layout--auto-iconify))) (xcb:flush exwm--connection)) (defun exwm-layout--hide (id) @@ -141,6 +160,7 @@ :window id :value-mask xcb:CW:EventMask :event-mask exwm--client-event-mask)) (exwm-layout--set-state id xcb:icccm:WM_STATE:IconicState) + (exwm-layout--auto-iconify) (xcb:flush exwm--connection)))) ;;;###autoload @@ -296,7 +316,9 @@ selected by `other-buffer'." (dolist (window windows) (with-current-buffer (window-buffer window) (when (derived-mode-p 'exwm-mode) - (switch-to-prev-buffer window)))))) + (if (window-prev-buffers window) + (switch-to-prev-buffer window) + (switch-to-next-buffer window))))))) (defun exwm-layout--refresh-workspace (frame) "Refresh workspace frame FRAME." @@ -346,14 +368,18 @@ selected by `other-buffer'." ;; Set some sensible buffer to vacated windows. (let ((exwm-layout--other-buffer-exclude-buffers covered-buffers)) (dolist (window vacated-windows) - (switch-to-prev-buffer window))) + (if (window-prev-buffers window) + (switch-to-prev-buffer window) + (switch-to-next-buffer window)))) ;; Make sure windows floating / on other workspaces are excluded (let ((exwm-layout--other-buffer-exclude-exwm-mode-buffers t)) (dolist (window (window-list frame 'nomini)) (with-current-buffer (window-buffer window) (when (and (derived-mode-p 'exwm-mode) (or exwm--floating-frame (not (eq frame exwm--frame)))) - (switch-to-prev-buffer window))))) + (if (window-prev-buffers window) + (switch-to-prev-buffer window) + (switch-to-next-buffer window)))))) (exwm-layout--set-client-list-stacking) (xcb:flush exwm--connection))) @@ -473,6 +499,7 @@ windows." See also `exwm-layout-enlarge-window'." (interactive "p") + (exwm--log "%s" delta) (exwm-layout-enlarge-window delta t)) ;;;###autoload @@ -481,6 +508,7 @@ See also `exwm-layout-enlarge-window'." See also `exwm-layout-enlarge-window'." (interactive "p") + (exwm--log "%s" delta) (exwm-layout-enlarge-window (- delta))) ;;;###autoload @@ -489,6 +517,7 @@ See also `exwm-layout-enlarge-window'." See also `exwm-layout-enlarge-window'." (interactive "p") + (exwm--log "%s" delta) (exwm-layout-enlarge-window (- delta) t)) ;;;###autoload diff --git a/exwm-manage.el b/exwm-manage.el index 79c5405fffe8..b41512c485c0 100644 --- a/exwm-manage.el +++ b/exwm-manage.el @@ -1,7 +1,7 @@ ;;; exwm-manage.el --- Window Management Module for -*- lexical-binding: t -*- ;;; EXWM -;; Copyright (C) 2015-2018 Free Software Foundation, Inc. +;; Copyright (C) 2015-2019 Free Software Foundation, Inc. ;; Author: Chris Feng <chris.w.feng@gmail.com> @@ -70,6 +70,7 @@ You can still make the X windows floating afterwards." (const :tag "Prefix keys" prefix-keys) (const :tag "Simulation keys" simulation-keys) (const :tag "Workspace" workspace) + (const :tag "Managed" managed) ;; For forward compatibility. (other)) :value-type (sexp :tag "Value" nil)))) @@ -90,6 +91,7 @@ You can still make the X windows floating afterwards." (defvar exwm-manage--ping-lock nil "Non-nil indicates EXWM is pinging a window.") +(defvar exwm-input--skip-buffer-list-update) (defvar exwm-input-prefix-keys) (defvar exwm-workspace--current) (defvar exwm-workspace--id-struts-alist) @@ -200,7 +202,8 @@ You can still make the X windows floating afterwards." (make-instance 'xcb:ChangeSaveSet :mode xcb:SetMode:Insert :window id)) - (with-current-buffer (generate-new-buffer "*EXWM*") + (with-current-buffer (let ((exwm-input--skip-buffer-list-update t)) + (generate-new-buffer "*EXWM*")) ;; Keep the oldest X window first. (setq exwm--id-buffer-alist (nconc exwm--id-buffer-alist `((,id . ,(current-buffer))))) @@ -214,22 +217,33 @@ You can still make the X windows floating afterwards." (exwm--update-hints id) (exwm-manage--update-geometry id) (exwm-manage--update-mwm-hints id) - ;; No need to manage (please check OverrideRedirect outside) - (when (or - (not - (or (not exwm-window-type) - (memq xcb:Atom:_NET_WM_WINDOW_TYPE_UTILITY exwm-window-type) - (memq xcb:Atom:_NET_WM_WINDOW_TYPE_DIALOG exwm-window-type) - (memq xcb:Atom:_NET_WM_WINDOW_TYPE_NORMAL exwm-window-type))) - ;; Check the _MOTIF_WM_HINTS property. - (and (not exwm--mwm-hints-decorations) - (not exwm--hints-input) - ;; Floating windows only - (or exwm-transient-for exwm--fixed-size - (memq xcb:Atom:_NET_WM_WINDOW_TYPE_UTILITY - exwm-window-type) - (memq xcb:Atom:_NET_WM_WINDOW_TYPE_DIALOG - exwm-window-type)))) + (exwm--update-title id) + (exwm--update-protocols id) + (setq exwm--configurations (exwm-manage--get-configurations)) + ;; OverrideRedirect is not checked here. + (when (and + ;; The user has specified to manage it. + (not (plist-get exwm--configurations 'managed)) + (or + ;; The user has specified not to manage it. + (plist-member exwm--configurations 'managed) + ;; This is not a type of X window we can manage. + (and exwm-window-type + (not (cl-intersection + exwm-window-type + (list xcb:Atom:_NET_WM_WINDOW_TYPE_UTILITY + xcb:Atom:_NET_WM_WINDOW_TYPE_DIALOG + xcb:Atom:_NET_WM_WINDOW_TYPE_NORMAL)))) + ;; Check the _MOTIF_WM_HINTS property to not manage floating X + ;; windows without decoration. + (and (not exwm--mwm-hints-decorations) + (not exwm--hints-input) + ;; Floating windows only + (or exwm-transient-for exwm--fixed-size + (memq xcb:Atom:_NET_WM_WINDOW_TYPE_UTILITY + exwm-window-type) + (memq xcb:Atom:_NET_WM_WINDOW_TYPE_DIALOG + exwm-window-type))))) (exwm--log "No need to manage #x%x" id) ;; Update struts. (when (memq xcb:Atom:_NET_WM_WINDOW_TYPE_DOCK exwm-window-type) @@ -274,10 +288,10 @@ You can still make the X windows floating afterwards." :stack-mode xcb:StackMode:Below))) (xcb:flush exwm--connection) (setq exwm--id-buffer-alist (assq-delete-all id exwm--id-buffer-alist)) - (let ((kill-buffer-query-functions nil)) + (let ((kill-buffer-query-functions nil) + (exwm-input--skip-buffer-list-update t)) (kill-buffer (current-buffer))) (throw 'return 'ignored)) - (setq exwm--configurations (exwm-manage--get-configurations)) (let ((index (plist-get exwm--configurations 'workspace))) (when (and index (< index (length exwm-workspace--list))) (setq exwm--frame (elt exwm-workspace--list index)))) @@ -299,8 +313,6 @@ You can still make the X windows floating afterwards." :button button :modifiers xcb:ModMask:Any))) (exwm-manage--set-client-list) (xcb:flush exwm--connection) - (exwm--update-title id) - (exwm--update-protocols id) (if (plist-member exwm--configurations 'floating) ;; User has specified whether it should be floating. (if (plist-get exwm--configurations 'floating) @@ -556,7 +568,7 @@ Would you like to kill it? " (with-slots (window x y width height border-width sibling stack-mode value-mask) obj - (exwm--log "ConfigureRequest from #x%x (#x%x) @%dx%d%+d%+d; \ + (exwm--log "#x%x (#x%x) @%dx%d%+d%+d; \ border-width: %d; sibling: #x%x; stack-mode: %d" window value-mask width height x y border-width sibling stack-mode) @@ -654,7 +666,7 @@ border-width: %d; sibling: #x%x; stack-mode: %d" (progn (xcb:+request exwm--connection (make-instance 'xcb:MapWindow :window window)) (xcb:flush exwm--connection)) - (exwm--log "MapRequest from #x%x" window) + (exwm--log "#x%x" window) (exwm-manage--manage-window window)))))) (defun exwm-manage--on-UnmapNotify (data _synthetic) @@ -674,11 +686,20 @@ border-width: %d; sibling: #x%x; stack-mode: %d" (exwm--log "id=#x%x" window) ;; With this we ensure that a "window hierarchy change" happens after ;; mapping the window, as some servers (XQuartz) do not generate it. - (xcb:+request exwm--connection - (make-instance 'xcb:ConfigureWindow - :window window - :value-mask xcb:ConfigWindow:StackMode - :stack-mode xcb:StackMode:Above)) + (with-current-buffer (exwm--id->buffer window) + (if exwm--floating-frame + (xcb:+request exwm--connection + (make-instance 'xcb:ConfigureWindow + :window window + :value-mask xcb:ConfigWindow:StackMode + :stack-mode xcb:StackMode:Above)) + (xcb:+request exwm--connection + (make-instance 'xcb:ConfigureWindow + :window window + :value-mask (logior xcb:ConfigWindow:Sibling + xcb:ConfigWindow:StackMode) + :sibling exwm--guide-window + :stack-mode xcb:StackMode:Above)))) (xcb:flush exwm--connection))))) (defun exwm-manage--on-DestroyNotify (data synthetic) @@ -687,21 +708,14 @@ border-width: %d; sibling: #x%x; stack-mode: %d" (exwm--log) (let ((obj (make-instance 'xcb:DestroyNotify))) (xcb:unmarshal obj data) - (exwm--log "DestroyNotify from #x%x" (slot-value obj 'window)) + (exwm--log "#x%x" (slot-value obj 'window)) (exwm-manage--unmanage-window (slot-value obj 'window))))) (defun exwm-manage--init () "Initialize manage module." ;; Intern _MOTIF_WM_HINTS (exwm--log) - (let ((atom-name "_MOTIF_WM_HINTS")) - (setq exwm-manage--_MOTIF_WM_HINTS - (slot-value (xcb:+request-unchecked+reply exwm--connection - (make-instance 'xcb:InternAtom - :only-if-exists 0 - :name-len (length atom-name) - :name atom-name)) - 'atom))) + (setq exwm-manage--_MOTIF_WM_HINTS (exwm--intern-atom "_MOTIF_WM_HINTS")) (add-hook 'after-make-frame-functions #'exwm-manage--add-frame) (add-hook 'delete-frame-functions #'exwm-manage--remove-frame) (xcb:+event exwm--connection 'xcb:ConfigureRequest @@ -714,6 +728,7 @@ border-width: %d; sibling: #x%x; stack-mode: %d" (defun exwm-manage--exit () "Exit the manage module." + (exwm--log) (dolist (pair exwm--id-buffer-alist) (exwm-manage--unmanage-window (car pair) 'quit)) (remove-hook 'after-make-frame-functions #'exwm-manage--add-frame) diff --git a/exwm-randr.el b/exwm-randr.el index af900ab36f0d..7d20022e9e53 100644 --- a/exwm-randr.el +++ b/exwm-randr.el @@ -1,6 +1,6 @@ ;;; exwm-randr.el --- RandR Module for EXWM -*- lexical-binding: t -*- -;; Copyright (C) 2015-2018 Free Software Foundation, Inc. +;; Copyright (C) 2015-2019 Free Software Foundation, Inc. ;; Author: Chris Feng <chris.w.feng@gmail.com> @@ -27,11 +27,11 @@ ;; that. ;; To use this module, load, enable it and configure -;; `exwm-randr-workspace-output-plist' and `exwm-randr-screen-change-hook' +;; `exwm-randr-workspace-monitor-plist' and `exwm-randr-screen-change-hook' ;; as follows: ;; ;; (require 'exwm-randr) -;; (setq exwm-randr-workspace-output-plist '(0 "VGA1")) +;; (setq exwm-randr-workspace-monitor-plist '(0 "VGA1")) ;; (add-hook 'exwm-randr-screen-change-hook ;; (lambda () ;; (start-process-shell-command @@ -48,7 +48,9 @@ ;;; Code: (require 'xcb-randr) + (require 'exwm-core) +(require 'exwm-workspace) (defgroup exwm-randr nil "RandR." @@ -63,83 +65,94 @@ "Normal hook run when screen changes." :type 'hook) -(defcustom exwm-randr-workspace-output-plist nil - "Plist mapping workspace to output. +(defcustom exwm-randr-workspace-monitor-plist nil + "Plist mapping workspaces to monitors. + +In RandR 1.5 a monitor is a rectangle region decoupled from the physical +size of screens, and can be identified with `xrandr --listmonitors' (name of +the primary monitor is prefixed with an `*'). When no monitor is created it +automatically fallback to RandR 1.2 output which represents the physical +screen size. RandR 1.5 monitors can be created with `xrandr --setmonitor'. +For example, to split an output (`LVDS-1') of size 1280x800 into two +side-by-side monitors one could invoke (the digits after `/' are size in mm) -If an output is not available, the workspaces mapped to it are displayed on -the primary output until it becomes available. Unspecified workspaces are -all mapped to the primary output. For example, with the following value -workspace other than 1 and 3 would always be displayed on the primary output -where workspace 1 and 3 would be displayed on their corresponding output -whenever the outputs are available. + xrandr --setmonitor *LVDS-1-L 640/135x800/163+0+0 LVDS-1 + xrandr --setmonitor LVDS-1-R 640/135x800/163+640+0 none - '(1 \"HDMI-1\" 3 \"DP-1\") +If a monitor is not active, the workspaces mapped to it are displayed on the +primary monitor until it becomes active (if ever). Unspecified workspaces +are all mapped to the primary monitor. For example, with the following +setting workspace other than 1 and 3 would always be displayed on the +primary monitor where workspace 1 and 3 would be displayed on their +corresponding monitors whenever the monitors are active. -The outputs available can be identified by running the 'xrandr' utility with -the first one in result being the primary output." + \\='(1 \"HDMI-1\" 3 \"DP-1\")" :type '(plist :key-type integer :value-type string)) -(defvar exwm-workspace--fullscreen-frame-count) -(defvar exwm-workspace--list) -(declare-function exwm-workspace--count "exwm-workspace.el") -(declare-function exwm-workspace--set-active "exwm-workspace.el" - (frame active)) -(declare-function exwm-workspace--set-desktop-geometry "exwm-workspace.el" ()) -(declare-function exwm-workspace--set-fullscreen "exwm-workspace.el" (frame)) -(declare-function exwm-workspace--show-minibuffer "exwm-workspace.el" ()) -(declare-function exwm-workspace--update-workareas "exwm-workspace.el" ()) - -(defun exwm-randr--refresh () - "Refresh workspaces according to the updated RandR info." - (let (output-name geometry output-plist primary-output default-geometry - container-output-alist container-frame-alist) - ;; Query all outputs - (with-slots (config-timestamp outputs) +(with-no-warnings + (define-obsolete-variable-alias 'exwm-randr-workspace-output-plist + 'exwm-randr-workspace-monitor-plist "27.1")) + +(defvar exwm-randr--last-timestamp 0 "Used for debouncing events.") + +(defvar exwm-randr--prev-screen-change-seqnum nil + "The most recent ScreenChangeNotify sequence number.") + +(defun exwm-randr--get-monitors () + "Get RandR monitors." + (exwm--log) + (let (monitor-name geometry monitor-plist primary-monitor) + (with-slots (timestamp monitors) (xcb:+request-unchecked+reply exwm--connection - (make-instance 'xcb:randr:GetScreenResourcesCurrent - :window exwm--root)) - (dolist (output outputs) - (with-slots (crtc connection name) - (xcb:+request-unchecked+reply exwm--connection - (make-instance 'xcb:randr:GetOutputInfo - :output output - :config-timestamp config-timestamp)) - (setf output-name ;UTF-8 encoded - (decode-coding-string (apply #'unibyte-string name) 'utf-8)) - (if (or (/= connection xcb:randr:Connection:Connected) - (= 0 crtc)) ;FIXME - (plist-put output-plist output-name nil) - (with-slots (x y width height) - (xcb:+request-unchecked+reply exwm--connection - (make-instance 'xcb:randr:GetCrtcInfo - :crtc crtc - :config-timestamp config-timestamp)) - (setq geometry (make-instance 'xcb:RECTANGLE - :x x :y y - :width width :height height) - output-plist (plist-put output-plist output-name geometry)) - (unless primary-output - (setq primary-output output-name - default-geometry geometry))))))) - (exwm--log "(randr) outputs: %s" output-plist) - (when output-plist + (make-instance 'xcb:randr:GetMonitors + :window exwm--root + :get-active 1)) + (when (> timestamp exwm-randr--last-timestamp) + (setq exwm-randr--last-timestamp timestamp)) + (dolist (monitor monitors) + (with-slots (name primary x y width height) monitor + (setq monitor-name (x-get-atom-name name) + geometry (make-instance 'xcb:RECTANGLE + :x x + :y y + :width width + :height height) + monitor-plist (plist-put monitor-plist monitor-name geometry)) + (exwm--log "%s: %sx%s+%s+%s" monitor-name x y width height) + ;; Save primary monitor when available (fallback to the first one). + (when (or (/= 0 primary) + (not primary-monitor)) + (setq primary-monitor monitor-name))))) + (exwm--log "Primary monitor: %s" primary-monitor) + (list primary-monitor monitor-plist))) + +;;;###autoload +(defun exwm-randr-refresh () + "Refresh workspaces according to the updated RandR info." + (interactive) + (exwm--log) + (let* ((result (exwm-randr--get-monitors)) + (primary-monitor (elt result 0)) + (monitor-plist (elt result 1)) + container-monitor-alist container-frame-alist) + (when (and primary-monitor monitor-plist) (when exwm-workspace--fullscreen-frame-count ;; Not all workspaces are fullscreen; reset this counter. (setq exwm-workspace--fullscreen-frame-count 0)) (dotimes (i (exwm-workspace--count)) - (let* ((output (plist-get exwm-randr-workspace-output-plist i)) - (geometry (lax-plist-get output-plist output)) + (let* ((monitor (plist-get exwm-randr-workspace-monitor-plist i)) + (geometry (lax-plist-get monitor-plist monitor)) (frame (elt exwm-workspace--list i)) (container (frame-parameter frame 'exwm-container))) (unless geometry - (setq geometry default-geometry - output primary-output)) - (setq container-output-alist (nconc - `((,container . ,(intern output))) - container-output-alist) + (setq monitor primary-monitor + geometry (lax-plist-get monitor-plist primary-monitor))) + (setq container-monitor-alist (nconc + `((,container . ,(intern monitor))) + container-monitor-alist) container-frame-alist (nconc `((,container . ,frame)) container-frame-alist)) - (set-frame-parameter frame 'exwm-randr-output output) + (set-frame-parameter frame 'exwm-randr-monitor monitor) (set-frame-parameter frame 'exwm-geometry geometry))) ;; Update workareas. (exwm-workspace--update-workareas) @@ -156,72 +169,114 @@ the first one in result being the primary output." ;; Update active/inactive workspaces. (dolist (w exwm-workspace--list) (exwm-workspace--set-active w nil)) + ;; Mark the workspace on the top of each monitor as active. (dolist (xwin (reverse (slot-value (xcb:+request-unchecked+reply exwm--connection (make-instance 'xcb:QueryTree :window exwm--root)) 'children))) - (let ((output (cdr (assq xwin container-output-alist)))) - (when output - (setq container-output-alist - (rassq-delete-all output container-output-alist)) + (let ((monitor (cdr (assq xwin container-monitor-alist)))) + (when monitor + (setq container-monitor-alist + (rassq-delete-all monitor container-monitor-alist)) (exwm-workspace--set-active (cdr (assq xwin container-frame-alist)) t)))) (xcb:flush exwm--connection) (run-hooks 'exwm-randr-refresh-hook)))) -(defun exwm-randr--on-ScreenChangeNotify (_data _synthetic) - (exwm--log "(RandR) ScreenChangeNotify") - (run-hooks 'exwm-randr-screen-change-hook) - (exwm-randr--refresh)) +(define-obsolete-function-alias 'exwm-randr--refresh #'exwm-randr-refresh + "27.1") + +(defun exwm-randr--on-ScreenChangeNotify (data _synthetic) + "Handle `ScreenChangeNotify' event. + +Run `exwm-randr-screen-change-hook' (usually user scripts to configure RandR)." + (exwm--log) + (let ((evt (make-instance 'xcb:randr:ScreenChangeNotify))) + (xcb:unmarshal evt data) + (let ((seqnum (slot-value evt '~sequence))) + (unless (equal seqnum exwm-randr--prev-screen-change-seqnum) + (setq exwm-randr--prev-screen-change-seqnum seqnum) + (run-hooks 'exwm-randr-screen-change-hook))))) + +(defun exwm-randr--on-Notify (data _synthetic) + "Handle `CrtcChangeNotify' and `OutputChangeNotify' events. + +Refresh when any CRTC/output changes." + (exwm--log) + (let ((evt (make-instance 'xcb:randr:Notify)) + notify) + (xcb:unmarshal evt data) + (with-slots (subCode u) evt + (cl-case subCode + (xcb:randr:Notify:CrtcChange + (setq notify (slot-value u 'cc))) + (xcb:randr:Notify:OutputChange + (setq notify (slot-value u 'oc)))) + (when notify + (with-slots (timestamp) notify + (when (> timestamp exwm-randr--last-timestamp) + (exwm-randr-refresh) + (setq exwm-randr--last-timestamp timestamp))))))) + +(defun exwm-randr--on-ConfigureNotify (data _synthetic) + "Handle `ConfigureNotify' event. + +Refresh when any RandR 1.5 monitor changes." + (exwm--log) + (let ((evt (make-instance 'xcb:ConfigureNotify))) + (xcb:unmarshal evt data) + (with-slots (window) evt + (when (eq window exwm--root) + (exwm-randr-refresh))))) (defun exwm-randr--init () "Initialize RandR extension and EXWM RandR module." + (exwm--log) (if (= 0 (slot-value (xcb:get-extension-data exwm--connection 'xcb:randr) 'present)) (error "[EXWM] RandR extension is not supported by the server") (with-slots (major-version minor-version) (xcb:+request-unchecked+reply exwm--connection (make-instance 'xcb:randr:QueryVersion - :major-version 1 :minor-version 3)) - (if (or (/= major-version 1) (< minor-version 3)) + :major-version 1 :minor-version 5)) + (if (or (/= major-version 1) (< minor-version 5)) (error "[EXWM] The server only support RandR version up to %d.%d" major-version minor-version) ;; External monitor(s) may already be connected. (run-hooks 'exwm-randr-screen-change-hook) - (exwm-randr--refresh) + (exwm-randr-refresh) + ;; Listen for `ScreenChangeNotify' to notify external tools to + ;; configure RandR and `CrtcChangeNotify/OutputChangeNotify' to + ;; refresh the workspace layout. (xcb:+event exwm--connection 'xcb:randr:ScreenChangeNotify #'exwm-randr--on-ScreenChangeNotify) - ;; (xcb:+event exwm--connection 'xcb:randr:Notify - ;; (lambda (_data _synthetic) - ;; (exwm--log "(RandR) Notify") - ;; (exwm-randr--refresh))) + (xcb:+event exwm--connection 'xcb:randr:Notify #'exwm-randr--on-Notify) + (xcb:+event exwm--connection 'xcb:ConfigureNotify + #'exwm-randr--on-ConfigureNotify) (xcb:+request exwm--connection (make-instance 'xcb:randr:SelectInput :window exwm--root - :enable xcb:randr:NotifyMask:ScreenChange - ;; :enable (eval-when-compile - ;; (logior - ;; xcb:randr:NotifyMask:ScreenChange - ;; xcb:randr:NotifyMask:OutputChange - ;; xcb:randr:NotifyMask:OutputProperty - ;; xcb:randr:NotifyMask:CrtcChange)) - )) + :enable (logior xcb:randr:NotifyMask:ScreenChange + xcb:randr:NotifyMask:CrtcChange + xcb:randr:NotifyMask:OutputChange))) (xcb:flush exwm--connection) - (add-hook 'exwm-workspace-list-change-hook #'exwm-randr--refresh)))) + (add-hook 'exwm-workspace-list-change-hook #'exwm-randr-refresh)))) ;; Prevent frame parameters introduced by this module from being ;; saved/restored. - (dolist (i '(exwm-randr-output)) + (dolist (i '(exwm-randr-monitor)) (unless (assq i frameset-filter-alist) (push (cons i :never) frameset-filter-alist)))) (defun exwm-randr--exit () "Exit the RandR module." - (remove-hook 'exwm-workspace-list-change-hook #'exwm-randr--refresh)) + (exwm--log) + (remove-hook 'exwm-workspace-list-change-hook #'exwm-randr-refresh)) (defun exwm-randr-enable () "Enable RandR support for EXWM." + (exwm--log) (add-hook 'exwm-init-hook #'exwm-randr--init) (add-hook 'exwm-exit-hook #'exwm-randr--exit)) diff --git a/exwm-systemtray.el b/exwm-systemtray.el index d3244ab8d088..80505c22a555 100644 --- a/exwm-systemtray.el +++ b/exwm-systemtray.el @@ -1,7 +1,7 @@ ;;; exwm-systemtray.el --- System Tray Module for -*- lexical-binding: t -*- ;;; EXWM -;; Copyright (C) 2016-2018 Free Software Foundation, Inc. +;; Copyright (C) 2016-2019 Free Software Foundation, Inc. ;; Author: Chris Feng <chris.w.feng@gmail.com> @@ -33,7 +33,9 @@ (require 'xcb-icccm) (require 'xcb-xembed) (require 'xcb-systemtray) + (require 'exwm-core) +(require 'exwm-workspace) (defclass exwm-systemtray--icon () ((width :initarg :width) @@ -77,22 +79,17 @@ You shall use the default value if using auto-hide minibuffer." (defvar exwm-systemtray--selection-owner-window nil "The selection owner window.") -(defvar exwm-workspace--current) -(defvar exwm-workspace--minibuffer) -(defvar exwm-workspace--workareas) -(defvar exwm-workspace-current-index) (defvar xcb:Atom:_NET_SYSTEM_TRAY_S0) -(declare-function exwm-workspace--minibuffer-own-frame-p "exwm-workspace.el") (defun exwm-systemtray--embed (icon) "Embed an icon." - (exwm--log "(System Tray) Try to embed #x%x" icon) + (exwm--log "Try to embed #x%x" icon) (let ((info (xcb:+request-unchecked+reply exwm-systemtray--connection (make-instance 'xcb:xembed:get-_XEMBED_INFO :window icon))) width* height* visible) (when info - (exwm--log "(System Tray) Embed #x%x" icon) + (exwm--log "Embed #x%x" icon) (with-slots (width height) (xcb:+request-unchecked+reply exwm-systemtray--connection (make-instance 'xcb:GetGeometry :drawable icon)) @@ -101,7 +98,7 @@ You shall use the default value if using auto-hide minibuffer." (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" + (exwm--log "Resize from %dx%d to %dx%d" width height width* height*)) ;; Add this icon to save-set. (xcb:+request exwm-systemtray--connection @@ -151,7 +148,7 @@ You shall use the default value if using auto-hide minibuffer." ;; Default to visible. (setq visible t)) (when visible - (exwm--log "(System Tray) Map the window") + (exwm--log "Map the window") (xcb:+request exwm-systemtray--connection (make-instance 'xcb:MapWindow :window icon))) (xcb:+request exwm-systemtray--connection @@ -175,7 +172,7 @@ You shall use the default value if using auto-hide minibuffer." (defun exwm-systemtray--unembed (icon) "Unembed an icon." - (exwm--log "(System Tray) Unembed #x%x" icon) + (exwm--log "Unembed #x%x" icon) (xcb:+request exwm-systemtray--connection (make-instance 'xcb:UnmapWindow :window icon)) (xcb:+request exwm-systemtray--connection @@ -189,6 +186,7 @@ You shall use the default value if using auto-hide minibuffer." (defun exwm-systemtray--refresh () "Refresh the system tray." + (exwm--log) ;; Make sure to redraw the embedder. (xcb:+request exwm-systemtray--connection (make-instance 'xcb:UnmapWindow @@ -222,6 +220,7 @@ You shall use the default value if using auto-hide minibuffer." (defun exwm-systemtray--on-DestroyNotify (data _synthetic) "Unembed icons on DestroyNotify." + (exwm--log) (let ((obj (make-instance 'xcb:DestroyNotify))) (xcb:unmarshal obj data) (with-slots (window) obj @@ -230,6 +229,7 @@ You shall use the default value if using auto-hide minibuffer." (defun exwm-systemtray--on-ReparentNotify (data _synthetic) "Unembed icons on ReparentNotify." + (exwm--log) (let ((obj (make-instance 'xcb:ReparentNotify))) (xcb:unmarshal obj data) (with-slots (window parent) obj @@ -239,6 +239,7 @@ You shall use the default value if using auto-hide minibuffer." (defun exwm-systemtray--on-ResizeRequest (data _synthetic) "Resize the tray icon on ResizeRequest." + (exwm--log) (let ((obj (make-instance 'xcb:ResizeRequest)) attr) (xcb:unmarshal obj data) @@ -266,6 +267,7 @@ You shall use the default value if using auto-hide minibuffer." (defun exwm-systemtray--on-PropertyNotify (data _synthetic) "Map/Unmap the tray icon on PropertyNotify." + (exwm--log) (let ((obj (make-instance 'xcb:PropertyNotify)) attr info visible) (xcb:unmarshal obj data) @@ -279,7 +281,7 @@ You shall use the default value if using auto-hide minibuffer." (when info (setq visible (/= 0 (logand (slot-value info 'flags) xcb:xembed:MAPPED))) - (exwm--log "(System Tray) #x%x visible? %s" window visible) + (exwm--log "#x%x visible? %s" window visible) (if visible (xcb:+request exwm-systemtray--connection (make-instance 'xcb:MapWindow :window window)) @@ -297,6 +299,7 @@ You shall use the default value if using auto-hide minibuffer." (when (eq type xcb:Atom:_NET_SYSTEM_TRAY_OPCODE) (setq data32 (slot-value data 'data32) opcode (elt data32 1)) + (exwm--log "opcode: %s" opcode) (cond ((= opcode xcb:systemtray:opcode:REQUEST-DOCK) (unless (assoc (elt data32 2) exwm-systemtray--list) (exwm-systemtray--embed (elt data32 2)))) @@ -304,10 +307,11 @@ You shall use the default value if using auto-hide minibuffer." ((or (= opcode xcb:systemtray:opcode:BEGIN-MESSAGE) (= opcode xcb:systemtray:opcode:CANCEL-MESSAGE))) (t - (exwm--log "(System Tray) Unknown opcode message: %s" obj))))))) + (exwm--log "Unknown opcode message: %s" obj))))))) (defun exwm-systemtray--on-KeyPress (data _synthetic) "Forward all KeyPress events to Emacs frame." + (exwm--log) ;; This function is only executed when there's no autohide minibuffer, ;; a workspace frame has the input focus and the pointer is over a ;; tray icon. @@ -325,6 +329,7 @@ You shall use the default value if using auto-hide minibuffer." (defun exwm-systemtray--on-workspace-switch () "Reparent/Refresh the system tray in `exwm-workspace-switch-hook'." + (exwm--log) (unless (exwm-workspace--minibuffer-own-frame-p) (xcb:+request exwm-systemtray--connection (make-instance 'xcb:ReparentWindow @@ -339,6 +344,7 @@ You shall use the default value if using auto-hide minibuffer." (defun exwm-systemtray--on-randr-refresh () "Reposition/Refresh the system tray in `exwm-randr-refresh-hook'." + (exwm--log) (unless (exwm-workspace--minibuffer-own-frame-p) (xcb:+request exwm-systemtray--connection (make-instance 'xcb:ConfigureWindow @@ -353,6 +359,7 @@ You shall use the default value if using auto-hide minibuffer." (cl-defun exwm-systemtray--init () "Initialize system tray module." + (exwm--log) (cl-assert (not exwm-systemtray--connection)) (cl-assert (not exwm-systemtray--list)) (cl-assert (not exwm-systemtray--selection-owner-window)) @@ -493,6 +500,7 @@ You shall use the default value if using auto-hide minibuffer." (defun exwm-systemtray--exit () "Exit the systemtray module." + (exwm--log) (when exwm-systemtray--connection ;; Hide & reparent out the embedder before disconnection to prevent ;; embedded icons from being reparented to an Emacs frame (which is the @@ -521,6 +529,7 @@ You shall use the default value if using auto-hide minibuffer." (defun exwm-systemtray-enable () "Enable system tray support for EXWM." + (exwm--log) (add-hook 'exwm-init-hook #'exwm-systemtray--init) (add-hook 'exwm-exit-hook #'exwm-systemtray--exit)) diff --git a/exwm-workspace.el b/exwm-workspace.el index d58758fc1f01..783287366aff 100644 --- a/exwm-workspace.el +++ b/exwm-workspace.el @@ -1,6 +1,6 @@ ;;; exwm-workspace.el --- Workspace Module for EXWM -*- lexical-binding: t -*- -;; Copyright (C) 2015-2018 Free Software Foundation, Inc. +;; Copyright (C) 2015-2019 Free Software Foundation, Inc. ;; Author: Chris Feng <chris.w.feng@gmail.com> @@ -308,7 +308,8 @@ NIL if FRAME is not a workspace" ;; Make left/top processed first. (push struts* exwm-workspace--struts) (setq exwm-workspace--struts - (append exwm-workspace--struts (list struts*)))))))))) + (append exwm-workspace--struts (list struts*)))))))) + (exwm--log "%s" exwm-workspace--struts))) (defun exwm-workspace--update-workareas () "Update `exwm-workspace--workareas'." @@ -372,10 +373,11 @@ NIL if FRAME is not a workspace" ;; Save the result. (setq exwm-workspace--workareas workareas) (xcb:flush exwm--connection)) + (exwm--log "%s" exwm-workspace--workareas) (run-hooks 'exwm-workspace--update-workareas-hook)) (defun exwm-workspace--set-active (frame active) - "Make frame FRAME active on its output." + "Make frame FRAME active on its monitor." (exwm--log "active=%s; frame=%s" frame active) (set-frame-parameter frame 'exwm-active active) (if active @@ -452,7 +454,8 @@ NIL if FRAME is not a workspace" :window (frame-parameter exwm-workspace--minibuffer 'exwm-outer-id) :value-mask xcb:ConfigWindow:Width - :width width)))) + :width width)) + (exwm--log "y: %s, width: %s" y width))) (defun exwm-workspace--switch-map-nth-prefix (&optional prefix-digits) "Allow selecting a workspace by number. @@ -544,15 +547,15 @@ for internal use only." (set-frame-parameter (buffer-local-value 'exwm--frame (window-buffer)) 'exwm-selected-window (selected-window))) ;; Show/Hide X windows. - (let ((output-old (frame-parameter old-frame 'exwm-randr-output)) - (output-new (frame-parameter frame 'exwm-randr-output)) + (let ((monitor-old (frame-parameter old-frame 'exwm-randr-monitor)) + (monitor-new (frame-parameter frame 'exwm-randr-monitor)) (active-old (exwm-workspace--active-p old-frame)) (active-new (exwm-workspace--active-p frame)) workspaces-to-hide) (cond ((not active-old) (exwm-workspace--set-active frame t)) - ((equal output-old output-new) + ((equal monitor-old monitor-new) (exwm-workspace--set-active frame t) (unless (eq frame old-frame) (exwm-workspace--set-active old-frame nil) @@ -561,8 +564,8 @@ for internal use only." (t (dolist (w exwm-workspace--list) (when (and (exwm-workspace--active-p w) - (equal output-new - (frame-parameter w 'exwm-randr-output))) + (equal monitor-new + (frame-parameter w 'exwm-randr-monitor))) (exwm-workspace--set-active w nil) (setq workspaces-to-hide (append workspaces-to-hide (list w))))) (exwm-workspace--set-active frame t))) @@ -648,6 +651,7 @@ Passing a workspace frame as the first option is for internal use only." (t 0)))) (unless frame-or-index (setq frame-or-index 0)) + (exwm--log "%s" frame-or-index) (if (or (framep frame-or-index) (< frame-or-index (exwm-workspace--count))) (exwm-workspace-switch frame-or-index) @@ -748,6 +752,7 @@ before it." INDEX must not exceed the current number of workspaces." (interactive) + (exwm--log "%s" index) (if (and index ;; No need to move if it's the last one. (< index (exwm-workspace--count))) @@ -758,6 +763,7 @@ INDEX must not exceed the current number of workspaces." (defun exwm-workspace-delete (&optional frame-or-index) "Delete the workspace FRAME-OR-INDEX." (interactive) + (exwm--log "%s" frame-or-index) (when (< 1 (exwm-workspace--count)) (delete-frame (if frame-or-index @@ -766,6 +772,7 @@ INDEX must not exceed the current number of workspaces." (defun exwm-workspace--set-desktop (id) "Set _NET_WM_DESKTOP for X window ID." + (exwm--log "#x%x" id) (with-current-buffer (exwm--id->buffer id) (let ((desktop (exwm-workspace--position exwm--frame))) (setq exwm--desktop desktop) @@ -790,6 +797,7 @@ INDEX must not exceed the current number of workspaces." (let ((frame (exwm-workspace--workspace-from-frame-or-index frame-or-index)) old-frame container) (unless id (setq id (exwm--buffer->id (window-buffer)))) + (exwm--log "Moving #x%x to %s" id frame-or-index) (with-current-buffer (exwm--id->buffer id) (unless (eq exwm--frame frame) (unless exwm-workspace-show-all-buffers @@ -819,8 +827,8 @@ INDEX must not exceed the current number of workspaces." ;; Floating. (setq container (frame-parameter exwm--floating-frame 'exwm-container)) - (unless (equal (frame-parameter old-frame 'exwm-randr-output) - (frame-parameter frame 'exwm-randr-output)) + (unless (equal (frame-parameter old-frame 'exwm-randr-monitor) + (frame-parameter frame 'exwm-randr-monitor)) (with-slots (x y) (xcb:+request-unchecked+reply exwm--connection (make-instance 'xcb:GetGeometry @@ -985,6 +993,7 @@ INDEX must not exceed the current number of workspaces." (defun exwm-workspace--x-create-frame (orig-fun params) "Set override-redirect on the frame created by `x-create-frame'." + (exwm--log) (let ((frame (funcall orig-fun params))) (xcb:+request exwm--connection (make-instance 'xcb:ChangeWindowAttributes @@ -1006,6 +1015,7 @@ Please check `exwm-workspace--minibuffer-own-frame-p' first." (defun exwm-workspace-attach-minibuffer () "Attach the minibuffer so that it always shows." (interactive) + (exwm--log) (when (and (exwm-workspace--minibuffer-own-frame-p) (not (exwm-workspace--minibuffer-attached-p))) ;; Reset the frame size. @@ -1030,6 +1040,7 @@ Please check `exwm-workspace--minibuffer-own-frame-p' first." (defun exwm-workspace-detach-minibuffer () "Detach the minibuffer so that it automatically hides." (interactive) + (exwm--log) (when (and (exwm-workspace--minibuffer-own-frame-p) (exwm-workspace--minibuffer-attached-p)) (setq exwm-workspace--attached-minibuffer-height 0) @@ -1047,6 +1058,7 @@ Please check `exwm-workspace--minibuffer-own-frame-p' first." (defun exwm-workspace-toggle-minibuffer () "Attach the minibuffer if it's detached, or detach it if it's attached." (interactive) + (exwm--log) (when (exwm-workspace--minibuffer-own-frame-p) (if (exwm-workspace--minibuffer-attached-p) (exwm-workspace-detach-minibuffer) @@ -1072,6 +1084,7 @@ Please check `exwm-workspace--minibuffer-own-frame-p' first." (when (and (integerp max-mini-window-height) (> height max-mini-window-height)) (setq height max-mini-window-height)) + (exwm--log "%s" height) (set-frame-height exwm-workspace--minibuffer height)))) (defun exwm-workspace--on-ConfigureNotify (data _synthetic) @@ -1082,6 +1095,7 @@ Please check `exwm-workspace--minibuffer-own-frame-p' first." (with-slots (window height) obj (when (eq (frame-parameter exwm-workspace--minibuffer 'exwm-outer-id) window) + (exwm--log) (when (and (floatp max-mini-window-height) (> height (* max-mini-window-height (exwm-workspace--current-height)))) @@ -1123,6 +1137,7 @@ Please check `exwm-workspace--minibuffer-own-frame-p' first." (defun exwm-workspace--show-minibuffer () "Show the minibuffer frame." + (exwm--log) ;; Cancel pending timer. (when exwm-workspace--display-echo-area-timer (cancel-timer exwm-workspace--display-echo-area-timer) @@ -1144,6 +1159,7 @@ Please check `exwm-workspace--minibuffer-own-frame-p' first." (defun exwm-workspace--hide-minibuffer () "Hide the minibuffer frame." + (exwm--log) ;; Hide the minibuffer frame. (if (exwm-workspace--minibuffer-attached-p) (xcb:+request exwm--connection @@ -1165,6 +1181,7 @@ Please check `exwm-workspace--minibuffer-own-frame-p' first." (defun exwm-workspace--on-minibuffer-setup () "Run in minibuffer-setup-hook to show the minibuffer and its container." + (exwm--log) (when (and (= 1 (minibuffer-depth)) (not (exwm-workspace--client-p))) (add-hook 'post-command-hook #'exwm-workspace--update-minibuffer-height) @@ -1186,6 +1203,7 @@ Please check `exwm-workspace--minibuffer-own-frame-p' first." (defun exwm-workspace--on-minibuffer-exit () "Run in minibuffer-exit-hook to hide the minibuffer container." + (exwm--log) (when (and (= 1 (minibuffer-depth)) (not (exwm-workspace--client-p))) (remove-hook 'post-command-hook #'exwm-workspace--update-minibuffer-height) @@ -1228,6 +1246,7 @@ Please check `exwm-workspace--minibuffer-own-frame-p' first." (defun exwm-workspace--set-desktop-geometry () "Set _NET_DESKTOP_GEOMETRY." + (exwm--log) ;; We don't support large desktop so it's the same with screen size. (xcb:+request exwm--connection (make-instance 'xcb:ewmh:set-_NET_DESKTOP_GEOMETRY @@ -1237,6 +1256,7 @@ Please check `exwm-workspace--minibuffer-own-frame-p' first." (defun exwm-workspace--add-frame-as-workspace (frame) "Configure frame FRAME to be treated as a workspace." + (exwm--log "%s" frame) (setq exwm-workspace--list (nconc exwm-workspace--list (list frame))) (let ((outer-id (string-to-number (frame-parameter frame 'outer-window-id))) @@ -1252,7 +1272,7 @@ Please check `exwm-workspace--minibuffer-own-frame-p' first." ;; prevent potential problems. The values do not matter here as ;; they'll be updated by the RandR module later. (let ((w (car exwm-workspace--list))) - (dolist (param '(exwm-randr-output + (dolist (param '(exwm-randr-monitor exwm-geometry)) (set-frame-parameter frame param (frame-parameter w param)))) (xcb:+request exwm--connection @@ -1401,6 +1421,7 @@ Please check `exwm-workspace--minibuffer-own-frame-p' first." (defun exwm-workspace--update-ewmh-props () "Update EWMH properties to match the workspace list." + (exwm--log) (let ((num-workspaces (exwm-workspace--count))) ;; Avoid setting 0 desktops. (when (= 0 num-workspaces) @@ -1420,6 +1441,7 @@ Please check `exwm-workspace--minibuffer-own-frame-p' first." NEW-X-PARAMETERS is an alist of frame parameters, merged into current `window-system-default-frame-alist' for the X Window System. The parameters are applied to all subsequently created X frames." + (exwm--log) ;; The parameters are modified in place; take current ;; ones or insert a new X-specific list. (let ((x-parameters (or (assq 'x window-system-default-frame-alist) @@ -1439,6 +1461,7 @@ applied to all subsequently created X frames." (interactive "e")) (defun exwm-workspace--init-minibuffer-frame () + (exwm--log) ;; Initialize workspaces without minibuffers. (setq exwm-workspace--minibuffer (make-frame '((window-system . x) (minibuffer . only) @@ -1509,6 +1532,7 @@ applied to all subsequently created X frames." :test #'equal)) (defun exwm-workspace--exit-minibuffer-frame () + (exwm--log) ;; Only on minibuffer-frame. (remove-hook 'minibuffer-setup-hook #'exwm-workspace--on-minibuffer-setup) (remove-hook 'minibuffer-exit-hook #'exwm-workspace--on-minibuffer-exit) @@ -1532,6 +1556,7 @@ applied to all subsequently created X frames." (defun exwm-workspace--init () "Initialize workspace module." + (exwm--log) ;; Prevent unexpected exit (setq exwm-workspace--fullscreen-frame-count 0) (exwm-workspace--modify-all-x-frames-parameters @@ -1599,6 +1624,7 @@ applied to all subsequently created X frames." (defun exwm-workspace--exit () "Exit the workspace module." + (exwm--log) (when (exwm-workspace--minibuffer-own-frame-p) (exwm-workspace--exit-minibuffer-frame)) (advice-remove 'x-create-frame #'exwm-workspace--x-create-frame) @@ -1636,6 +1662,7 @@ applied to all subsequently created X frames." (defun exwm-workspace--post-init () "The second stage in the initialization of the workspace module." + (exwm--log) (when exwm-workspace--client ;; Reset the 'fullscreen' frame parameter to make emacsclinet frames ;; fullscreen (even without the RandR module enabled). diff --git a/exwm-xim.el b/exwm-xim.el new file mode 100644 index 000000000000..6a213acc0cb5 --- /dev/null +++ b/exwm-xim.el @@ -0,0 +1,781 @@ +;;; exwm-xim.el --- XIM Module for EXWM -*- lexical-binding: t -*- + +;; Copyright (C) 2019 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 XIM support for EXWM and allows sending characters +;; generated by any Emacs's builtin input method (info node `Input Methods') +;; to X windows. + +;; This module is essentially an X input method server utilizing Emacs as +;; its backend. It talks with X windows through the XIM protocol. The XIM +;; protocol is quite flexible by itself, stating that an implementation can +;; create network connections of various types as well as make use of an +;; existing X connection for communication, and that an IM server may +;; support multiple transport versions, various input styles and several +;; event flow modals, etc. Here we only make choices that are most popular +;; among other IM servers and more importantly, practical for Emacs to act +;; as an IM server: +;; +;; + Packets are transported on top of an X connection like most IMEs. +;; + Only transport version 0.0 (i.e. only-CM & Property-with-CM) is +;; supported (same as "IM Server Developers Kit", adopted by most IMEs). +;; + Only support static event flow, on-demand-synchronous method. +;; + Only "root-window" input style is supported. + +;; To use this module, first load and enable it as follows: +;; +;; (require 'exwm-xim) +;; (exwm-xim-enable) +;; +;; A keybinding for `toggle-input-method' is probably required to turn on & +;; off an input method (default to `default-input-method'). It's bound to +;; 'C-\' by default and can be made reachable when working with X windows: +;; +;; (push ?\C-\\ exwm-input-prefix-keys) +;; +;; It's also required (and error-prone) to setup environment variables to +;; make applications actually use this input method. Typically the +;; following lines should be inserted into '~/.xinitrc'. +;; +;; export XMODIFIERS=@im=exwm-xim +;; export GTK_IM_MODULE=xim +;; export QT_IM_MODULE=xim +;; export CLUTTER_IM_MODULE=xim + +;; References: +;; + XIM (http://www.x.org/releases/X11R7.6/doc/libX11/specs/XIM/xim.html) +;; + IMdkit (http://xorg.freedesktop.org/archive/unsupported/lib/IMdkit/) +;; + UIM (https://github.com/uim/uim) + +;;; Code: + +(eval-when-compile (require 'cl-lib)) + +(require 'xcb-keysyms) +(require 'xcb-xim) + +(require 'exwm-core) +(require 'exwm-input) + +(defconst exwm-xim--locales + "@locale=\ +aa,af,ak,am,an,anp,ar,as,ast,ayc,az,be,bem,ber,bg,bhb,bho,bn,bo,br,brx,bs,byn,\ +ca,ce,cmn,crh,cs,csb,cv,cy,da,de,doi,dv,dz,el,en,es,et,eu,fa,ff,fi,fil,fo,fr,\ +fur,fy,ga,gd,gez,gl,gu,gv,ha,hak,he,hi,hne,hr,hsb,ht,hu,hy,ia,id,ig,ik,is,it,\ +iu,iw,ja,ka,kk,kl,km,kn,ko,kok,ks,ku,kw,ky,lb,lg,li,li,lij,lo,lt,lv,lzh,mag,\ +mai,mg,mhr,mi,mk,ml,mn,mni,mr,ms,mt,my,nan,nb,nds,ne,nhn,niu,nl,nn,nr,nso,oc,\ +om,or,os,pa,pa,pap,pl,ps,pt,quz,raj,ro,ru,rw,sa,sat,sc,sd,se,shs,si,sid,sk,sl,\ +so,sq,sr,ss,st,sv,sw,szl,ta,tcy,te,tg,th,the,ti,tig,tk,tl,tn,tr,ts,tt,ug,uk,\ +unm,ur,uz,ve,vi,wa,wae,wal,wo,xh,yi,yo,yue,zh,zu,\ +C,no" + "All supported locales (stolen from glibc).") + +(defconst exwm-xim--default-error + (make-instance 'xim:error + :im-id 0 + :ic-id 0 + :flag xim:error-flag:invalid-both + :error-code xim:error-code:bad-something + :length 0 + :type 0 + :detail nil) + "Default error returned to clients.") + +(defconst exwm-xim--default-im-attrs + (list (make-instance 'xim:XIMATTR + :id 0 + :type xim:ATTRIBUTE-VALUE-TYPE:xim-styles + :length (length xlib:XNQueryInputStyle) + :attribute xlib:XNQueryInputStyle)) + "Default IM attrs returned to clients.") + +(defconst exwm-xim--default-ic-attrs + (list (make-instance 'xim:XICATTR + :id 0 + :type xim:ATTRIBUTE-VALUE-TYPE:long-data + :length (length xlib:XNInputStyle) + :attribute xlib:XNInputStyle) + (make-instance 'xim:XICATTR + :id 1 + :type xim:ATTRIBUTE-VALUE-TYPE:window + :length (length xlib:XNClientWindow) + :attribute xlib:XNClientWindow) + ;; Required by e.g. xterm. + (make-instance 'xim:XICATTR + :id 2 + :type xim:ATTRIBUTE-VALUE-TYPE:window + :length (length xlib:XNFocusWindow) + :attribute xlib:XNFocusWindow)) + "Default IC attrs returned to clients.") + +(defconst exwm-xim--default-styles + (make-instance 'xim:XIMStyles + :number nil + :styles (list (logior xlib:XIMPreeditNothing + xlib:XIMStatusNothing))) + "Default styles: root-window, i.e. no preediting or status display support.") + +(defconst exwm-xim--default-attributes + (list (make-instance 'xim:XIMATTRIBUTE + :id 0 + :length nil + :value exwm-xim--default-styles)) + "Default IM/IC attributes returned to clients.") + +(defvar exwm-xim--conn nil + "The X connection for initiating other XIM connections.") +(defvar exwm-xim--event-xwin nil + "X window for initiating new XIM connections.") +(defvar exwm-xim--server-client-plist '(nil nil) + "Plist mapping server window to [X connection, client window, byte-order].") +(defvar exwm-xim--client-server-plist '(nil nil) + "Plist mapping client window to server window.") +(defvar exwm-xim--property-index 0 "For generating a unique property name.") +(defvar exwm-xim--im-id 0 "Last IM ID.") +(defvar exwm-xim--ic-id 0 "Last IC ID.") +(defvar exwm-xim--event-pending nil + "Indicating whether Emacs requires more events.") + +;; X11 atoms. +(defvar exwm-xim--@server nil) +(defvar exwm-xim--LOCALES nil) +(defvar exwm-xim--TRANSPORT nil) +(defvar exwm-xim--XIM_SERVERS nil) +(defvar exwm-xim--_XIM_PROTOCOL nil) +(defvar exwm-xim--_XIM_XCONNECT nil) + +(defun exwm-xim--on-SelectionRequest (data _synthetic) + "Handle SelectionRequest events on IMS window. + +Such events would be received when clients query for LOCALES or TRANSPORT." + (exwm--log) + (let ((evt (make-instance 'xcb:SelectionRequest)) + value fake-event) + (xcb:unmarshal evt data) + (with-slots (time requestor selection target property) evt + (setq value (cond ((= target exwm-xim--LOCALES) + ;; Return supported locales. + exwm-xim--locales) + ((= target exwm-xim--TRANSPORT) + ;; Use XIM over an X connection. + "@transport=X/"))) + (when value + ;; Change the property. + (xcb:+request exwm-xim--conn + (make-instance 'xcb:ChangeProperty + :mode xcb:PropMode:Replace + :window requestor + :property property + :type target + :format 8 + :data-len (length value) + :data value)) + ;; Send a SelectionNotify event. + (setq fake-event (make-instance 'xcb:SelectionNotify + :time time + :requestor requestor + :selection selection + :target target + :property property)) + (xcb:+request exwm-xim--conn + (make-instance 'xcb:SendEvent + :propagate 0 + :destination requestor + :event-mask xcb:EventMask:NoEvent + :event (xcb:marshal fake-event exwm-xim--conn))) + (xcb:flush exwm-xim--conn))))) + +(cl-defun exwm-xim--on-ClientMessage-0 (data _synthetic) + "Handle ClientMessage event on IMS window (new connection). + +Such events would be received when clients request for _XIM_XCONNECT. +A new X connection and server window would be created to communicate with +this client." + (exwm--log) + (let ((evt (make-instance 'xcb:ClientMessage)) + conn client-xwin server-xwin) + (xcb:unmarshal evt data) + (with-slots (window type data) evt + (unless (= type exwm-xim--_XIM_XCONNECT) + ;; Only handle _XIM_XCONNECT. + (exwm--log "Ignore ClientMessage %s" type) + (cl-return-from exwm-xim--on-ClientMessage-0)) + (setq client-xwin (elt (slot-value data 'data32) 0) + ;; Create a new X connection and a new server window. + conn (xcb:connect) + server-xwin (xcb:generate-id conn)) + (set-process-query-on-exit-flag (slot-value conn 'process) nil) + ;; Store this client. + (plist-put exwm-xim--server-client-plist server-xwin + `[,conn ,client-xwin nil]) + (plist-put exwm-xim--client-server-plist client-xwin server-xwin) + ;; Select DestroyNotify events on this client window. + (xcb:+request exwm-xim--conn + (make-instance 'xcb:ChangeWindowAttributes + :window client-xwin + :value-mask xcb:CW:EventMask + :event-mask xcb:EventMask:StructureNotify)) + (xcb:flush exwm-xim--conn) + ;; Handle ClientMessage events from this new connection. + (xcb:+event conn 'xcb:ClientMessage #'exwm-xim--on-ClientMessage) + ;; Create a communication window. + (xcb:+request conn + (make-instance 'xcb:CreateWindow + :depth 0 + :wid server-xwin + :parent exwm--root + :x 0 + :y 0 + :width 1 + :height 1 + :border-width 0 + :class xcb:WindowClass:InputOutput + :visual 0 + :value-mask xcb:CW:OverrideRedirect + :override-redirect 1)) + (xcb:flush conn) + ;; Send connection establishment ClientMessage. + (setf window client-xwin + (slot-value data 'data32) `(,server-xwin 0 0 0 0)) + (slot-makeunbound data 'data8) + (slot-makeunbound data 'data16) + (xcb:+request exwm-xim--conn + (make-instance 'xcb:SendEvent + :propagate 0 + :destination client-xwin + :event-mask xcb:EventMask:NoEvent + :event (xcb:marshal evt exwm-xim--conn))) + (xcb:flush exwm-xim--conn)))) + +(cl-defun exwm-xim--on-ClientMessage (data _synthetic) + "Handle ClientMessage event on IMS communication window (request). + +Such events would be received when clients request for _XIM_PROTOCOL. +The actual XIM request is in client message data or a property." + (exwm--log) + (let ((evt (make-instance 'xcb:ClientMessage)) + conn client-xwin server-xwin) + (xcb:unmarshal evt data) + (with-slots (format window type data) evt + (unless (= type exwm-xim--_XIM_PROTOCOL) + (exwm--log "Ignore ClientMessage %s" type) + (cl-return-from exwm-xim--on-ClientMessage)) + (setq server-xwin window + conn (plist-get exwm-xim--server-client-plist server-xwin) + client-xwin (elt conn 1) + conn (elt conn 0)) + (cond ((= format 8) + ;; Data. + (exwm-xim--on-request (vconcat (slot-value data 'data8)) + conn client-xwin server-xwin)) + ((= format 32) + ;; Atom. + (with-slots (data32) data + (with-slots (value) + (xcb:+request-unchecked+reply conn + (make-instance 'xcb:GetProperty + :delete 1 + :window server-xwin + :property (elt data32 1) + :type xcb:GetPropertyType:Any + :long-offset 0 + :long-length (elt data32 0))) + (when (> (length value) 0) + (exwm-xim--on-request value conn client-xwin + server-xwin))))))))) + +(defun exwm-xim--on-request (data conn client-xwin server-xwin) + "Handle an XIM reuqest." + (exwm--log) + (let ((opcode (elt data 0)) + ;; Let-bind `xim:lsb' to make pack/unpack functions work correctly. + (xim:lsb (elt (plist-get exwm-xim--server-client-plist server-xwin) 2)) + req replies) + (cond ((= opcode xim:opcode:error) + (exwm--log "ERROR: %s" data)) + ((= opcode xim:opcode:connect) + (exwm--log "CONNECT") + (setq xim:lsb (= (elt data 4) xim:connect-byte-order:lsb-first)) + ;; Store byte-order. + (setf (elt (plist-get exwm-xim--server-client-plist server-xwin) 2) + xim:lsb) + (setq req (make-instance 'xim:connect)) + (xcb:unmarshal req data) + (if (and (= (slot-value req 'major-version) 1) + (= (slot-value req 'minor-version) 0) + ;; Do not support authentication. + (= (slot-value req 'number) 0)) + ;; Accept the connection. + (push (make-instance 'xim:connect-reply) replies) + ;; Deny it. + (push exwm-xim--default-error replies))) + ((memq opcode (list xim:opcode:auth-required + xim:opcode:auth-reply + xim:opcode:auth-next + xim:opcode:auth-ng)) + (exwm--log "AUTH: %d" opcode) + ;; Deny any attempt to make authentication. + (push exwm-xim--default-error replies)) + ((= opcode xim:opcode:disconnect) + (exwm--log "DISCONNECT") + ;; Gracefully disconnect from the client. + (exwm-xim--make-request (make-instance 'xim:disconnect-reply) + conn client-xwin) + ;; Destroy the communication window & connection. + (xcb:+request conn + (make-instance 'xcb:DestroyWindow + :window server-xwin)) + (xcb:disconnect conn) + ;; Clean up cache. + (cl-remf exwm-xim--server-client-plist server-xwin) + (cl-remf exwm-xim--client-server-plist client-xwin)) + ((= opcode xim:opcode:open) + (exwm--log "OPEN") + ;; Note: We make no check here. + (setq exwm-xim--im-id (if (< exwm-xim--im-id #xffff) + (1+ exwm-xim--im-id) + 1)) + (setq replies + (list + (make-instance 'xim:open-reply + :im-id exwm-xim--im-id + :im-attrs-length nil + :im-attrs exwm-xim--default-im-attrs + :ic-attrs-length nil + :ic-attrs exwm-xim--default-ic-attrs) + (make-instance 'xim:set-event-mask + :im-id exwm-xim--im-id + :ic-id 0 + ;; Static event flow. + :forward-event-mask xcb:EventMask:KeyPress + ;; on-demand-synchronous method. + :synchronous-event-mask + xcb:EventMask:NoEvent)))) + ((= opcode xim:opcode:close) + (exwm--log "CLOSE") + (setq req (make-instance 'xim:close)) + (xcb:unmarshal req data) + (push (make-instance 'xim:close-reply + :im-id (slot-value req 'im-id)) + replies)) + ((= opcode xim:opcode:trigger-notify) + (exwm--log "TRIGGER-NOTIFY") + ;; Only static event flow modal is supported. + (push exwm-xim--default-error replies)) + ((= opcode xim:opcode:encoding-negotiation) + (exwm--log "ENCODING-NEGOTIATION") + (setq req (make-instance 'xim:encoding-negotiation)) + (xcb:unmarshal req data) + (let ((index (cl-position "COMPOUND_TEXT" + (mapcar (lambda (i) (slot-value i 'name)) + (slot-value req 'names)) + :test #'equal))) + (unless index + ;; Fallback to portable character encoding (a subset of ASCII). + (setq index -1)) + (push (make-instance 'xim:encoding-negotiation-reply + :im-id (slot-value req 'im-id) + :category + xim:encoding-negotiation-reply-category:name + :index index) + replies))) + ((= opcode xim:opcode:query-extension) + (exwm--log "QUERY-EXTENSION") + (setq req (make-instance 'xim:query-extension)) + (xcb:unmarshal req data) + (push (make-instance 'xim:query-extension-reply + :im-id (slot-value req 'im-id) + ;; No extension support. + :length 0 + :extensions nil) + replies)) + ((= opcode xim:opcode:set-im-values) + (exwm--log "SET-IM-VALUES") + ;; There's only one possible input method attribute. + (setq req (make-instance 'xim:set-im-values)) + (xcb:unmarshal req data) + (push (make-instance 'xim:set-im-values-reply + :im-id (slot-value req 'im-id)) + replies)) + ((= opcode xim:opcode:get-im-values) + (exwm--log "GET-IM-VALUES") + (setq req (make-instance 'xim:get-im-values)) + (let (im-attributes-id) + (xcb:unmarshal req data) + (setq im-attributes-id (slot-value req 'im-attributes-id)) + (if (cl-notevery (lambda (i) (= i 0)) im-attributes-id) + ;; Only support one IM attributes. + (push (make-instance 'xim:error + :im-id (slot-value req 'im-id) + :ic-id 0 + :flag xim:error-flag:invalid-ic-id + :error-code xim:error-code:bad-something + :length 0 + :type 0 + :detail nil) + replies) + (push + (make-instance 'xim:get-im-values-reply + :im-id (slot-value req 'im-id) + :length nil + :im-attributes exwm-xim--default-attributes) + replies)))) + ((= opcode xim:opcode:create-ic) + (exwm--log "CREATE-IC") + (setq req (make-instance 'xim:create-ic)) + (xcb:unmarshal req data) + ;; Note: The ic-attributes slot is ignored. + (setq exwm-xim--ic-id (if (< exwm-xim--ic-id #xffff) + (1+ exwm-xim--ic-id) + 1)) + (push (make-instance 'xim:create-ic-reply + :im-id (slot-value req 'im-id) + :ic-id exwm-xim--ic-id) + replies)) + ((= opcode xim:opcode:destroy-ic) + (exwm--log "DESTROY-IC") + (setq req (make-instance 'xim:destroy-ic)) + (xcb:unmarshal req data) + (push (make-instance 'xim:destroy-ic-reply + :im-id (slot-value req 'im-id) + :ic-id (slot-value req 'ic-id)) + replies)) + ((= opcode xim:opcode:set-ic-values) + (exwm--log "SET-IC-VALUES") + (setq req (make-instance 'xim:set-ic-values)) + (xcb:unmarshal req data) + ;; We don't distinguish between input contexts. + (push (make-instance 'xim:set-ic-values-reply + :im-id (slot-value req 'im-id) + :ic-id (slot-value req 'ic-id)) + replies)) + ((= opcode xim:opcode:get-ic-values) + (exwm--log "GET-IC-VALUES") + (setq req (make-instance 'xim:get-ic-values)) + (xcb:unmarshal req data) + (push (make-instance 'xim:get-ic-values-reply + :im-id (slot-value req 'im-id) + :ic-id (slot-value req 'ic-id) + :length nil + :ic-attributes exwm-xim--default-attributes) + replies)) + ((= opcode xim:opcode:set-ic-focus) + (exwm--log "SET-IC-FOCUS") + ;; All input contexts are the same. + ) + ((= opcode xim:opcode:unset-ic-focus) + (exwm--log "UNSET-IC-FOCUS") + ;; All input contexts are the same. + ) + ((= opcode xim:opcode:forward-event) + (exwm--log "FORWARD-EVENT") + (setq req (make-instance 'xim:forward-event)) + (xcb:unmarshal req data) + (let ((im-func (with-current-buffer (window-buffer) + input-method-function)) + key-event keysym event result) + ;; Note: The flag slot is ignored. + ;; Do conversion in client's byte-order. + (let ((xcb:lsb xim:lsb)) + (setq key-event (make-instance 'xcb:KeyPress)) + (xcb:unmarshal key-event (slot-value req 'event))) + (with-slots (detail state) key-event + (setq keysym (xcb:keysyms:keycode->keysym exwm-xim--conn detail + state)) + (when (/= (car keysym) 0) + (setq event (xcb:keysyms:keysym->event + exwm-xim--conn + (car keysym) + (logand state (lognot (cdr keysym))))))) + (if exwm-xim--event-pending + ;; In case any event reaches here, it should be forwarded + ;; to Emacs. + (when event + (setq unread-command-events + (append unread-command-events (list event)))) + (setq exwm-xim--event-pending t) + (if (or (not im-func) + ;; `list' is the default method. + (eq im-func #'list) + (not event) + ;; Select only printable keys. + (not (integerp event)) (> #x20 event) (< #x7e event)) + ;; Either there is no active input method, or invalid key + ;; is detected. + (with-slots (im-id ic-id serial-number event) req + (push (make-instance 'xim:forward-event + :im-id im-id + :ic-id ic-id + :flag xim:commit-flag:synchronous + :serial-number serial-number + :event event) + replies)) + (when (eq exwm--selected-input-mode 'char-mode) + ;; Grab keyboard temporarily for char-mode. + (exwm-input--grab-keyboard)) + (unwind-protect + (with-temp-buffer + ;; Always show key strokes. + (let ((input-method-use-echo-area t)) + (setq result (funcall im-func event)))) + (when (eq exwm--selected-input-mode 'char-mode) + (exwm-input--release-keyboard))) + ;; This also works for portable character encoding. + (setq result + (encode-coding-string (concat result) + 'compound-text-with-extensions)) + (message "") + (push + (make-instance 'xim:commit-x-lookup-chars + :im-id (slot-value req 'im-id) + :ic-id (slot-value req 'ic-id) + :flag (logior xim:commit-flag:synchronous + xim:commit-flag:x-lookup-chars) + :length (length result) + :string result) + replies)) + (setq exwm-xim--event-pending nil)))) + ((= opcode xim:opcode:sync) + (exwm--log "SYNC") + (setq req (make-instance 'xim:sync)) + (xcb:unmarshal req data) + (push (make-instance 'xim:sync-reply + :im-id (slot-value req 'im-id) + :ic-id (slot-value req 'ic-id)) + replies)) + ((= opcode xim:opcode:sync-reply) + (exwm--log "SYNC-REPLY")) + ((= opcode xim:opcode:reset-ic) + (exwm--log "RESET-IC") + ;; No context-specific data saved. + (setq req (make-instance 'xim:reset-ic)) + (xcb:unmarshal req data) + (push (make-instance 'xim:reset-ic-reply + :im-id (slot-value req 'im-id) + :ic-id (slot-value req 'ic-id) + :length 0 + :string "") + replies)) + ((memq opcode (list xim:opcode:str-conversion-reply + xim:opcode:preedit-start-reply + xim:opcode:preedit-caret-reply)) + (exwm--log "PREEDIT: %d" opcode) + ;; No preedit support. + (push exwm-xim--default-error replies)) + (t + (exwm--log "Bad protocol") + (push exwm-xim--default-error replies))) + ;; Actually send the replies. + (when replies + (mapc (lambda (reply) + (exwm-xim--make-request reply conn client-xwin)) + replies) + (xcb:flush conn)))) + +(defun exwm-xim--make-request (req conn client-xwin) + "Make an XIM request REQ via connection CONN. + +CLIENT-XWIN would receive a ClientMessage event either telling the client +the request data or where to fetch the data." + (exwm--log) + (let ((data (xcb:marshal req)) + property format client-message-data client-message) + (if (<= (length data) 20) + ;; Send short requests directly with client messages. + (setq format 8 + ;; Pad to 20 bytes. + data (append data (make-list (- 20 (length data)) 0)) + client-message-data (make-instance 'xcb:ClientMessageData + :data8 data)) + ;; Send long requests with properties. + (setq property (exwm--intern-atom (format "_EXWM_XIM_%x" + exwm-xim--property-index))) + (cl-incf exwm-xim--property-index) + (xcb:+request conn + (make-instance 'xcb:ChangeProperty + :mode xcb:PropMode:Append + :window client-xwin + :property property + :type xcb:Atom:STRING + :format 8 + :data-len (length data) + :data data)) + ;; Also send a client message to notify the client about this property. + (setq format 32 + client-message-data (make-instance 'xcb:ClientMessageData + :data32 `(,(length data) + ,property + ;; Pad to 20 bytes. + 0 0 0)))) + ;; Send the client message. + (setq client-message (make-instance 'xcb:ClientMessage + :format format + :window client-xwin + :type exwm-xim--_XIM_PROTOCOL + :data client-message-data)) + (xcb:+request conn + (make-instance 'xcb:SendEvent + :propagate 0 + :destination client-xwin + :event-mask xcb:EventMask:NoEvent + :event (xcb:marshal client-message conn))))) + +(defun exwm-xim--on-DestroyNotify (data synthetic) + "Do cleanups on receiving DestroyNotify event. + +Such event would be received when the client window is destroyed." + (exwm--log) + (unless synthetic + (let ((evt (make-instance 'xcb:DestroyNotify)) + conn client-xwin server-xwin) + (xcb:unmarshal evt data) + (setq client-xwin (slot-value evt 'window) + server-xwin (plist-get exwm-xim--client-server-plist client-xwin)) + (when server-xwin + (setq conn (aref (plist-get exwm-xim--server-client-plist server-xwin) + 0)) + (cl-remf exwm-xim--server-client-plist server-xwin) + (cl-remf exwm-xim--client-server-plist client-xwin) + ;; Destroy the communication window & connection. + (xcb:+request conn + (make-instance 'xcb:DestroyWindow + :window server-xwin)) + (xcb:disconnect conn))))) + +(cl-defun exwm-xim--init () + "Initialize the XIM module." + (exwm--log) + (when exwm-xim--conn + (cl-return-from exwm-xim--init)) + ;; Initialize atoms. + (setq exwm-xim--@server (exwm--intern-atom "@server=exwm-xim") + exwm-xim--LOCALES (exwm--intern-atom "LOCALES") + exwm-xim--TRANSPORT (exwm--intern-atom "TRANSPORT") + exwm-xim--XIM_SERVERS (exwm--intern-atom "XIM_SERVERS") + exwm-xim--_XIM_PROTOCOL (exwm--intern-atom "_XIM_PROTOCOL") + exwm-xim--_XIM_XCONNECT (exwm--intern-atom "_XIM_XCONNECT")) + ;; Create a new connection and event window. + (setq exwm-xim--conn (xcb:connect) + exwm-xim--event-xwin (xcb:generate-id exwm-xim--conn)) + (set-process-query-on-exit-flag (slot-value exwm-xim--conn 'process) nil) + ;; Initialize xcb:keysyms module. + (xcb:keysyms:init exwm-xim--conn) + ;; Listen to SelectionRequest event for connection establishment. + (xcb:+event exwm-xim--conn 'xcb:SelectionRequest + #'exwm-xim--on-SelectionRequest) + ;; Listen to ClientMessage event on IMS window for new XIM connection. + (xcb:+event exwm-xim--conn 'xcb:ClientMessage #'exwm-xim--on-ClientMessage-0) + ;; Listen to DestroyNotify event to do cleanups. + (xcb:+event exwm-xim--conn 'xcb:DestroyNotify #'exwm-xim--on-DestroyNotify) + ;; Create the event window. + (xcb:+request exwm-xim--conn + (make-instance 'xcb:CreateWindow + :depth 0 + :wid exwm-xim--event-xwin + :parent exwm--root + :x 0 + :y 0 + :width 1 + :height 1 + :border-width 0 + :class xcb:WindowClass:InputOutput + :visual 0 + :value-mask xcb:CW:OverrideRedirect + :override-redirect 1)) + ;; Set the selection owner. + (xcb:+request exwm-xim--conn + (make-instance 'xcb:SetSelectionOwner + :owner exwm-xim--event-xwin + :selection exwm-xim--@server + :time xcb:Time:CurrentTime)) + ;; Set XIM_SERVERS property on the root window. + (xcb:+request exwm-xim--conn + (make-instance 'xcb:ChangeProperty + :mode xcb:PropMode:Prepend + :window exwm--root + :property exwm-xim--XIM_SERVERS + :type xcb:Atom:ATOM + :format 32 + :data-len 1 + :data (funcall (if xcb:lsb + #'xcb:-pack-u4-lsb + #'xcb:-pack-u4) + exwm-xim--@server))) + (xcb:flush exwm-xim--conn)) + +(cl-defun exwm-xim--exit () + "Exit the XIM module." + (exwm--log) + ;; Close IMS communication connections. + (mapc (lambda (i) + (when (vectorp i) + (xcb:disconnect (elt i 0)))) + exwm-xim--server-client-plist) + ;; Close the IMS connection. + (unless exwm-xim--conn + (cl-return-from exwm-xim--exit)) + ;; Remove exwm-xim from XIM_SERVERS. + (let ((reply (xcb:+request-unchecked+reply exwm-xim--conn + (make-instance 'xcb:GetProperty + :delete 1 + :window exwm--root + :property exwm-xim--XIM_SERVERS + :type xcb:Atom:ATOM + :long-offset 0 + :long-length 1000))) + unpacked-reply pack unpack) + (unless reply + (cl-return-from exwm-xim--exit)) + (setq reply (slot-value reply 'value)) + (unless (> (length reply) 4) + (cl-return-from exwm-xim--exit)) + (setq reply (vconcat reply) + pack (if xcb:lsb #'xcb:-pack-u4-lsb #'xcb:-pack-u4) + unpack (if xcb:lsb #'xcb:-unpack-u4-lsb #'xcb:-unpack-u4)) + (dotimes (i (/ (length reply) 4)) + (push (funcall unpack reply (* i 4)) unpacked-reply)) + (setq unpacked-reply (delq exwm-xim--@server unpacked-reply) + reply (mapcar pack unpacked-reply)) + (xcb:+request exwm-xim--conn + (make-instance 'xcb:ChangeProperty + :mode xcb:PropMode:Replace + :window exwm--root + :property exwm-xim--XIM_SERVERS + :type xcb:Atom:ATOM + :format 32 + :data-len (length reply) + :data reply)) + (xcb:flush exwm-xim--conn)) + (xcb:disconnect exwm-xim--conn) + (setq exwm-xim--conn nil)) + +(defun exwm-xim-enable () + "Enable XIM support for EXWM." + (exwm--log) + (add-hook 'exwm-init-hook #'exwm-xim--init) + (add-hook 'exwm-exit-hook #'exwm-xim--exit)) + + + +(provide 'exwm-xim) + +;;; exwm-xim.el ends here diff --git a/exwm.el b/exwm.el index 6021f85b012c..7affebfff2ba 100644 --- a/exwm.el +++ b/exwm.el @@ -1,10 +1,10 @@ ;;; exwm.el --- Emacs X Window Manager -*- lexical-binding: t -*- -;; Copyright (C) 2015-2018 Free Software Foundation, Inc. +;; Copyright (C) 2015-2019 Free Software Foundation, Inc. ;; Author: Chris Feng <chris.w.feng@gmail.com> ;; Maintainer: Chris Feng <chris.w.feng@gmail.com> -;; Version: 0.20 +;; Version: 0.21 ;; Package-Requires: ((xelb "0.16")) ;; Keywords: unix ;; URL: https://github.com/ch11ng/exwm @@ -107,6 +107,7 @@ (defun exwm-reset () "Reset the state of the selected window (non-fullscreen, line-mode, etc)." (interactive) + (exwm--log) (with-current-buffer (window-buffer) (when (derived-mode-p 'exwm-mode) (when (exwm-layout--fullscreen-p) @@ -119,6 +120,7 @@ (defun exwm-restart () "Restart EXWM." (interactive) + (exwm--log) (when (exwm--confirm-kill-emacs "[EXWM] Restart? " 'no-check) (let* ((attr (process-attributes (emacs-pid))) (args (cdr (assq 'args attr))) @@ -146,6 +148,7 @@ (defun exwm--update-desktop (xwin) "Update _NET_WM_DESKTOP." + (exwm--log "#x%x" xwin) (with-current-buffer (exwm--id->buffer xwin) (let ((reply (xcb:+request-unchecked+reply exwm--connection (make-instance 'xcb:ewmh:get-_NET_WM_DESKTOP @@ -172,6 +175,7 @@ (defun exwm--update-window-type (id &optional force) "Update _NET_WM_WINDOW_TYPE." + (exwm--log "#x%x" id) (with-current-buffer (exwm--id->buffer id) (unless (and exwm-window-type (not force)) (let ((reply (xcb:+request-unchecked+reply exwm--connection @@ -182,6 +186,7 @@ (defun exwm--update-class (id &optional force) "Update WM_CLASS." + (exwm--log "#x%x" id) (with-current-buffer (exwm--id->buffer id) (unless (and exwm-instance-name exwm-class-name (not force)) (let ((reply (xcb:+request-unchecked+reply exwm--connection @@ -194,6 +199,7 @@ (defun exwm--update-utf8-title (id &optional force) "Update _NET_WM_NAME." + (exwm--log "#x%x" id) (with-current-buffer (exwm--id->buffer id) (when (or force (not exwm-title)) (let ((reply (xcb:+request-unchecked+reply exwm--connection @@ -206,6 +212,7 @@ (defun exwm--update-ctext-title (id &optional force) "Update WM_NAME." + (exwm--log "#x%x" id) (with-current-buffer (exwm--id->buffer id) (unless (or exwm--title-is-utf8 (and exwm-title (not force))) @@ -218,11 +225,13 @@ (defun exwm--update-title (id) "Update _NET_WM_NAME or WM_NAME." + (exwm--log "#x%x" id) (exwm--update-utf8-title id) (exwm--update-ctext-title id)) (defun exwm--update-transient-for (id &optional force) "Update WM_TRANSIENT_FOR." + (exwm--log "#x%x" id) (with-current-buffer (exwm--id->buffer id) (unless (and exwm-transient-for (not force)) (let ((reply (xcb:+request-unchecked+reply exwm--connection @@ -233,6 +242,7 @@ (defun exwm--update-normal-hints (id &optional force) "Update WM_NORMAL_HINTS." + (exwm--log "#x%x" id) (with-current-buffer (exwm--id->buffer id) (unless (and (not force) (or exwm--normal-hints-x exwm--normal-hints-y @@ -280,6 +290,7 @@ (defun exwm--update-hints (id &optional force) "Update WM_HINTS." + (exwm--log "#x%x" id) (with-current-buffer (exwm--id->buffer id) (unless (and (not force) exwm--hints-input exwm--hints-urgency) (let ((reply (xcb:+request-unchecked+reply exwm--connection @@ -301,6 +312,7 @@ (defun exwm--update-protocols (id &optional force) "Update WM_PROTOCOLS." + (exwm--log "#x%x" id) (with-current-buffer (exwm--id->buffer id) (unless (and exwm--protocols (not force)) (let ((reply (xcb:+request-unchecked+reply exwm--connection @@ -311,6 +323,7 @@ (defun exwm--update-struts-legacy (id) "Update _NET_WM_STRUT." + (exwm--log "#x%x" id) (let ((pair (assq id exwm-workspace--id-struts-alist)) reply struts) (unless (and pair (< 4 (length (cdr pair)))) @@ -331,6 +344,7 @@ (defun exwm--update-struts-partial (id) "Update _NET_WM_STRUT_PARTIAL." + (exwm--log "#x%x" id) (let ((reply (xcb:+request-unchecked+reply exwm--connection (make-instance 'xcb:ewmh:get-_NET_WM_STRUT_PARTIAL :window id))) @@ -350,6 +364,7 @@ (defun exwm--update-struts (id) "Update _NET_WM_STRUT_PARTIAL or _NET_WM_STRUT." + (exwm--log "#x%x" id) (exwm--update-struts-partial id) (exwm--update-struts-legacy id)) @@ -386,9 +401,10 @@ ((= atom xcb:Atom:WM_PROTOCOLS) (exwm--update-protocols id t)) ((= atom xcb:Atom:_NET_WM_USER_TIME)) ;ignored - (t (exwm--log "Unhandled PropertyNotify: %s(%d)" - (x-get-atom-name atom exwm-workspace--current) - atom))))))) + (t + (exwm--log "Unhandled: %s(%d)" + (x-get-atom-name atom exwm-workspace--current) + atom))))))) (defun exwm--on-ClientMessage (raw-data _synthetic) "Handle ClientMessage event." @@ -416,16 +432,21 @@ (exwm-workspace-switch (elt data 0))) ;; _NET_ACTIVE_WINDOW. ((= type xcb:Atom:_NET_ACTIVE_WINDOW) - (let ((buffer (exwm--id->buffer id))) + (let ((buffer (exwm--id->buffer id)) + iconic window) (when (buffer-live-p buffer) (with-current-buffer buffer (when (eq exwm--frame exwm-workspace--current) - (when (exwm-layout--iconic-state-p) + (setq iconic (exwm-layout--iconic-state-p)) + (when iconic ;; State change: iconic => normal. (set-window-buffer (frame-selected-window exwm--frame) (current-buffer))) ;; Focus transfer. - (select-window (get-buffer-window nil t))))))) + (setq window (get-buffer-window nil t)) + (when (or iconic + (not (eq window (selected-window)))) + (select-window window))))))) ;; _NET_CLOSE_WINDOW. ((= type xcb:Atom:_NET_CLOSE_WINDOW) (let ((buffer (exwm--id->buffer id))) @@ -529,11 +550,13 @@ (= (elt data 0) xcb:icccm:WM_STATE:IconicState)) (with-current-buffer buffer (bury-buffer))))) - (t (exwm--log "Unhandled client message: %s" obj))))) + (t + (exwm--log "Unhandled: %s(%d)" + (x-get-atom-name type exwm-workspace--current) type))))) (defun exwm--on-SelectionClear (data _synthetic) "Handle SelectionClear events." - (exwm--log "SelectionClear") + (exwm--log) (let ((obj (make-instance 'xcb:SelectionClear)) owner selection) (xcb:unmarshal obj data) @@ -545,6 +568,7 @@ (defun exwm--init-icccm-ewmh () "Initialize ICCCM/EWMH support." + (exwm--log) ;; Handle PropertyNotify event (xcb:+event exwm--connection 'xcb:PropertyNotify #'exwm--on-PropertyNotify) ;; Handle relevant client messages @@ -688,6 +712,7 @@ REPLACE specifies what to do in case there already is a window manager. If t, replace it, if nil, abort and ask the user if `ask'." + (exwm--log "%s" replace) (with-slots (owner) (xcb:+request-unchecked+reply exwm--connection (make-instance 'xcb:GetSelectionOwner @@ -762,6 +787,7 @@ manager. If t, replace it, if nil, abort and ask the user if `ask'." (cl-defun exwm-init (&optional frame) "Initialize EXWM." (interactive) + (exwm--log "%s" frame) (if frame ;; The frame might not be selected if it's created by emacslicnet. (select-frame-set-input-focus frame) @@ -807,9 +833,9 @@ manager. If t, replace it, if nil, abort and ask the user if `ask'." (exwm--unlock) (exwm-workspace--post-init) (exwm-input--post-init) + (run-hooks 'exwm-init-hook) ;; Manage existing windows - (exwm-manage--scan) - (run-hooks 'exwm-init-hook)) + (exwm-manage--scan)) ((quit error) (exwm-exit) ;; Rethrow error @@ -820,6 +846,7 @@ manager. If t, replace it, if nil, abort and ask the user if `ask'." (defun exwm-exit () "Exit EXWM." (interactive) + (exwm--log) (run-hooks 'exwm-exit-hook) (setq confirm-kill-emacs nil) ;; Exit modules. @@ -835,6 +862,7 @@ manager. If t, replace it, if nil, abort and ask the user if `ask'." (defun exwm-enable (&optional undo) "Enable/Disable EXWM." + (exwm--log "%s" undo) (pcase undo (`undo ;prevent reinitialization (remove-hook 'window-setup-hook #'exwm-init) @@ -861,6 +889,7 @@ manager. If t, replace it, if nil, abort and ask the user if `ask'." (defun exwm--server-stop () "Stop the subordinate Emacs server." + (exwm--log) (server-force-delete exwm--server-name) (when exwm--server-process (delete-process exwm--server-process) @@ -869,6 +898,7 @@ manager. If t, replace it, if nil, abort and ask the user if `ask'." (defun exwm--server-eval-at (&rest args) "Wrapper of `server-eval-at' used to advice subrs." ;; Start the subordinate Emacs server if it's not alive + (exwm--log "%s" args) (unless (server-running-p exwm--server-name) (when exwm--server-process (delete-process exwm--server-process)) (setq exwm--server-process @@ -907,6 +937,7 @@ manager. If t, replace it, if nil, abort and ask the user if `ask'." (defun exwm--confirm-kill-emacs (prompt &optional force) "Confirm before exiting Emacs." + (exwm--log) (when (cond ((and force (not (eq force 'no-check))) ;; Force killing Emacs. diff --git a/xinitrc b/xinitrc index 0adc06845078..591e4199144f 100644 --- a/xinitrc +++ b/xinitrc @@ -1,17 +1,20 @@ -# Disable access control +# Disable access control for the current user. xhost +SI:localuser:$USER # Make Java applications aware this is a non-reparenting window manager. export _JAVA_AWT_WM_NONREPARENTING=1 -# Themes, etc -gnome-settings-daemon & - -# Fallback cursor +# Set default cursor. xsetroot -cursor_name left_ptr -# Keyboard repeat rate +# Set keyboard repeat rate. xset r rate 200 60 -# Start Emacs -exec dbus-launch --exit-with-session emacs +# Uncomment the following block to use the exwm-xim module. +#export XMODIFIERS=@im=exwm-xim +#export GTK_IM_MODULE=xim +#export QT_IM_MODULE=xim +#export CLUTTER_IM_MODULE=xim + +# Finally start Emacs +exec emacs |