chore(3p/exwm): Subtree EXWM ... again
Upstream EXWM is now sort-of unmaintained[0], but some important PRs are pending[1] which I would like to absorb here somehow. [0]: https://github.com/ch11ng/exwm/issues/845 [1]: https://github.com/ch11ng/exwm/pull/848 git-subtree-dir: third_party/exwm git-subtree-mainline:7894d7e178
git-subtree-split:0368127976
Change-Id: I078358c0ea1d2bac99111748ce9c137d2804ac4e
This commit is contained in:
commit
cefff2ca25
16 changed files with 8567 additions and 0 deletions
1
third_party/exwm/.elpaignore
vendored
Normal file
1
third_party/exwm/.elpaignore
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
README.md
|
3
third_party/exwm/.gitignore
vendored
Normal file
3
third_party/exwm/.gitignore
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
*.elc
|
||||
*-pkg.el
|
||||
*-autoloads.el
|
21
third_party/exwm/README.md
vendored
Normal file
21
third_party/exwm/README.md
vendored
Normal file
|
@ -0,0 +1,21 @@
|
|||
# Emacs X Window Manager
|
||||
|
||||
EXWM (Emacs X Window Manager) is a full-featured tiling X window manager
|
||||
for Emacs built on top of [XELB](https://github.com/ch11ng/xelb).
|
||||
It features:
|
||||
+ Fully keyboard-driven operations
|
||||
+ Hybrid layout modes (tiling & stacking)
|
||||
+ Dynamic workspace support
|
||||
+ ICCCM/EWMH compliance
|
||||
+ (Optional) RandR (multi-monitor) support
|
||||
+ (Optional) Builtin system tray
|
||||
+ (Optional) Builtin input method
|
||||
|
||||
Please check out the
|
||||
[screenshots](https://github.com/ch11ng/exwm/wiki/Screenshots)
|
||||
to get an overview of what EXWM is capable of,
|
||||
and the [user guide](https://github.com/ch11ng/exwm/wiki)
|
||||
for a detailed explanation of its usage.
|
||||
|
||||
**Note**: If you install EXWM from source, it's recommended to install
|
||||
XELB also from source (otherwise install both from GNU ELPA).
|
50
third_party/exwm/exwm-cm.el
vendored
Normal file
50
third_party/exwm/exwm-cm.el
vendored
Normal file
|
@ -0,0 +1,50 @@
|
|||
;;; exwm-cm.el --- Compositing Manager for EXWM -*- lexical-binding: t -*-
|
||||
|
||||
;; Copyright (C) 2016-2020 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 is obsolete since EXWM now supports third-party compositors.
|
||||
|
||||
;;; Code:
|
||||
|
||||
(make-obsolete-variable 'exwm-cm-opacity
|
||||
"This variable should no longer be used." "26")
|
||||
|
||||
(defun exwm-cm-set-opacity (&rest _args)
|
||||
(declare (obsolete nil "26")))
|
||||
|
||||
(defun exwm-cm-enable ()
|
||||
(declare (obsolete nil "26")))
|
||||
|
||||
(defun exwm-cm-start ()
|
||||
(declare (obsolete nil "26")))
|
||||
|
||||
(defun exwm-cm-stop ()
|
||||
(declare (obsolete nil "26")))
|
||||
|
||||
(defun exwm-cm-toggle ()
|
||||
(declare (obsolete nil "26")))
|
||||
|
||||
|
||||
|
||||
(provide 'exwm-cm)
|
||||
|
||||
;;; exwm-cm.el ends here
|
131
third_party/exwm/exwm-config.el
vendored
Normal file
131
third_party/exwm/exwm-config.el
vendored
Normal file
|
@ -0,0 +1,131 @@
|
|||
;;; exwm-config.el --- Predefined configurations -*- lexical-binding: t -*-
|
||||
|
||||
;; Copyright (C) 2015-2020 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 contains typical (yet minimal) configurations of EXWM.
|
||||
|
||||
;;; Code:
|
||||
|
||||
(require 'exwm)
|
||||
(require 'ido)
|
||||
|
||||
(define-obsolete-function-alias 'exwm-config-default
|
||||
#'exwm-config-example "27.1")
|
||||
|
||||
(defun exwm-config-example ()
|
||||
"Default configuration of EXWM."
|
||||
;; Set the initial workspace number.
|
||||
(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)))
|
||||
;; 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
|
||||
(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
|
||||
(exwm-config-ido)
|
||||
;; Other configurations
|
||||
(exwm-config-misc))
|
||||
|
||||
(defun exwm-config--fix/ido-buffer-window-other-frame ()
|
||||
"Fix `ido-buffer-window-other-frame'."
|
||||
(defalias 'exwm-config-ido-buffer-window-other-frame
|
||||
(symbol-function #'ido-buffer-window-other-frame))
|
||||
(defun ido-buffer-window-other-frame (buffer)
|
||||
"This is a version redefined by EXWM.
|
||||
|
||||
You can find the original one at `exwm-config-ido-buffer-window-other-frame'."
|
||||
(with-current-buffer (window-buffer (selected-window))
|
||||
(if (and (derived-mode-p 'exwm-mode)
|
||||
exwm--floating-frame)
|
||||
;; Switch from a floating frame.
|
||||
(with-current-buffer buffer
|
||||
(if (and (derived-mode-p 'exwm-mode)
|
||||
exwm--floating-frame
|
||||
(eq exwm--frame exwm-workspace--current))
|
||||
;; Switch to another floating frame.
|
||||
(frame-root-window exwm--floating-frame)
|
||||
;; Do not switch if the buffer is not on the current workspace.
|
||||
(or (get-buffer-window buffer exwm-workspace--current)
|
||||
(selected-window))))
|
||||
(with-current-buffer buffer
|
||||
(when (derived-mode-p 'exwm-mode)
|
||||
(if (eq exwm--frame exwm-workspace--current)
|
||||
(when exwm--floating-frame
|
||||
;; Switch to a floating frame on the current workspace.
|
||||
(frame-selected-window exwm--floating-frame))
|
||||
;; Do not switch to exwm-mode buffers on other workspace (which
|
||||
;; won't work unless `exwm-layout-show-all-buffers' is set)
|
||||
(unless exwm-layout-show-all-buffers
|
||||
(selected-window)))))))))
|
||||
|
||||
(defun exwm-config-ido ()
|
||||
"Configure Ido to work with EXWM."
|
||||
(ido-mode 1)
|
||||
(add-hook 'exwm-init-hook #'exwm-config--fix/ido-buffer-window-other-frame))
|
||||
|
||||
(defun exwm-config-misc ()
|
||||
"Other configurations."
|
||||
;; Make more room
|
||||
(menu-bar-mode -1)
|
||||
(tool-bar-mode -1)
|
||||
(scroll-bar-mode -1)
|
||||
(fringe-mode 1))
|
||||
|
||||
|
||||
|
||||
(provide 'exwm-config)
|
||||
|
||||
;;; exwm-config.el ends here
|
375
third_party/exwm/exwm-core.el
vendored
Normal file
375
third_party/exwm/exwm-core.el
vendored
Normal file
|
@ -0,0 +1,375 @@
|
|||
;;; exwm-core.el --- Core definitions -*- lexical-binding: t -*-
|
||||
|
||||
;; Copyright (C) 2015-2020 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 includes core definitions of variables, macros, functions, etc
|
||||
;; shared by various other modules.
|
||||
|
||||
;;; Code:
|
||||
|
||||
(require 'kmacro)
|
||||
|
||||
(require 'xcb)
|
||||
(require 'xcb-icccm)
|
||||
(require 'xcb-ewmh)
|
||||
(require 'xcb-debug)
|
||||
|
||||
(defcustom exwm-debug-log-time-function #'exwm-debug-log-uptime
|
||||
"Function used for generating timestamps in `exwm-debug' logs.
|
||||
|
||||
Here are some predefined candidates:
|
||||
`exwm-debug-log-uptime': Display the uptime of this Emacs instance.
|
||||
`exwm-debug-log-time': Display time of day.
|
||||
`nil': Disable timestamp."
|
||||
:group 'exwm-debug
|
||||
:type `(choice (const :tag "Emacs uptime" ,#'exwm-debug-log-uptime)
|
||||
(const :tag "Time of day" ,#'exwm-debug-log-time)
|
||||
(const :tag "Off" nil)
|
||||
(function :tag "Other"))
|
||||
:set (lambda (symbol value)
|
||||
(set-default symbol value)
|
||||
;; Also change the format for XELB to make logs consistent
|
||||
;; (as they share the same buffer).
|
||||
(setq xcb-debug:log-time-function value)))
|
||||
|
||||
(defalias 'exwm-debug-log-uptime 'xcb-debug:log-uptime
|
||||
"Add uptime to `exwm-debug' logs.")
|
||||
|
||||
(defalias 'exwm-debug-log-time 'xcb-debug:log-time
|
||||
"Add time of day to `exwm-debug' logs.")
|
||||
|
||||
(defvar exwm--connection nil "X connection.")
|
||||
|
||||
(defvar exwm--wmsn-window nil
|
||||
"An X window owning the WM_S0 selection.")
|
||||
|
||||
(defvar exwm--wmsn-acquire-timeout 3
|
||||
"Number of seconds to wait for other window managers to release the selection.")
|
||||
|
||||
(defvar exwm--guide-window nil
|
||||
"An X window separating workspaces and X windows.")
|
||||
|
||||
(defvar exwm--id-buffer-alist nil "Alist of (<X window ID> . <Emacs buffer>).")
|
||||
|
||||
(defvar exwm--root nil "Root window.")
|
||||
|
||||
(defvar exwm-input--global-prefix-keys)
|
||||
(defvar exwm-input--simulation-keys)
|
||||
(defvar exwm-input-line-mode-passthrough)
|
||||
(defvar exwm-input-prefix-keys)
|
||||
(declare-function exwm-input--fake-key "exwm-input.el" (event))
|
||||
(declare-function exwm-input--on-KeyPress-line-mode "exwm-input.el"
|
||||
(key-press raw-data))
|
||||
(declare-function exwm-floating-hide "exwm-floating.el")
|
||||
(declare-function exwm-floating-toggle-floating "exwm-floating.el")
|
||||
(declare-function exwm-input-release-keyboard "exwm-input.el")
|
||||
(declare-function exwm-input-send-next-key "exwm-input.el" (times))
|
||||
(declare-function exwm-layout-set-fullscreen "exwm-layout.el" (&optional id))
|
||||
(declare-function exwm-layout-toggle-mode-line "exwm-layout.el")
|
||||
(declare-function exwm-manage--kill-buffer-query-function "exwm-manage.el")
|
||||
(declare-function exwm-workspace-move-window "exwm-workspace.el"
|
||||
(frame-or-index &optional id))
|
||||
|
||||
(define-minor-mode exwm-debug
|
||||
"Debug-logging enabled if non-nil"
|
||||
:global t)
|
||||
|
||||
(defmacro exwm--debug (&rest forms)
|
||||
(when exwm-debug `(progn ,@forms)))
|
||||
|
||||
(defmacro exwm--log (&optional format-string &rest objects)
|
||||
"Emit a message prepending the name of the function being executed.
|
||||
|
||||
FORMAT-STRING is a string specifying the message to output, as in
|
||||
`format'. The OBJECTS arguments specify the substitutions."
|
||||
(unless format-string (setq format-string ""))
|
||||
`(when exwm-debug
|
||||
(xcb-debug:message ,(concat "%s%s:\t" format-string "\n")
|
||||
(if exwm-debug-log-time-function
|
||||
(funcall exwm-debug-log-time-function)
|
||||
"")
|
||||
(xcb-debug:compile-time-function-name)
|
||||
,@objects)
|
||||
nil))
|
||||
|
||||
(defsubst exwm--id->buffer (id)
|
||||
"X window ID => Emacs buffer."
|
||||
(cdr (assoc id exwm--id-buffer-alist)))
|
||||
|
||||
(defsubst exwm--buffer->id (buffer)
|
||||
"Emacs buffer BUFFER => X window ID."
|
||||
(car (rassoc buffer exwm--id-buffer-alist)))
|
||||
|
||||
(defun exwm--lock (&rest _args)
|
||||
"Lock (disable all events)."
|
||||
(exwm--log)
|
||||
(xcb:+request exwm--connection
|
||||
(make-instance 'xcb:ChangeWindowAttributes
|
||||
:window exwm--root
|
||||
:value-mask xcb:CW:EventMask
|
||||
:event-mask xcb:EventMask:NoEvent))
|
||||
(xcb:flush exwm--connection))
|
||||
|
||||
(defun exwm--unlock (&rest _args)
|
||||
"Unlock (enable all events)."
|
||||
(exwm--log)
|
||||
(xcb:+request exwm--connection
|
||||
(make-instance 'xcb:ChangeWindowAttributes
|
||||
:window exwm--root
|
||||
:value-mask xcb:CW:EventMask
|
||||
:event-mask (eval-when-compile
|
||||
(logior xcb:EventMask:SubstructureRedirect
|
||||
xcb:EventMask:StructureNotify))))
|
||||
(xcb:flush exwm--connection))
|
||||
|
||||
(defun exwm--set-geometry (xwin x y width height)
|
||||
"Set the geometry of X window XWIN to WIDTHxHEIGHT+X+Y.
|
||||
|
||||
Nil can be passed as placeholder."
|
||||
(exwm--log "Setting #x%x to %sx%s+%s+%s" xwin width height x y)
|
||||
(xcb:+request exwm--connection
|
||||
(make-instance 'xcb:ConfigureWindow
|
||||
:window xwin
|
||||
:value-mask (logior (if x xcb:ConfigWindow:X 0)
|
||||
(if y xcb:ConfigWindow:Y 0)
|
||||
(if width xcb:ConfigWindow:Width 0)
|
||||
(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.
|
||||
|
||||
The action is to call FUNCTION with arguments ARGS. If Emacs is not idle,
|
||||
defer the action until Emacs is idle. Otherwise, defer the action until at
|
||||
least SECS seconds later."
|
||||
`(run-with-idle-timer (+ (float-time (or (current-idle-time)
|
||||
(seconds-to-time (- ,secs))))
|
||||
,secs)
|
||||
nil
|
||||
,function
|
||||
,@args))
|
||||
|
||||
(defun exwm--get-client-event-mask ()
|
||||
"Return event mask set on all managed windows."
|
||||
(logior xcb:EventMask:StructureNotify
|
||||
xcb:EventMask:PropertyChange
|
||||
(if mouse-autoselect-window
|
||||
xcb:EventMask:EnterWindow 0)))
|
||||
|
||||
(defun exwm--color->pixel (color)
|
||||
"Convert COLOR to PIXEL (index in TrueColor colormap)."
|
||||
(when (and color
|
||||
(eq (x-display-visual-class) 'true-color))
|
||||
(let ((rgb (x-color-values color)))
|
||||
(logior (lsh (lsh (pop rgb) -8) 16)
|
||||
(lsh (lsh (pop rgb) -8) 8)
|
||||
(lsh (pop rgb) -8)))))
|
||||
|
||||
;; Internal variables
|
||||
(defvar-local exwm--id nil) ;window ID
|
||||
(defvar-local exwm--configurations nil) ;initial configurations.
|
||||
(defvar-local exwm--frame nil) ;workspace frame
|
||||
(defvar-local exwm--floating-frame nil) ;floating frame
|
||||
(defvar-local exwm--mode-line-format nil) ;save mode-line-format
|
||||
(defvar-local exwm--floating-frame-position nil) ;set when hidden.
|
||||
(defvar-local exwm--fixed-size nil) ;fixed size
|
||||
(defvar-local exwm--selected-input-mode 'line-mode
|
||||
"Input mode as selected by the user.
|
||||
One of `line-mode' or `char-mode'.")
|
||||
(defvar-local exwm--input-mode 'line-mode
|
||||
"Actual input mode, i.e. whether mouse and keyboard are grabbed.")
|
||||
;; Properties
|
||||
(defvar-local exwm--desktop nil "_NET_WM_DESKTOP.")
|
||||
(defvar-local exwm-window-type nil "_NET_WM_WINDOW_TYPE.")
|
||||
(defvar-local exwm--geometry nil)
|
||||
(defvar-local exwm-class-name nil "Class name in WM_CLASS.")
|
||||
(defvar-local exwm-instance-name nil "Instance name in WM_CLASS.")
|
||||
(defvar-local exwm-title nil "Window title (either _NET_WM_NAME or WM_NAME)")
|
||||
(defvar-local exwm--title-is-utf8 nil)
|
||||
(defvar-local exwm-transient-for nil "WM_TRANSIENT_FOR.")
|
||||
(defvar-local exwm--protocols nil)
|
||||
(defvar-local exwm-state xcb:icccm:WM_STATE:NormalState "WM_STATE.")
|
||||
(defvar-local exwm--ewmh-state nil "_NET_WM_STATE.")
|
||||
;; _NET_WM_NORMAL_HINTS
|
||||
(defvar-local exwm--normal-hints-x nil)
|
||||
(defvar-local exwm--normal-hints-y nil)
|
||||
(defvar-local exwm--normal-hints-width nil)
|
||||
(defvar-local exwm--normal-hints-height nil)
|
||||
(defvar-local exwm--normal-hints-min-width nil)
|
||||
(defvar-local exwm--normal-hints-min-height nil)
|
||||
(defvar-local exwm--normal-hints-max-width nil)
|
||||
(defvar-local exwm--normal-hints-max-height nil)
|
||||
;; (defvar-local exwm--normal-hints-win-gravity nil)
|
||||
;; WM_HINTS
|
||||
(defvar-local exwm--hints-input nil)
|
||||
(defvar-local exwm--hints-urgency nil)
|
||||
;; _MOTIF_WM_HINTS
|
||||
(defvar-local exwm--mwm-hints-decorations t)
|
||||
|
||||
(defvar exwm-mode-map
|
||||
(let ((map (make-sparse-keymap)))
|
||||
(define-key map "\C-c\C-d\C-l" #'xcb-debug:clear)
|
||||
(define-key map "\C-c\C-d\C-m" #'xcb-debug:mark)
|
||||
(define-key map "\C-c\C-d\C-t" #'exwm-debug)
|
||||
(define-key map "\C-c\C-f" #'exwm-layout-set-fullscreen)
|
||||
(define-key map "\C-c\C-h" #'exwm-floating-hide)
|
||||
(define-key map "\C-c\C-k" #'exwm-input-release-keyboard)
|
||||
(define-key map "\C-c\C-m" #'exwm-workspace-move-window)
|
||||
(define-key map "\C-c\C-q" #'exwm-input-send-next-key)
|
||||
(define-key map "\C-c\C-t\C-f" #'exwm-floating-toggle-floating)
|
||||
(define-key map "\C-c\C-t\C-m" #'exwm-layout-toggle-mode-line)
|
||||
map)
|
||||
"Keymap for `exwm-mode'.")
|
||||
|
||||
(defvar exwm--kmacro-map
|
||||
(let ((map (make-sparse-keymap)))
|
||||
(define-key map [t]
|
||||
(lambda ()
|
||||
(interactive)
|
||||
(cond
|
||||
((or exwm-input-line-mode-passthrough
|
||||
;; Do not test `exwm-input--during-command'.
|
||||
(active-minibuffer-window)
|
||||
(memq last-input-event exwm-input--global-prefix-keys)
|
||||
(memq last-input-event exwm-input-prefix-keys)
|
||||
(lookup-key exwm-mode-map (vector last-input-event))
|
||||
(gethash last-input-event exwm-input--simulation-keys))
|
||||
(set-transient-map (make-composed-keymap (list exwm-mode-map
|
||||
global-map)))
|
||||
(push last-input-event unread-command-events))
|
||||
(t
|
||||
(exwm-input--fake-key last-input-event)))))
|
||||
map)
|
||||
"Keymap used when executing keyboard macros.")
|
||||
|
||||
;; This menu mainly acts as an reminder for users. Thus it should be as
|
||||
;; detailed as possible, even some entries do not make much sense here.
|
||||
;; Also, inactive entries should be disabled rather than hidden.
|
||||
(easy-menu-define exwm-mode-menu exwm-mode-map
|
||||
"Menu for `exwm-mode'."
|
||||
'("EXWM"
|
||||
"---"
|
||||
"*General*"
|
||||
"---"
|
||||
["Toggle floating" exwm-floating-toggle-floating]
|
||||
["Toggle fullscreen mode" exwm-layout-toggle-fullscreen]
|
||||
["Hide window" exwm-floating-hide exwm--floating-frame]
|
||||
["Close window" (kill-buffer (current-buffer))]
|
||||
|
||||
"---"
|
||||
"*Resizing*"
|
||||
"---"
|
||||
["Toggle mode-line" exwm-layout-toggle-mode-line]
|
||||
["Enlarge window vertically" exwm-layout-enlarge-window]
|
||||
["Enlarge window horizontally" exwm-layout-enlarge-window-horizontally]
|
||||
["Shrink window vertically" exwm-layout-shrink-window]
|
||||
["Shrink window horizontally" exwm-layout-shrink-window-horizontally]
|
||||
|
||||
"---"
|
||||
"*Keyboard*"
|
||||
"---"
|
||||
["Toggle keyboard mode" exwm-input-toggle-keyboard]
|
||||
["Send key" exwm-input-send-next-key (eq exwm--input-mode 'line-mode)]
|
||||
;; This is merely a reference.
|
||||
("Send simulation key" :filter
|
||||
(lambda (&rest _args)
|
||||
(let (result)
|
||||
(maphash
|
||||
(lambda (key value)
|
||||
(when (sequencep key)
|
||||
(setq result (append result
|
||||
`([
|
||||
,(format "Send '%s'"
|
||||
(key-description value))
|
||||
(lambda ()
|
||||
(interactive)
|
||||
(dolist (i ',value)
|
||||
(exwm-input--fake-key i)))
|
||||
:keys ,(key-description key)])))))
|
||||
exwm-input--simulation-keys)
|
||||
result)))
|
||||
|
||||
["Define global binding" exwm-input-set-key]
|
||||
|
||||
"---"
|
||||
"*Workspace*"
|
||||
"---"
|
||||
["Add workspace" exwm-workspace-add]
|
||||
["Delete current workspace" exwm-workspace-delete]
|
||||
["Move workspace to" exwm-workspace-move]
|
||||
["Swap workspaces" exwm-workspace-swap]
|
||||
["Move X window to" exwm-workspace-move-window]
|
||||
["Move X window from" exwm-workspace-switch-to-buffer]
|
||||
["Toggle minibuffer" exwm-workspace-toggle-minibuffer]
|
||||
["Switch workspace" exwm-workspace-switch]
|
||||
;; Place this entry at bottom to avoid selecting others by accident.
|
||||
("Switch to" :filter
|
||||
(lambda (&rest _args)
|
||||
(mapcar (lambda (i)
|
||||
`[,(format "Workspace %d" i)
|
||||
(lambda ()
|
||||
(interactive)
|
||||
(exwm-workspace-switch ,i))
|
||||
(/= ,i exwm-workspace-current-index)])
|
||||
(number-sequence 0 (1- (exwm-workspace--count))))))))
|
||||
|
||||
(define-derived-mode exwm-mode nil "EXWM"
|
||||
"Major mode for managing X windows.
|
||||
|
||||
\\{exwm-mode-map}"
|
||||
;;
|
||||
(setq mode-name
|
||||
'(:eval (propertize "EXWM" 'face
|
||||
(when (cl-some (lambda (i)
|
||||
(frame-parameter i 'exwm-urgency))
|
||||
exwm-workspace--list)
|
||||
'font-lock-warning-face))))
|
||||
;; Change major-mode is not allowed
|
||||
(add-hook 'change-major-mode-hook #'kill-buffer nil t)
|
||||
;; Kill buffer -> close window
|
||||
(add-hook 'kill-buffer-query-functions
|
||||
#'exwm-manage--kill-buffer-query-function nil t)
|
||||
;; Redirect events when executing keyboard macros.
|
||||
(push `(executing-kbd-macro . ,exwm--kmacro-map)
|
||||
minor-mode-overriding-map-alist)
|
||||
(setq buffer-read-only t
|
||||
cursor-type nil
|
||||
left-margin-width nil
|
||||
right-margin-width nil
|
||||
left-fringe-width 0
|
||||
right-fringe-width 0
|
||||
vertical-scroll-bar nil))
|
||||
|
||||
|
||||
|
||||
(provide 'exwm-core)
|
||||
|
||||
;;; exwm-core.el ends here
|
783
third_party/exwm/exwm-floating.el
vendored
Normal file
783
third_party/exwm/exwm-floating.el
vendored
Normal file
|
@ -0,0 +1,783 @@
|
|||
;;; exwm-floating.el --- Floating Module for EXWM -*- lexical-binding: t -*-
|
||||
|
||||
;; Copyright (C) 2015-2020 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 deals with the conversion between floating and non-floating
|
||||
;; states and implements moving/resizing operations on floating windows.
|
||||
|
||||
;;; Code:
|
||||
|
||||
(require 'xcb-cursor)
|
||||
(require 'exwm-core)
|
||||
|
||||
(defgroup exwm-floating nil
|
||||
"Floating."
|
||||
:version "25.3"
|
||||
:group 'exwm)
|
||||
|
||||
(defcustom exwm-floating-setup-hook nil
|
||||
"Normal hook run when an X window has been made floating, in the
|
||||
context of the corresponding buffer."
|
||||
:type 'hook)
|
||||
|
||||
(defcustom exwm-floating-exit-hook nil
|
||||
"Normal hook run when an X window has exited floating state, in the
|
||||
context of the corresponding buffer."
|
||||
:type 'hook)
|
||||
|
||||
(defcustom exwm-floating-border-color "navy"
|
||||
"Border color of floating windows."
|
||||
:type 'color
|
||||
:initialize #'custom-initialize-default
|
||||
:set (lambda (symbol value)
|
||||
(set-default symbol value)
|
||||
;; Change border color for all floating X windows.
|
||||
(when exwm--connection
|
||||
(let ((border-pixel (exwm--color->pixel value)))
|
||||
(when border-pixel
|
||||
(dolist (pair exwm--id-buffer-alist)
|
||||
(with-current-buffer (cdr pair)
|
||||
(when exwm--floating-frame
|
||||
(xcb:+request exwm--connection
|
||||
(make-instance 'xcb:ChangeWindowAttributes
|
||||
:window
|
||||
(frame-parameter exwm--floating-frame
|
||||
'exwm-container)
|
||||
:value-mask xcb:CW:BorderPixel
|
||||
:border-pixel border-pixel)))))
|
||||
(xcb:flush exwm--connection))))))
|
||||
|
||||
(defcustom exwm-floating-border-width 1
|
||||
"Border width of floating windows."
|
||||
:type '(integer
|
||||
:validate (lambda (widget)
|
||||
(when (< (widget-value widget) 0)
|
||||
(widget-put widget :error "Border width is at least 0")
|
||||
widget)))
|
||||
:initialize #'custom-initialize-default
|
||||
:set (lambda (symbol value)
|
||||
(let ((delta (- value exwm-floating-border-width))
|
||||
container)
|
||||
(set-default symbol value)
|
||||
;; Change border width for all floating X windows.
|
||||
(dolist (pair exwm--id-buffer-alist)
|
||||
(with-current-buffer (cdr pair)
|
||||
(when exwm--floating-frame
|
||||
(setq container (frame-parameter exwm--floating-frame
|
||||
'exwm-container))
|
||||
(with-slots (x y)
|
||||
(xcb:+request-unchecked+reply exwm--connection
|
||||
(make-instance 'xcb:GetGeometry
|
||||
:drawable container))
|
||||
(xcb:+request exwm--connection
|
||||
(make-instance 'xcb:ConfigureWindow
|
||||
:window container
|
||||
:value-mask
|
||||
(logior xcb:ConfigWindow:X
|
||||
xcb:ConfigWindow:Y
|
||||
xcb:ConfigWindow:BorderWidth)
|
||||
:border-width value
|
||||
:x (- x delta)
|
||||
:y (- y delta)))))))
|
||||
(when exwm--connection
|
||||
(xcb:flush exwm--connection)))))
|
||||
|
||||
;; Cursors for moving/resizing a window
|
||||
(defvar exwm-floating--cursor-move nil)
|
||||
(defvar exwm-floating--cursor-top-left nil)
|
||||
(defvar exwm-floating--cursor-top nil)
|
||||
(defvar exwm-floating--cursor-top-right nil)
|
||||
(defvar exwm-floating--cursor-right nil)
|
||||
(defvar exwm-floating--cursor-bottom-right nil)
|
||||
(defvar exwm-floating--cursor-bottom nil)
|
||||
(defvar exwm-floating--cursor-bottom-left nil)
|
||||
(defvar exwm-floating--cursor-left nil)
|
||||
|
||||
(defvar exwm-floating--moveresize-calculate nil
|
||||
"Calculate move/resize parameters [buffer event-mask x y width height].")
|
||||
|
||||
(defvar exwm-workspace--current)
|
||||
(defvar exwm-workspace--frame-y-offset)
|
||||
(defvar exwm-workspace--window-y-offset)
|
||||
(defvar exwm-workspace--workareas)
|
||||
(declare-function exwm-layout--hide "exwm-layout.el" (id))
|
||||
(declare-function exwm-layout--iconic-state-p "exwm-layout.el" (&optional id))
|
||||
(declare-function exwm-layout--refresh "exwm-layout.el" ())
|
||||
(declare-function exwm-layout--show "exwm-layout.el" (id &optional window))
|
||||
(declare-function exwm-workspace--position "exwm-workspace.el" (frame))
|
||||
(declare-function exwm-workspace--update-offsets "exwm-workspace.el" ())
|
||||
|
||||
(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
|
||||
:data (if tilling
|
||||
(vector xcb:Atom:_NET_WM_ACTION_MINIMIZE
|
||||
xcb:Atom:_NET_WM_ACTION_FULLSCREEN
|
||||
xcb:Atom:_NET_WM_ACTION_CHANGE_DESKTOP
|
||||
xcb:Atom:_NET_WM_ACTION_CLOSE)
|
||||
(vector xcb:Atom:_NET_WM_ACTION_MOVE
|
||||
xcb:Atom:_NET_WM_ACTION_RESIZE
|
||||
xcb:Atom:_NET_WM_ACTION_MINIMIZE
|
||||
xcb:Atom:_NET_WM_ACTION_FULLSCREEN
|
||||
xcb:Atom:_NET_WM_ACTION_CHANGE_DESKTOP
|
||||
xcb:Atom:_NET_WM_ACTION_CLOSE)))))
|
||||
|
||||
(defun exwm-floating--set-floating (id)
|
||||
"Make window ID floating."
|
||||
(let ((window (get-buffer-window (exwm--id->buffer id))))
|
||||
(when window
|
||||
;; Hide the non-floating X window first.
|
||||
(set-window-buffer window (other-buffer nil t))))
|
||||
(let* ((original-frame (buffer-local-value 'exwm--frame
|
||||
(exwm--id->buffer id)))
|
||||
;; Create new frame
|
||||
(frame (with-current-buffer
|
||||
(or (get-buffer "*scratch*")
|
||||
(progn
|
||||
(set-buffer-major-mode
|
||||
(get-buffer-create "*scratch*"))
|
||||
(get-buffer "*scratch*")))
|
||||
(make-frame
|
||||
`((minibuffer . ,(minibuffer-window exwm--frame))
|
||||
(left . ,(* window-min-width -10000))
|
||||
(top . ,(* window-min-height -10000))
|
||||
(width . ,window-min-width)
|
||||
(height . ,window-min-height)
|
||||
(unsplittable . t))))) ;and fix the size later
|
||||
(outer-id (string-to-number (frame-parameter frame 'outer-window-id)))
|
||||
(window-id (string-to-number (frame-parameter frame 'window-id)))
|
||||
(frame-container (xcb:generate-id exwm--connection))
|
||||
(window (frame-first-window frame)) ;and it's the only window
|
||||
(x (slot-value exwm--geometry 'x))
|
||||
(y (slot-value exwm--geometry 'y))
|
||||
(width (slot-value exwm--geometry 'width))
|
||||
(height (slot-value exwm--geometry 'height)))
|
||||
;; Force drawing menu-bar & tool-bar.
|
||||
(redisplay t)
|
||||
(exwm-workspace--update-offsets)
|
||||
(exwm--log "Floating geometry (original): %dx%d%+d%+d" width height x y)
|
||||
;; Save frame parameters.
|
||||
(set-frame-parameter frame 'exwm-outer-id outer-id)
|
||||
(set-frame-parameter frame 'exwm-id window-id)
|
||||
(set-frame-parameter frame 'exwm-container frame-container)
|
||||
;; Fix illegal parameters
|
||||
;; FIXME: check normal hints restrictions
|
||||
(let* ((workarea (elt exwm-workspace--workareas
|
||||
(exwm-workspace--position original-frame)))
|
||||
(x* (aref workarea 0))
|
||||
(y* (aref workarea 1))
|
||||
(width* (aref workarea 2))
|
||||
(height* (aref workarea 3)))
|
||||
;; Center floating windows
|
||||
(when (and (or (= x 0) (= x x*))
|
||||
(or (= y 0) (= y y*)))
|
||||
(let ((buffer (exwm--id->buffer exwm-transient-for))
|
||||
window edges)
|
||||
(when (and buffer (setq window (get-buffer-window buffer)))
|
||||
(setq edges (window-inside-absolute-pixel-edges window))
|
||||
(unless (and (<= width (- (elt edges 2) (elt edges 0)))
|
||||
(<= height (- (elt edges 3) (elt edges 1))))
|
||||
(setq edges nil)))
|
||||
(if edges
|
||||
;; Put at the center of leading window
|
||||
(setq x (+ x* (/ (- (elt edges 2) (elt edges 0) width) 2))
|
||||
y (+ y* (/ (- (elt edges 3) (elt edges 1) height) 2)))
|
||||
;; Put at the center of screen
|
||||
(setq x (/ (- width* width) 2)
|
||||
y (/ (- height* height) 2)))))
|
||||
(if (> width width*)
|
||||
;; Too wide
|
||||
(progn (setq x x*
|
||||
width width*))
|
||||
;; Invalid width
|
||||
(when (= 0 width) (setq width (/ width* 2)))
|
||||
;; Make sure at least half of the window is visible
|
||||
(unless (< x* (+ x (/ width 2)) (+ x* width*))
|
||||
(setq x (+ x* (/ (- width* width) 2)))))
|
||||
(if (> height height*)
|
||||
;; Too tall
|
||||
(setq y y*
|
||||
height height*)
|
||||
;; Invalid height
|
||||
(when (= 0 height) (setq height (/ height* 2)))
|
||||
;; Make sure at least half of the window is visible
|
||||
(unless (< y* (+ y (/ height 2)) (+ y* height*))
|
||||
(setq y (+ y* (/ (- height* height) 2)))))
|
||||
;; The geometry can be overridden by user options.
|
||||
(let ((x** (plist-get exwm--configurations 'x))
|
||||
(y** (plist-get exwm--configurations 'y))
|
||||
(width** (plist-get exwm--configurations 'width))
|
||||
(height** (plist-get exwm--configurations 'height)))
|
||||
(if (integerp x**)
|
||||
(setq x (+ x* x**))
|
||||
(when (and (floatp x**)
|
||||
(>= 1 x** 0))
|
||||
(setq x (+ x* (round (* x** width*))))))
|
||||
(if (integerp y**)
|
||||
(setq y (+ y* y**))
|
||||
(when (and (floatp y**)
|
||||
(>= 1 y** 0))
|
||||
(setq y (+ y* (round (* y** height*))))))
|
||||
(if (integerp width**)
|
||||
(setq width width**)
|
||||
(when (and (floatp width**)
|
||||
(> 1 width** 0))
|
||||
(setq width (max 1 (round (* width** width*))))))
|
||||
(if (integerp height**)
|
||||
(setq height height**)
|
||||
(when (and (floatp height**)
|
||||
(> 1 height** 0))
|
||||
(setq height (max 1 (round (* height** height*))))))))
|
||||
(exwm--set-geometry id x y nil nil)
|
||||
(xcb:flush exwm--connection)
|
||||
(exwm--log "Floating geometry (corrected): %dx%d%+d%+d" width height x y)
|
||||
;; Fit frame to client
|
||||
;; It seems we have to make the frame invisible in order to resize it
|
||||
;; timely.
|
||||
;; The frame will be made visible by `select-frame-set-input-focus'.
|
||||
(make-frame-invisible frame)
|
||||
(let* ((edges (window-inside-pixel-edges window))
|
||||
(frame-width (+ width (- (frame-pixel-width frame)
|
||||
(- (elt edges 2) (elt edges 0)))))
|
||||
(frame-height (+ height (- (frame-pixel-height frame)
|
||||
(- (elt edges 3) (elt edges 1)))
|
||||
;; Use `frame-outer-height' in the future.
|
||||
exwm-workspace--frame-y-offset))
|
||||
(floating-mode-line (plist-get exwm--configurations
|
||||
'floating-mode-line))
|
||||
(floating-header-line (plist-get exwm--configurations
|
||||
'floating-header-line))
|
||||
(border-pixel (exwm--color->pixel exwm-floating-border-color)))
|
||||
(if floating-mode-line
|
||||
(setq exwm--mode-line-format (or exwm--mode-line-format
|
||||
mode-line-format)
|
||||
mode-line-format floating-mode-line)
|
||||
(if (and (not (plist-member exwm--configurations 'floating-mode-line))
|
||||
exwm--mwm-hints-decorations)
|
||||
(when exwm--mode-line-format
|
||||
(setq mode-line-format exwm--mode-line-format))
|
||||
;; The mode-line need to be hidden in floating mode.
|
||||
(setq frame-height (- frame-height (window-mode-line-height
|
||||
(frame-root-window frame)))
|
||||
exwm--mode-line-format (or exwm--mode-line-format
|
||||
mode-line-format)
|
||||
mode-line-format nil)))
|
||||
(if floating-header-line
|
||||
(setq header-line-format floating-header-line)
|
||||
(if (and (not (plist-member exwm--configurations
|
||||
'floating-header-line))
|
||||
exwm--mwm-hints-decorations)
|
||||
(setq header-line-format nil)
|
||||
;; The header-line need to be hidden in floating mode.
|
||||
(setq frame-height (- frame-height (window-header-line-height
|
||||
(frame-root-window frame)))
|
||||
header-line-format nil)))
|
||||
(set-frame-size frame frame-width frame-height t)
|
||||
;; Create the frame container as the parent of the frame.
|
||||
(xcb:+request exwm--connection
|
||||
(make-instance 'xcb:CreateWindow
|
||||
:depth 0
|
||||
:wid frame-container
|
||||
:parent exwm--root
|
||||
:x x
|
||||
:y (- y exwm-workspace--window-y-offset)
|
||||
:width width
|
||||
:height height
|
||||
:border-width
|
||||
(with-current-buffer (exwm--id->buffer id)
|
||||
(let ((border-witdh (plist-get exwm--configurations
|
||||
'border-width)))
|
||||
(if (and (integerp border-witdh)
|
||||
(>= border-witdh 0))
|
||||
border-witdh
|
||||
exwm-floating-border-width)))
|
||||
:class xcb:WindowClass:InputOutput
|
||||
:visual 0
|
||||
:value-mask (logior xcb:CW:BackPixmap
|
||||
(if border-pixel
|
||||
xcb:CW:BorderPixel 0)
|
||||
xcb:CW:OverrideRedirect)
|
||||
:background-pixmap xcb:BackPixmap:ParentRelative
|
||||
:border-pixel border-pixel
|
||||
:override-redirect 1))
|
||||
(xcb:+request exwm--connection
|
||||
(make-instance 'xcb:ewmh:set-_NET_WM_NAME
|
||||
:window frame-container
|
||||
:data
|
||||
(format "EXWM floating frame container for 0x%x" id)))
|
||||
;; Map it.
|
||||
(xcb:+request exwm--connection
|
||||
(make-instance 'xcb:MapWindow :window frame-container))
|
||||
;; Put the X window right above this frame container.
|
||||
(xcb:+request exwm--connection
|
||||
(make-instance 'xcb:ConfigureWindow
|
||||
:window id
|
||||
:value-mask (logior xcb:ConfigWindow:Sibling
|
||||
xcb:ConfigWindow:StackMode)
|
||||
:sibling frame-container
|
||||
:stack-mode xcb:StackMode:Above)))
|
||||
;; Reparent this frame to its container.
|
||||
(xcb:+request exwm--connection
|
||||
(make-instance 'xcb:ReparentWindow
|
||||
:window outer-id :parent frame-container :x 0 :y 0))
|
||||
(exwm-floating--set-allowed-actions id nil)
|
||||
(xcb:flush exwm--connection)
|
||||
;; Set window/buffer
|
||||
(with-current-buffer (exwm--id->buffer id)
|
||||
(setq window-size-fixed exwm--fixed-size
|
||||
exwm--floating-frame frame)
|
||||
;; Do the refresh manually.
|
||||
(remove-hook 'window-configuration-change-hook #'exwm-layout--refresh)
|
||||
(set-window-buffer window (current-buffer)) ;this changes current buffer
|
||||
(add-hook 'window-configuration-change-hook #'exwm-layout--refresh)
|
||||
(set-window-dedicated-p window t)
|
||||
(exwm-layout--show id window))
|
||||
(with-current-buffer (exwm--id->buffer id)
|
||||
(if (exwm-layout--iconic-state-p id)
|
||||
;; Hide iconic floating X windows.
|
||||
(exwm-floating-hide)
|
||||
(with-selected-frame exwm--frame
|
||||
(exwm-layout--refresh)))
|
||||
(select-frame-set-input-focus frame))
|
||||
;; FIXME: Strangely, the Emacs frame can move itself at this point
|
||||
;; when there are left/top struts set. Force resetting its
|
||||
;; position seems working, but it'd better to figure out why.
|
||||
;; FIXME: This also happens in another case (#220) where the cause is
|
||||
;; still unclear.
|
||||
(exwm--set-geometry outer-id 0 0 nil nil)
|
||||
(xcb:flush exwm--connection))
|
||||
(with-current-buffer (exwm--id->buffer id)
|
||||
(run-hooks 'exwm-floating-setup-hook))
|
||||
;; Redraw the frame.
|
||||
(redisplay t))
|
||||
|
||||
(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
|
||||
;; The X window is already mapped.
|
||||
;; Unmap the X window.
|
||||
(xcb:+request exwm--connection
|
||||
(make-instance 'xcb:ChangeWindowAttributes
|
||||
:window id :value-mask xcb:CW:EventMask
|
||||
:event-mask xcb:EventMask:NoEvent))
|
||||
(xcb:+request exwm--connection
|
||||
(make-instance 'xcb:UnmapWindow :window id))
|
||||
(xcb:+request exwm--connection
|
||||
(make-instance 'xcb:ChangeWindowAttributes
|
||||
:window id :value-mask xcb:CW:EventMask
|
||||
:event-mask (exwm--get-client-event-mask)))
|
||||
;; Reparent the floating frame back to the root window.
|
||||
(let ((frame-id (frame-parameter exwm--floating-frame 'exwm-outer-id))
|
||||
(frame-container (frame-parameter exwm--floating-frame
|
||||
'exwm-container)))
|
||||
(xcb:+request exwm--connection
|
||||
(make-instance 'xcb:UnmapWindow :window frame-id))
|
||||
(xcb:+request exwm--connection
|
||||
(make-instance 'xcb:ReparentWindow
|
||||
:window frame-id
|
||||
:parent exwm--root
|
||||
:x 0 :y 0))
|
||||
;; Also destroy its container.
|
||||
(xcb:+request exwm--connection
|
||||
(make-instance 'xcb:DestroyWindow :window frame-container))))
|
||||
;; Place the X window just above the reference X window.
|
||||
;; (the stacking order won't change from now on).
|
||||
;; Also hide the possible floating border.
|
||||
(xcb:+request exwm--connection
|
||||
(make-instance 'xcb:ConfigureWindow
|
||||
:window id
|
||||
:value-mask (logior xcb:ConfigWindow:BorderWidth
|
||||
xcb:ConfigWindow:Sibling
|
||||
xcb:ConfigWindow:StackMode)
|
||||
:border-width 0
|
||||
:sibling exwm--guide-window
|
||||
:stack-mode xcb:StackMode:Above)))
|
||||
(exwm-floating--set-allowed-actions id t)
|
||||
(xcb:flush exwm--connection)
|
||||
(with-current-buffer buffer
|
||||
(when exwm--floating-frame ;from floating to non-floating
|
||||
(set-window-dedicated-p (frame-first-window exwm--floating-frame) nil)
|
||||
;; Select a tiling window and delete the old frame.
|
||||
(select-window (frame-selected-window exwm-workspace--current))
|
||||
(with-current-buffer buffer
|
||||
(delete-frame exwm--floating-frame))))
|
||||
(with-current-buffer buffer
|
||||
(setq window-size-fixed nil
|
||||
exwm--floating-frame nil)
|
||||
(if (not (plist-member exwm--configurations 'tiling-mode-line))
|
||||
(when exwm--mode-line-format
|
||||
(setq mode-line-format exwm--mode-line-format))
|
||||
(setq exwm--mode-line-format (or exwm--mode-line-format
|
||||
mode-line-format)
|
||||
mode-line-format (plist-get exwm--configurations
|
||||
'tiling-mode-line)))
|
||||
(if (not (plist-member exwm--configurations 'tiling-header-line))
|
||||
(setq header-line-format nil)
|
||||
(setq header-line-format (plist-get exwm--configurations
|
||||
'tiling-header-line))))
|
||||
;; Only show X windows in normal state.
|
||||
(unless (exwm-layout--iconic-state-p)
|
||||
(pop-to-buffer-same-window buffer)))
|
||||
(with-current-buffer (exwm--id->buffer id)
|
||||
(run-hooks 'exwm-floating-exit-hook)))
|
||||
|
||||
;;;###autoload
|
||||
(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)
|
||||
(if exwm--floating-frame
|
||||
(exwm-floating--unset-floating exwm--id)
|
||||
(exwm-floating--set-floating exwm--id))))
|
||||
|
||||
;;;###autoload
|
||||
(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)
|
||||
(select-frame-set-input-focus exwm-workspace--current)))
|
||||
|
||||
(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)
|
||||
;; Managed.
|
||||
(with-current-buffer buffer-or-id
|
||||
(setq frame exwm--floating-frame
|
||||
container-or-id (frame-parameter exwm--floating-frame
|
||||
'exwm-container)))
|
||||
;; Unmanaged.
|
||||
(setq container-or-id id))
|
||||
(when (and container-or-id
|
||||
;; Test if the pointer can be grabbed
|
||||
(= xcb:GrabStatus:Success
|
||||
(slot-value
|
||||
(xcb:+request-unchecked+reply exwm--connection
|
||||
(make-instance 'xcb:GrabPointer
|
||||
:owner-events 0
|
||||
:grab-window container-or-id
|
||||
:event-mask xcb:EventMask:NoEvent
|
||||
:pointer-mode xcb:GrabMode:Async
|
||||
:keyboard-mode xcb:GrabMode:Async
|
||||
:confine-to xcb:Window:None
|
||||
:cursor xcb:Cursor:None
|
||||
:time xcb:Time:CurrentTime))
|
||||
'status)))
|
||||
(with-slots (root-x root-y win-x win-y)
|
||||
(xcb:+request-unchecked+reply exwm--connection
|
||||
(make-instance 'xcb:QueryPointer :window id))
|
||||
(if (not (bufferp buffer-or-id))
|
||||
;; Unmanaged.
|
||||
(unless (eq type xcb:ewmh:_NET_WM_MOVERESIZE_MOVE)
|
||||
(with-slots ((width* width)
|
||||
(height* height))
|
||||
(xcb:+request-unchecked+reply exwm--connection
|
||||
(make-instance 'xcb:GetGeometry :drawable id))
|
||||
(setq width width*
|
||||
height height*)))
|
||||
;; Managed.
|
||||
(select-window (frame-first-window frame)) ;transfer input focus
|
||||
(setq width (frame-pixel-width frame)
|
||||
height (frame-pixel-height frame))
|
||||
(unless type
|
||||
;; Determine the resize type according to the pointer position
|
||||
;; Clicking the center 1/3 part to resize has no effect
|
||||
(setq x (/ (* 3 win-x) (float width))
|
||||
y (/ (* 3 win-y) (float height))
|
||||
type (cond ((and (< x 1) (< y 1))
|
||||
xcb:ewmh:_NET_WM_MOVERESIZE_SIZE_TOPLEFT)
|
||||
((and (> x 2) (< y 1))
|
||||
xcb:ewmh:_NET_WM_MOVERESIZE_SIZE_TOPRIGHT)
|
||||
((and (> x 2) (> y 2))
|
||||
xcb:ewmh:_NET_WM_MOVERESIZE_SIZE_BOTTOMRIGHT)
|
||||
((and (< x 1) (> y 2))
|
||||
xcb:ewmh:_NET_WM_MOVERESIZE_SIZE_BOTTOMLEFT)
|
||||
((> x 2) xcb:ewmh:_NET_WM_MOVERESIZE_SIZE_RIGHT)
|
||||
((> y 2) xcb:ewmh:_NET_WM_MOVERESIZE_SIZE_BOTTOM)
|
||||
((< x 1) xcb:ewmh:_NET_WM_MOVERESIZE_SIZE_LEFT)
|
||||
((< y 1) xcb:ewmh:_NET_WM_MOVERESIZE_SIZE_TOP)))))
|
||||
(if (not type)
|
||||
(exwm-floating--stop-moveresize)
|
||||
(cond ((= type xcb:ewmh:_NET_WM_MOVERESIZE_MOVE)
|
||||
(setq cursor exwm-floating--cursor-move
|
||||
exwm-floating--moveresize-calculate
|
||||
(lambda (x y)
|
||||
(vector buffer-or-id
|
||||
(eval-when-compile
|
||||
(logior xcb:ConfigWindow:X
|
||||
xcb:ConfigWindow:Y))
|
||||
(- x win-x) (- y win-y) 0 0))))
|
||||
((= type xcb:ewmh:_NET_WM_MOVERESIZE_SIZE_TOPLEFT)
|
||||
(setq cursor exwm-floating--cursor-top-left
|
||||
exwm-floating--moveresize-calculate
|
||||
(lambda (x y)
|
||||
(vector buffer-or-id
|
||||
(eval-when-compile
|
||||
(logior xcb:ConfigWindow:X
|
||||
xcb:ConfigWindow:Y
|
||||
xcb:ConfigWindow:Width
|
||||
xcb:ConfigWindow:Height))
|
||||
(- x win-x) (- y win-y)
|
||||
(- (+ root-x width) x)
|
||||
(- (+ root-y height) y)))))
|
||||
((= type xcb:ewmh:_NET_WM_MOVERESIZE_SIZE_TOP)
|
||||
(setq cursor exwm-floating--cursor-top
|
||||
exwm-floating--moveresize-calculate
|
||||
(lambda (_x y)
|
||||
(vector buffer-or-id
|
||||
(eval-when-compile
|
||||
(logior xcb:ConfigWindow:Y
|
||||
xcb:ConfigWindow:Height))
|
||||
0 (- y win-y) 0 (- (+ root-y height) y)))))
|
||||
((= type xcb:ewmh:_NET_WM_MOVERESIZE_SIZE_TOPRIGHT)
|
||||
(setq cursor exwm-floating--cursor-top-right
|
||||
exwm-floating--moveresize-calculate
|
||||
(lambda (x y)
|
||||
(vector buffer-or-id
|
||||
(eval-when-compile
|
||||
(logior xcb:ConfigWindow:Y
|
||||
xcb:ConfigWindow:Width
|
||||
xcb:ConfigWindow:Height))
|
||||
0 (- y win-y) (- x (- root-x width))
|
||||
(- (+ root-y height) y)))))
|
||||
((= type xcb:ewmh:_NET_WM_MOVERESIZE_SIZE_RIGHT)
|
||||
(setq cursor exwm-floating--cursor-right
|
||||
exwm-floating--moveresize-calculate
|
||||
(lambda (x _y)
|
||||
(vector buffer-or-id
|
||||
xcb:ConfigWindow:Width
|
||||
0 0 (- x (- root-x width)) 0))))
|
||||
((= type xcb:ewmh:_NET_WM_MOVERESIZE_SIZE_BOTTOMRIGHT)
|
||||
(setq cursor exwm-floating--cursor-bottom-right
|
||||
exwm-floating--moveresize-calculate
|
||||
(lambda (x y)
|
||||
(vector buffer-or-id
|
||||
(eval-when-compile
|
||||
(logior xcb:ConfigWindow:Width
|
||||
xcb:ConfigWindow:Height))
|
||||
0 0 (- x (- root-x width))
|
||||
(- y (- root-y height))))))
|
||||
((= type xcb:ewmh:_NET_WM_MOVERESIZE_SIZE_BOTTOM)
|
||||
(setq cursor exwm-floating--cursor-bottom
|
||||
exwm-floating--moveresize-calculate
|
||||
(lambda (_x y)
|
||||
(vector buffer-or-id
|
||||
xcb:ConfigWindow:Height
|
||||
0 0 0 (- y (- root-y height))))))
|
||||
((= type xcb:ewmh:_NET_WM_MOVERESIZE_SIZE_BOTTOMLEFT)
|
||||
(setq cursor exwm-floating--cursor-bottom-left
|
||||
exwm-floating--moveresize-calculate
|
||||
(lambda (x y)
|
||||
(vector buffer-or-id
|
||||
(eval-when-compile
|
||||
(logior xcb:ConfigWindow:X
|
||||
xcb:ConfigWindow:Width
|
||||
xcb:ConfigWindow:Height))
|
||||
(- x win-x)
|
||||
0
|
||||
(- (+ root-x width) x)
|
||||
(- y (- root-y height))))))
|
||||
((= type xcb:ewmh:_NET_WM_MOVERESIZE_SIZE_LEFT)
|
||||
(setq cursor exwm-floating--cursor-left
|
||||
exwm-floating--moveresize-calculate
|
||||
(lambda (x _y)
|
||||
(vector buffer-or-id
|
||||
(eval-when-compile
|
||||
(logior xcb:ConfigWindow:X
|
||||
xcb:ConfigWindow:Width))
|
||||
(- x win-x) 0 (- (+ root-x width) x) 0)))))
|
||||
;; Select events and change cursor (should always succeed)
|
||||
(xcb:+request-unchecked+reply exwm--connection
|
||||
(make-instance 'xcb:GrabPointer
|
||||
:owner-events 0 :grab-window container-or-id
|
||||
:event-mask (eval-when-compile
|
||||
(logior xcb:EventMask:ButtonRelease
|
||||
xcb:EventMask:ButtonMotion))
|
||||
:pointer-mode xcb:GrabMode:Async
|
||||
:keyboard-mode xcb:GrabMode:Async
|
||||
:confine-to xcb:Window:None
|
||||
:cursor cursor
|
||||
:time xcb:Time:CurrentTime)))))))
|
||||
|
||||
(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
|
||||
(let (result buffer-or-id outer-id container-id)
|
||||
(setq result (funcall exwm-floating--moveresize-calculate 0 0)
|
||||
buffer-or-id (aref result 0))
|
||||
(when (bufferp buffer-or-id)
|
||||
(with-current-buffer buffer-or-id
|
||||
(setq outer-id (frame-parameter exwm--floating-frame 'exwm-outer-id)
|
||||
container-id (frame-parameter exwm--floating-frame
|
||||
'exwm-container))
|
||||
(with-slots (x y width height border-width)
|
||||
(xcb:+request-unchecked+reply exwm--connection
|
||||
(make-instance 'xcb:GetGeometry
|
||||
:drawable container-id))
|
||||
;; Notify Emacs frame about this the position change.
|
||||
(xcb:+request exwm--connection
|
||||
(make-instance 'xcb:SendEvent
|
||||
:propagate 0
|
||||
:destination outer-id
|
||||
:event-mask xcb:EventMask:StructureNotify
|
||||
:event
|
||||
(xcb:marshal
|
||||
(make-instance 'xcb:ConfigureNotify
|
||||
:event outer-id
|
||||
:window outer-id
|
||||
:above-sibling xcb:Window:None
|
||||
:x (+ x border-width)
|
||||
:y (+ y border-width)
|
||||
:width width
|
||||
:height height
|
||||
:border-width 0
|
||||
:override-redirect 0)
|
||||
exwm--connection)))
|
||||
(xcb:flush exwm--connection))
|
||||
(exwm-layout--show exwm--id
|
||||
(frame-root-window exwm--floating-frame)))))
|
||||
(setq exwm-floating--moveresize-calculate nil)))
|
||||
|
||||
(defun exwm-floating--do-moveresize (data _synthetic)
|
||||
"Perform move/resize."
|
||||
(when exwm-floating--moveresize-calculate
|
||||
(let* ((obj (make-instance 'xcb:MotionNotify))
|
||||
result value-mask x y width height buffer-or-id container-or-id)
|
||||
(xcb:unmarshal obj data)
|
||||
(setq result (funcall exwm-floating--moveresize-calculate
|
||||
(slot-value obj 'root-x) (slot-value obj 'root-y))
|
||||
buffer-or-id (aref result 0)
|
||||
value-mask (aref result 1)
|
||||
x (aref result 2)
|
||||
y (aref result 3)
|
||||
width (max 1 (aref result 4))
|
||||
height (max 1 (aref result 5)))
|
||||
(if (not (bufferp buffer-or-id))
|
||||
;; Unmanaged.
|
||||
(setq container-or-id buffer-or-id)
|
||||
;; Managed.
|
||||
(setq container-or-id
|
||||
(with-current-buffer buffer-or-id
|
||||
(frame-parameter exwm--floating-frame 'exwm-container))
|
||||
x (- x exwm-floating-border-width)
|
||||
;; Use `frame-outer-height' in the future.
|
||||
y (- y exwm-floating-border-width
|
||||
exwm-workspace--window-y-offset)
|
||||
height (+ height exwm-workspace--window-y-offset)))
|
||||
(xcb:+request exwm--connection
|
||||
(make-instance 'xcb:ConfigureWindow
|
||||
:window container-or-id
|
||||
:value-mask (aref result 1)
|
||||
:x x
|
||||
:y y
|
||||
:width width
|
||||
:height height))
|
||||
(when (bufferp buffer-or-id)
|
||||
;; Managed.
|
||||
(setq value-mask (logand value-mask (logior xcb:ConfigWindow:Width
|
||||
xcb:ConfigWindow:Height)))
|
||||
(when (/= 0 value-mask)
|
||||
(with-current-buffer buffer-or-id
|
||||
(xcb:+request exwm--connection
|
||||
(make-instance 'xcb:ConfigureWindow
|
||||
:window (frame-parameter exwm--floating-frame
|
||||
'exwm-outer-id)
|
||||
:value-mask value-mask
|
||||
:width width
|
||||
:height height)))))
|
||||
(xcb:flush exwm--connection))))
|
||||
|
||||
(defun exwm-floating-move (&optional delta-x delta-y)
|
||||
"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))
|
||||
(unless delta-y (setq delta-y 1))
|
||||
(unless (and (= 0 delta-x) (= 0 delta-y))
|
||||
(let* ((floating-container (frame-parameter exwm--floating-frame
|
||||
'exwm-container))
|
||||
(geometry (xcb:+request-unchecked+reply exwm--connection
|
||||
(make-instance 'xcb:GetGeometry
|
||||
:drawable floating-container)))
|
||||
(edges (window-inside-absolute-pixel-edges)))
|
||||
(with-slots (x y) geometry
|
||||
(exwm--set-geometry floating-container
|
||||
(+ x delta-x) (+ y delta-y) nil nil))
|
||||
(exwm--set-geometry exwm--id
|
||||
(+ (pop edges) delta-x)
|
||||
(+ (pop edges) delta-y)
|
||||
nil nil))
|
||||
(xcb:flush exwm--connection)))
|
||||
|
||||
(defun exwm-floating--init ()
|
||||
"Initialize floating module."
|
||||
(exwm--log)
|
||||
;; Initialize cursors for moving/resizing a window
|
||||
(xcb:cursor:init exwm--connection)
|
||||
(setq exwm-floating--cursor-move
|
||||
(xcb:cursor:load-cursor exwm--connection "fleur")
|
||||
exwm-floating--cursor-top-left
|
||||
(xcb:cursor:load-cursor exwm--connection "top_left_corner")
|
||||
exwm-floating--cursor-top
|
||||
(xcb:cursor:load-cursor exwm--connection "top_side")
|
||||
exwm-floating--cursor-top-right
|
||||
(xcb:cursor:load-cursor exwm--connection "top_right_corner")
|
||||
exwm-floating--cursor-right
|
||||
(xcb:cursor:load-cursor exwm--connection "right_side")
|
||||
exwm-floating--cursor-bottom-right
|
||||
(xcb:cursor:load-cursor exwm--connection "bottom_right_corner")
|
||||
exwm-floating--cursor-bottom
|
||||
(xcb:cursor:load-cursor exwm--connection "bottom_side")
|
||||
exwm-floating--cursor-bottom-left
|
||||
(xcb:cursor:load-cursor exwm--connection "bottom_left_corner")
|
||||
exwm-floating--cursor-left
|
||||
(xcb:cursor:load-cursor exwm--connection "left_side")))
|
||||
|
||||
(defun exwm-floating--exit ()
|
||||
"Exit the floating module."
|
||||
(exwm--log))
|
||||
|
||||
|
||||
|
||||
(provide 'exwm-floating)
|
||||
|
||||
;;; exwm-floating.el ends here
|
1227
third_party/exwm/exwm-input.el
vendored
Normal file
1227
third_party/exwm/exwm-input.el
vendored
Normal file
File diff suppressed because it is too large
Load diff
620
third_party/exwm/exwm-layout.el
vendored
Normal file
620
third_party/exwm/exwm-layout.el
vendored
Normal file
|
@ -0,0 +1,620 @@
|
|||
;;; exwm-layout.el --- Layout Module for EXWM -*- lexical-binding: t -*-
|
||||
|
||||
;; Copyright (C) 2015-2020 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 is responsible for keeping X client window properly displayed.
|
||||
|
||||
;;; Code:
|
||||
|
||||
(require 'exwm-core)
|
||||
|
||||
(defgroup exwm-layout nil
|
||||
"Layout."
|
||||
: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)
|
||||
|
||||
(defconst exwm-layout--floating-hidden-position -101
|
||||
"Where to place hidden floating X windows.")
|
||||
|
||||
(defvar exwm-layout--other-buffer-exclude-buffers nil
|
||||
"List of buffers that should not be selected by `other-buffer'.")
|
||||
|
||||
(defvar exwm-layout--other-buffer-exclude-exwm-mode-buffers nil
|
||||
"When non-nil, prevent EXWM buffers from being selected by `other-buffer'.")
|
||||
|
||||
(defvar exwm-layout--timer nil "Timer used to track echo area changes.")
|
||||
|
||||
(defvar exwm-workspace--current)
|
||||
(defvar exwm-workspace--frame-y-offset)
|
||||
(declare-function exwm-input--release-keyboard "exwm-input.el")
|
||||
(declare-function exwm-input--grab-keyboard "exwm-input.el")
|
||||
(declare-function exwm-input-grab-keyboard "exwm-input.el")
|
||||
(declare-function exwm-workspace--active-p "exwm-workspace.el" (frame))
|
||||
(declare-function exwm-workspace--client-p "exwm-workspace.el"
|
||||
(&optional frame))
|
||||
(declare-function exwm-workspace--minibuffer-own-frame-p "exwm-workspace.el")
|
||||
(declare-function exwm-workspace--workspace-p "exwm-workspace.el"
|
||||
(workspace))
|
||||
(declare-function exwm-workspace-move-window "exwm-workspace.el"
|
||||
(frame-or-index &optional id))
|
||||
|
||||
(defun exwm-layout--set-state (id state)
|
||||
"Set WM_STATE."
|
||||
(exwm--log "id=#x%x" id)
|
||||
(xcb:+request exwm--connection
|
||||
(make-instance 'xcb:icccm:set-WM_STATE
|
||||
:window id :state state :icon xcb:Window:None))
|
||||
(with-current-buffer (exwm--id->buffer id)
|
||||
(setq exwm-state state)))
|
||||
|
||||
(defun exwm-layout--iconic-state-p (&optional id)
|
||||
(= xcb:icccm:WM_STATE:IconicState
|
||||
(if id
|
||||
(buffer-local-value 'exwm-state (exwm--id->buffer id))
|
||||
exwm-state)))
|
||||
|
||||
(defun exwm-layout--set-ewmh-state (xwin)
|
||||
"Set _NET_WM_STATE."
|
||||
(with-current-buffer (exwm--id->buffer xwin)
|
||||
(xcb:+request exwm--connection
|
||||
(make-instance 'xcb:ewmh:set-_NET_WM_STATE
|
||||
:window exwm--id
|
||||
:data exwm--ewmh-state))))
|
||||
|
||||
(defun exwm-layout--fullscreen-p ()
|
||||
(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)
|
||||
(let* ((edges (window-inside-absolute-pixel-edges window))
|
||||
(x (pop edges))
|
||||
(y (pop edges))
|
||||
(width (- (pop edges) x))
|
||||
(height (- (pop edges) y))
|
||||
frame-x frame-y frame-width frame-height)
|
||||
(with-current-buffer (exwm--id->buffer id)
|
||||
(when exwm--floating-frame
|
||||
(setq frame-width (frame-pixel-width exwm--floating-frame)
|
||||
frame-height (+ (frame-pixel-height exwm--floating-frame)
|
||||
;; Use `frame-outer-height' in the future.
|
||||
exwm-workspace--frame-y-offset))
|
||||
(when exwm--floating-frame-position
|
||||
(setq frame-x (elt exwm--floating-frame-position 0)
|
||||
frame-y (elt exwm--floating-frame-position 1)
|
||||
x (+ x frame-x (- exwm-layout--floating-hidden-position))
|
||||
y (+ y frame-y (- exwm-layout--floating-hidden-position)))
|
||||
(setq exwm--floating-frame-position nil))
|
||||
(exwm--set-geometry (frame-parameter exwm--floating-frame
|
||||
'exwm-container)
|
||||
frame-x frame-y frame-width frame-height))
|
||||
(when (exwm-layout--fullscreen-p)
|
||||
(with-slots ((x* x)
|
||||
(y* y)
|
||||
(width* width)
|
||||
(height* height))
|
||||
(exwm-workspace--get-geometry exwm--frame)
|
||||
(setq x x*
|
||||
y y*
|
||||
width width*
|
||||
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)
|
||||
(setq exwm--ewmh-state
|
||||
(delq xcb:Atom:_NET_WM_STATE_HIDDEN exwm--ewmh-state))
|
||||
(exwm-layout--set-ewmh-state id)
|
||||
(exwm-layout--auto-iconify)))
|
||||
(xcb:flush exwm--connection))
|
||||
|
||||
(defun exwm-layout--hide (id)
|
||||
"Hide window ID."
|
||||
(with-current-buffer (exwm--id->buffer id)
|
||||
(unless (or (exwm-layout--iconic-state-p)
|
||||
(and exwm--floating-frame
|
||||
(eq 4294967295. exwm--desktop)))
|
||||
(exwm--log "Hide #x%x" id)
|
||||
(when exwm--floating-frame
|
||||
(let* ((container (frame-parameter exwm--floating-frame
|
||||
'exwm-container))
|
||||
(geometry (xcb:+request-unchecked+reply exwm--connection
|
||||
(make-instance 'xcb:GetGeometry
|
||||
:drawable container))))
|
||||
(setq exwm--floating-frame-position
|
||||
(vector (slot-value geometry 'x) (slot-value geometry 'y)))
|
||||
(exwm--set-geometry container exwm-layout--floating-hidden-position
|
||||
exwm-layout--floating-hidden-position
|
||||
1
|
||||
1)))
|
||||
(xcb:+request exwm--connection
|
||||
(make-instance 'xcb:ChangeWindowAttributes
|
||||
:window id :value-mask xcb:CW:EventMask
|
||||
:event-mask xcb:EventMask:NoEvent))
|
||||
(xcb:+request exwm--connection
|
||||
(make-instance 'xcb:UnmapWindow :window id))
|
||||
(xcb:+request exwm--connection
|
||||
(make-instance 'xcb:ChangeWindowAttributes
|
||||
:window id :value-mask xcb:CW:EventMask
|
||||
:event-mask (exwm--get-client-event-mask)))
|
||||
(exwm-layout--set-state id xcb:icccm:WM_STATE:IconicState)
|
||||
(cl-pushnew xcb:Atom:_NET_WM_STATE_HIDDEN exwm--ewmh-state)
|
||||
(exwm-layout--set-ewmh-state id)
|
||||
(exwm-layout--auto-iconify)
|
||||
(xcb:flush exwm--connection))))
|
||||
|
||||
;;;###autoload
|
||||
(cl-defun exwm-layout-set-fullscreen (&optional id)
|
||||
"Make window ID fullscreen."
|
||||
(interactive)
|
||||
(exwm--log "id=#x%x" (or id 0))
|
||||
(unless (and (or id (derived-mode-p 'exwm-mode))
|
||||
(not (exwm-layout--fullscreen-p)))
|
||||
(cl-return-from exwm-layout-set-fullscreen))
|
||||
(with-current-buffer (if id (exwm--id->buffer id) (window-buffer))
|
||||
;; Expand the X window to fill the whole screen.
|
||||
(with-slots (x y width height) (exwm-workspace--get-geometry exwm--frame)
|
||||
(exwm--set-geometry exwm--id x y width height))
|
||||
;; Raise the X window.
|
||||
(xcb:+request exwm--connection
|
||||
(make-instance 'xcb:ConfigureWindow
|
||||
:window exwm--id
|
||||
:value-mask (logior xcb:ConfigWindow:BorderWidth
|
||||
xcb:ConfigWindow:StackMode)
|
||||
:border-width 0
|
||||
:stack-mode xcb:StackMode:Above))
|
||||
(cl-pushnew xcb:Atom:_NET_WM_STATE_FULLSCREEN exwm--ewmh-state)
|
||||
(exwm-layout--set-ewmh-state exwm--id)
|
||||
(xcb:flush exwm--connection)
|
||||
(set-window-dedicated-p (get-buffer-window) t)
|
||||
(exwm-input--release-keyboard exwm--id)))
|
||||
|
||||
;;;###autoload
|
||||
(cl-defun exwm-layout-unset-fullscreen (&optional id)
|
||||
"Restore window from fullscreen state."
|
||||
(interactive)
|
||||
(exwm--log "id=#x%x" (or id 0))
|
||||
(unless (and (or id (derived-mode-p 'exwm-mode))
|
||||
(exwm-layout--fullscreen-p))
|
||||
(cl-return-from exwm-layout-unset-fullscreen))
|
||||
(with-current-buffer (if id (exwm--id->buffer id) (window-buffer))
|
||||
(setq exwm--ewmh-state
|
||||
(delq xcb:Atom:_NET_WM_STATE_FULLSCREEN exwm--ewmh-state))
|
||||
(if exwm--floating-frame
|
||||
(exwm-layout--show exwm--id (frame-root-window exwm--floating-frame))
|
||||
(xcb:+request exwm--connection
|
||||
(make-instance 'xcb:ConfigureWindow
|
||||
:window exwm--id
|
||||
:value-mask (logior xcb:ConfigWindow:Sibling
|
||||
xcb:ConfigWindow:StackMode)
|
||||
:sibling exwm--guide-window
|
||||
:stack-mode xcb:StackMode:Above))
|
||||
(let ((window (get-buffer-window nil t)))
|
||||
(when window
|
||||
(exwm-layout--show exwm--id window))))
|
||||
(setq exwm--ewmh-state
|
||||
(delq xcb:Atom:_NET_WM_STATE_FULLSCREEN exwm--ewmh-state))
|
||||
(exwm-layout--set-ewmh-state exwm--id)
|
||||
(xcb:flush exwm--connection)
|
||||
(set-window-dedicated-p (get-buffer-window) nil)
|
||||
(when (eq 'line-mode exwm--selected-input-mode)
|
||||
(exwm-input--grab-keyboard exwm--id))))
|
||||
|
||||
;;;###autoload
|
||||
(cl-defun exwm-layout-toggle-fullscreen (&optional id)
|
||||
"Toggle fullscreen mode."
|
||||
(interactive (list (exwm--buffer->id (window-buffer))))
|
||||
(exwm--log "id=#x%x" (or id 0))
|
||||
(unless (or id (derived-mode-p 'exwm-mode))
|
||||
(cl-return-from exwm-layout-toggle-fullscreen))
|
||||
(when id
|
||||
(with-current-buffer (exwm--id->buffer id)
|
||||
(if (exwm-layout--fullscreen-p)
|
||||
(exwm-layout-unset-fullscreen id)
|
||||
(exwm-layout-set-fullscreen id)))))
|
||||
|
||||
(defun exwm-layout--other-buffer-predicate (buffer)
|
||||
"Return non-nil when the BUFFER may be displayed in selected frame.
|
||||
|
||||
Prevents EXWM-mode buffers already being displayed on some other window from
|
||||
being selected.
|
||||
|
||||
Should be set as `buffer-predicate' frame parameter for all
|
||||
frames. Used by `other-buffer'.
|
||||
|
||||
When variable `exwm-layout--other-buffer-exclude-exwm-mode-buffers'
|
||||
is t EXWM buffers are never selected by `other-buffer'.
|
||||
|
||||
When variable `exwm-layout--other-buffer-exclude-buffers' is a
|
||||
list of buffers, EXWM buffers belonging to that list are never
|
||||
selected by `other-buffer'."
|
||||
(or (not (with-current-buffer buffer (derived-mode-p 'exwm-mode)))
|
||||
(and (not exwm-layout--other-buffer-exclude-exwm-mode-buffers)
|
||||
(not (memq buffer exwm-layout--other-buffer-exclude-buffers))
|
||||
;; Do not select if already shown in some window.
|
||||
(not (get-buffer-window buffer t)))))
|
||||
|
||||
(defun exwm-layout--set-client-list-stacking ()
|
||||
"Set _NET_CLIENT_LIST_STACKING."
|
||||
(exwm--log)
|
||||
(let (id clients-floating clients clients-iconic clients-other)
|
||||
(dolist (pair exwm--id-buffer-alist)
|
||||
(setq id (car pair))
|
||||
(with-current-buffer (cdr pair)
|
||||
(if (eq exwm--frame exwm-workspace--current)
|
||||
(if exwm--floating-frame
|
||||
;; A floating X window on the current workspace.
|
||||
(setq clients-floating (cons id clients-floating))
|
||||
(if (get-buffer-window (cdr pair) exwm-workspace--current)
|
||||
;; A normal tilling X window on the current workspace.
|
||||
(setq clients (cons id clients))
|
||||
;; An iconic tilling X window on the current workspace.
|
||||
(setq clients-iconic (cons id clients-iconic))))
|
||||
;; X window on other workspaces.
|
||||
(setq clients-other (cons id clients-other)))))
|
||||
(xcb:+request exwm--connection
|
||||
(make-instance 'xcb:ewmh:set-_NET_CLIENT_LIST_STACKING
|
||||
:window exwm--root
|
||||
:data (vconcat (append clients-other clients-iconic
|
||||
clients clients-floating))))))
|
||||
|
||||
(defun exwm-layout--refresh (&optional frame)
|
||||
"Refresh layout."
|
||||
;; `window-size-change-functions' sets this argument while
|
||||
;; `window-configuration-change-hook' makes the frame selected.
|
||||
(unless frame
|
||||
(setq frame (selected-frame)))
|
||||
(exwm--log "frame=%s" frame)
|
||||
(if (not (exwm-workspace--workspace-p frame))
|
||||
(if (frame-parameter frame 'exwm-outer-id)
|
||||
(exwm-layout--refresh-floating frame)
|
||||
(exwm-layout--refresh-other frame))
|
||||
(exwm-layout--refresh-workspace frame)))
|
||||
|
||||
(defun exwm-layout--refresh-floating (frame)
|
||||
"Refresh floating frame FRAME."
|
||||
(exwm--log "Refresh floating %s" frame)
|
||||
(let ((window (frame-first-window frame)))
|
||||
(with-current-buffer (window-buffer window)
|
||||
(when (and (derived-mode-p 'exwm-mode)
|
||||
;; It may be a buffer waiting to be killed.
|
||||
(exwm--id->buffer exwm--id))
|
||||
(exwm--log "Refresh floating window #x%x" exwm--id)
|
||||
(if (exwm-workspace--active-p exwm--frame)
|
||||
(exwm-layout--show exwm--id window)
|
||||
(exwm-layout--hide exwm--id))))))
|
||||
|
||||
(defun exwm-layout--refresh-other (frame)
|
||||
"Refresh client or nox frame FRAME."
|
||||
;; Other frames (e.g. terminal/graphical frame of emacsclient)
|
||||
;; We shall bury all `exwm-mode' buffers in this case
|
||||
(exwm--log "Refresh other %s" frame)
|
||||
(let ((windows (window-list frame 'nomini)) ;exclude minibuffer
|
||||
(exwm-layout--other-buffer-exclude-exwm-mode-buffers t))
|
||||
(dolist (window windows)
|
||||
(with-current-buffer (window-buffer window)
|
||||
(when (derived-mode-p 'exwm-mode)
|
||||
(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."
|
||||
(exwm--log "Refresh workspace %s" frame)
|
||||
;; Workspaces other than the active one can also be refreshed (RandR)
|
||||
(let (covered-buffers ;EXWM-buffers covered by a new X window.
|
||||
vacated-windows) ;Windows previously displaying EXWM-buffers.
|
||||
(dolist (pair exwm--id-buffer-alist)
|
||||
(with-current-buffer (cdr pair)
|
||||
(when (and (not exwm--floating-frame) ;exclude floating X windows
|
||||
(or exwm-layout-show-all-buffers
|
||||
;; Exclude X windows on other workspaces
|
||||
(eq frame exwm--frame)))
|
||||
(let (;; List of windows in current frame displaying the `exwm-mode'
|
||||
;; buffers.
|
||||
(windows (get-buffer-window-list (current-buffer) 'nomini
|
||||
frame)))
|
||||
(if (not windows)
|
||||
(when (eq frame exwm--frame)
|
||||
;; Hide it if it was being shown in this workspace.
|
||||
(exwm-layout--hide exwm--id))
|
||||
(let ((window (car windows)))
|
||||
(if (eq frame exwm--frame)
|
||||
;; Show it if `frame' is active, hide otherwise.
|
||||
(if (exwm-workspace--active-p frame)
|
||||
(exwm-layout--show exwm--id window)
|
||||
(exwm-layout--hide exwm--id))
|
||||
;; It was last shown in other workspace; move it here.
|
||||
(exwm-workspace-move-window frame exwm--id))
|
||||
;; Vacate any other windows (in any workspace) showing this
|
||||
;; `exwm-mode' buffer.
|
||||
(setq vacated-windows
|
||||
(append vacated-windows (remove
|
||||
window
|
||||
(get-buffer-window-list
|
||||
(current-buffer) 'nomini t))))
|
||||
;; Note any `exwm-mode' buffer is being covered by another
|
||||
;; `exwm-mode' buffer. We want to avoid that `exwm-mode'
|
||||
;; buffer to be reappear in any of the vacated windows.
|
||||
(let ((prev-buffer (car-safe
|
||||
(car-safe (window-prev-buffers window)))))
|
||||
(and
|
||||
prev-buffer
|
||||
(with-current-buffer prev-buffer
|
||||
(derived-mode-p 'exwm-mode))
|
||||
(push prev-buffer covered-buffers)))))))))
|
||||
;; Set some sensible buffer to vacated windows.
|
||||
(let ((exwm-layout--other-buffer-exclude-buffers covered-buffers))
|
||||
(dolist (window vacated-windows)
|
||||
(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))))
|
||||
(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)))
|
||||
|
||||
(defun exwm-layout--on-minibuffer-setup ()
|
||||
"Refresh layout when minibuffer grows."
|
||||
(exwm--log)
|
||||
(unless (exwm-workspace--client-p)
|
||||
(exwm--defer 0 (lambda ()
|
||||
(when (< 1 (window-height (minibuffer-window)))
|
||||
(exwm-layout--refresh))))))
|
||||
|
||||
(defun exwm-layout--on-echo-area-change (&optional dirty)
|
||||
"Run when message arrives or in `echo-area-clear-hook' to refresh layout."
|
||||
(when (and (current-message)
|
||||
(not (exwm-workspace--client-p))
|
||||
(or (cl-position ?\n (current-message))
|
||||
(> (length (current-message))
|
||||
(frame-width exwm-workspace--current))))
|
||||
(exwm--log)
|
||||
(if dirty
|
||||
(exwm-layout--refresh)
|
||||
(exwm--defer 0 #'exwm-layout--refresh))))
|
||||
|
||||
;;;###autoload
|
||||
(defun exwm-layout-enlarge-window (delta &optional horizontal)
|
||||
"Make the selected window DELTA pixels taller.
|
||||
|
||||
If no argument is given, make the selected window one pixel taller. If the
|
||||
optional argument HORIZONTAL is non-nil, make selected window DELTA pixels
|
||||
wider. If DELTA is negative, shrink selected window by -DELTA pixels.
|
||||
|
||||
Normal hints are checked and regarded if the selected window is displaying an
|
||||
`exwm-mode' buffer. However, this may violate the normal hints set on other X
|
||||
windows."
|
||||
(interactive "p")
|
||||
(exwm--log)
|
||||
(cond
|
||||
((zerop delta)) ;no operation
|
||||
((window-minibuffer-p)) ;avoid resize minibuffer-window
|
||||
((not (and (derived-mode-p 'exwm-mode) exwm--floating-frame))
|
||||
;; Resize on tiling layout
|
||||
(unless (= 0 (window-resizable nil delta horizontal nil t)) ;not resizable
|
||||
(let ((window-resize-pixelwise t))
|
||||
(window-resize nil delta horizontal nil t))))
|
||||
;; Resize on floating layout
|
||||
(exwm--fixed-size) ;fixed size
|
||||
(horizontal
|
||||
(let* ((width (frame-pixel-width))
|
||||
(edges (window-inside-pixel-edges))
|
||||
(inner-width (- (elt edges 2) (elt edges 0)))
|
||||
(margin (- width inner-width)))
|
||||
(if (> delta 0)
|
||||
(if (not exwm--normal-hints-max-width)
|
||||
(cl-incf width delta)
|
||||
(if (>= inner-width exwm--normal-hints-max-width)
|
||||
(setq width nil)
|
||||
(setq width (min (+ exwm--normal-hints-max-width margin)
|
||||
(+ width delta)))))
|
||||
(if (not exwm--normal-hints-min-width)
|
||||
(cl-incf width delta)
|
||||
(if (<= inner-width exwm--normal-hints-min-width)
|
||||
(setq width nil)
|
||||
(setq width (max (+ exwm--normal-hints-min-width margin)
|
||||
(+ width delta))))))
|
||||
(when (and width (> width 0))
|
||||
(setf (slot-value exwm--geometry 'width) width)
|
||||
(xcb:+request exwm--connection
|
||||
(make-instance 'xcb:ConfigureWindow
|
||||
:window (frame-parameter exwm--floating-frame
|
||||
'exwm-outer-id)
|
||||
:value-mask xcb:ConfigWindow:Width
|
||||
:width width))
|
||||
(xcb:+request exwm--connection
|
||||
(make-instance 'xcb:ConfigureWindow
|
||||
:window (frame-parameter exwm--floating-frame
|
||||
'exwm-container)
|
||||
:value-mask xcb:ConfigWindow:Width
|
||||
:width width))
|
||||
(xcb:flush exwm--connection))))
|
||||
(t
|
||||
(let* ((height (+ (frame-pixel-height) exwm-workspace--frame-y-offset))
|
||||
(edges (window-inside-pixel-edges))
|
||||
(inner-height (- (elt edges 3) (elt edges 1)))
|
||||
(margin (- height inner-height)))
|
||||
(if (> delta 0)
|
||||
(if (not exwm--normal-hints-max-height)
|
||||
(cl-incf height delta)
|
||||
(if (>= inner-height exwm--normal-hints-max-height)
|
||||
(setq height nil)
|
||||
(setq height (min (+ exwm--normal-hints-max-height margin)
|
||||
(+ height delta)))))
|
||||
(if (not exwm--normal-hints-min-height)
|
||||
(cl-incf height delta)
|
||||
(if (<= inner-height exwm--normal-hints-min-height)
|
||||
(setq height nil)
|
||||
(setq height (max (+ exwm--normal-hints-min-height margin)
|
||||
(+ height delta))))))
|
||||
(when (and height (> height 0))
|
||||
(setf (slot-value exwm--geometry 'height) height)
|
||||
(xcb:+request exwm--connection
|
||||
(make-instance 'xcb:ConfigureWindow
|
||||
:window (frame-parameter exwm--floating-frame
|
||||
'exwm-outer-id)
|
||||
:value-mask xcb:ConfigWindow:Height
|
||||
:height height))
|
||||
(xcb:+request exwm--connection
|
||||
(make-instance 'xcb:ConfigureWindow
|
||||
:window (frame-parameter exwm--floating-frame
|
||||
'exwm-container)
|
||||
:value-mask xcb:ConfigWindow:Height
|
||||
:height height))
|
||||
(xcb:flush exwm--connection))))))
|
||||
|
||||
;;;###autoload
|
||||
(defun exwm-layout-enlarge-window-horizontally (delta)
|
||||
"Make the selected window DELTA pixels wider.
|
||||
|
||||
See also `exwm-layout-enlarge-window'."
|
||||
(interactive "p")
|
||||
(exwm--log "%s" delta)
|
||||
(exwm-layout-enlarge-window delta t))
|
||||
|
||||
;;;###autoload
|
||||
(defun exwm-layout-shrink-window (delta)
|
||||
"Make the selected window DELTA pixels lower.
|
||||
|
||||
See also `exwm-layout-enlarge-window'."
|
||||
(interactive "p")
|
||||
(exwm--log "%s" delta)
|
||||
(exwm-layout-enlarge-window (- delta)))
|
||||
|
||||
;;;###autoload
|
||||
(defun exwm-layout-shrink-window-horizontally (delta)
|
||||
"Make the selected window DELTA pixels narrower.
|
||||
|
||||
See also `exwm-layout-enlarge-window'."
|
||||
(interactive "p")
|
||||
(exwm--log "%s" delta)
|
||||
(exwm-layout-enlarge-window (- delta) t))
|
||||
|
||||
;;;###autoload
|
||||
(defun exwm-layout-hide-mode-line ()
|
||||
"Hide mode-line."
|
||||
(interactive)
|
||||
(exwm--log)
|
||||
(when (and (derived-mode-p 'exwm-mode) mode-line-format)
|
||||
(let (mode-line-height)
|
||||
(when exwm--floating-frame
|
||||
(setq mode-line-height (window-mode-line-height
|
||||
(frame-root-window exwm--floating-frame))))
|
||||
(setq exwm--mode-line-format mode-line-format
|
||||
mode-line-format nil)
|
||||
(if (not exwm--floating-frame)
|
||||
(exwm-layout--show exwm--id)
|
||||
(set-frame-height exwm--floating-frame
|
||||
(- (frame-pixel-height exwm--floating-frame)
|
||||
mode-line-height)
|
||||
nil t)))))
|
||||
|
||||
;;;###autoload
|
||||
(defun exwm-layout-show-mode-line ()
|
||||
"Show mode-line."
|
||||
(interactive)
|
||||
(exwm--log)
|
||||
(when (and (derived-mode-p 'exwm-mode) (not mode-line-format))
|
||||
(setq mode-line-format exwm--mode-line-format
|
||||
exwm--mode-line-format nil)
|
||||
(if (not exwm--floating-frame)
|
||||
(exwm-layout--show exwm--id)
|
||||
(set-frame-height exwm--floating-frame
|
||||
(+ (frame-pixel-height exwm--floating-frame)
|
||||
(window-mode-line-height (frame-root-window
|
||||
exwm--floating-frame)))
|
||||
nil t)
|
||||
(call-interactively #'exwm-input-grab-keyboard))
|
||||
(force-mode-line-update)))
|
||||
|
||||
;;;###autoload
|
||||
(defun exwm-layout-toggle-mode-line ()
|
||||
"Toggle the display of mode-line."
|
||||
(interactive)
|
||||
(exwm--log)
|
||||
(when (derived-mode-p 'exwm-mode)
|
||||
(if mode-line-format
|
||||
(exwm-layout-hide-mode-line)
|
||||
(exwm-layout-show-mode-line))))
|
||||
|
||||
(defun exwm-layout--init ()
|
||||
"Initialize layout module."
|
||||
;; Auto refresh layout
|
||||
(exwm--log)
|
||||
(add-hook 'window-configuration-change-hook #'exwm-layout--refresh)
|
||||
;; The behavior of `window-configuration-change-hook' will be changed.
|
||||
(when (fboundp 'window-pixel-width-before-size-change)
|
||||
(add-hook 'window-size-change-functions #'exwm-layout--refresh))
|
||||
(unless (exwm-workspace--minibuffer-own-frame-p)
|
||||
;; Refresh when minibuffer grows
|
||||
(add-hook 'minibuffer-setup-hook #'exwm-layout--on-minibuffer-setup t)
|
||||
(setq exwm-layout--timer
|
||||
(run-with-idle-timer 0 t #'exwm-layout--on-echo-area-change t))
|
||||
(add-hook 'echo-area-clear-hook #'exwm-layout--on-echo-area-change)))
|
||||
|
||||
(defun exwm-layout--exit ()
|
||||
"Exit the layout module."
|
||||
(exwm--log)
|
||||
(remove-hook 'window-configuration-change-hook #'exwm-layout--refresh)
|
||||
(when (fboundp 'window-pixel-width-before-size-change)
|
||||
(remove-hook 'window-size-change-functions #'exwm-layout--refresh))
|
||||
(remove-hook 'minibuffer-setup-hook #'exwm-layout--on-minibuffer-setup)
|
||||
(when exwm-layout--timer
|
||||
(cancel-timer exwm-layout--timer)
|
||||
(setq exwm-layout--timer nil))
|
||||
(remove-hook 'echo-area-clear-hook #'exwm-layout--on-echo-area-change))
|
||||
|
||||
|
||||
|
||||
(provide 'exwm-layout)
|
||||
|
||||
;;; exwm-layout.el ends here
|
805
third_party/exwm/exwm-manage.el
vendored
Normal file
805
third_party/exwm/exwm-manage.el
vendored
Normal file
|
@ -0,0 +1,805 @@
|
|||
;;; exwm-manage.el --- Window Management Module for -*- lexical-binding: t -*-
|
||||
;;; EXWM
|
||||
|
||||
;; Copyright (C) 2015-2020 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 is the fundamental module of EXWM that deals with window management.
|
||||
|
||||
;;; Code:
|
||||
|
||||
(require 'exwm-core)
|
||||
|
||||
(defgroup exwm-manage nil
|
||||
"Manage."
|
||||
:version "25.3"
|
||||
:group 'exwm)
|
||||
|
||||
(defcustom exwm-manage-finish-hook nil
|
||||
"Normal hook run after a window is just managed, in the context of the
|
||||
corresponding buffer."
|
||||
:type 'hook)
|
||||
|
||||
(defcustom exwm-manage-force-tiling nil
|
||||
"Non-nil to force managing all X windows in tiling layout.
|
||||
You can still make the X windows floating afterwards."
|
||||
:type 'boolean)
|
||||
|
||||
(defcustom exwm-manage-ping-timeout 3
|
||||
"Seconds to wait before killing a client."
|
||||
:type 'integer)
|
||||
|
||||
(defcustom exwm-manage-configurations nil
|
||||
"Per-application configurations.
|
||||
|
||||
Configuration options allow to override various default behaviors of EXWM
|
||||
and only take effect when they are present. Note for certain options
|
||||
specifying nil is not exactly the same as leaving them out. Currently
|
||||
possible choices:
|
||||
* floating: Force floating (non-nil) or tiling (nil) on startup.
|
||||
* x/y/width/height: Override the initial geometry (floating X window only).
|
||||
* border-width: Override the border width (only visible when floating).
|
||||
* fullscreen: Force full screen (non-nil) on startup.
|
||||
* floating-mode-line: `mode-line-format' used when floating.
|
||||
* tiling-mode-line: `mode-line-format' used when tiling.
|
||||
* floating-header-line: `header-line-format' used when floating.
|
||||
* tiling-header-line: `header-line-format' used when tiling.
|
||||
* char-mode: Force char-mode (non-nil) on startup.
|
||||
* prefix-keys: `exwm-input-prefix-keys' local to this X window.
|
||||
* simulation-keys: `exwm-input-simulation-keys' local to this X window.
|
||||
* workspace: The initial workspace.
|
||||
* managed: Force to manage (non-nil) or not manage (nil) the X window.
|
||||
|
||||
For each X window managed for the first time, matching criteria (sexps) are
|
||||
evaluated sequentially and the first configuration with a non-nil matching
|
||||
criterion would be applied. Apart from generic forms, one would typically
|
||||
want to match against EXWM internal variables such as `exwm-title',
|
||||
`exwm-class-name' and `exwm-instance-name'."
|
||||
:type '(alist :key-type (sexp :tag "Matching criterion" nil)
|
||||
:value-type
|
||||
(plist :tag "Configurations"
|
||||
:options
|
||||
(((const :tag "Floating" floating) boolean)
|
||||
((const :tag "X" x) number)
|
||||
((const :tag "Y" y) number)
|
||||
((const :tag "Width" width) number)
|
||||
((const :tag "Height" height) number)
|
||||
((const :tag "Border width" border-width) integer)
|
||||
((const :tag "Fullscreen" fullscreen) boolean)
|
||||
((const :tag "Floating mode-line" floating-mode-line)
|
||||
sexp)
|
||||
((const :tag "Tiling mode-line" tiling-mode-line) sexp)
|
||||
((const :tag "Floating header-line"
|
||||
floating-header-line)
|
||||
sexp)
|
||||
((const :tag "Tiling header-line" tiling-header-line)
|
||||
sexp)
|
||||
((const :tag "Char-mode" char-mode) boolean)
|
||||
((const :tag "Prefix keys" prefix-keys)
|
||||
(repeat key-sequence))
|
||||
((const :tag "Simulation keys" simulation-keys)
|
||||
(alist :key-type (key-sequence :tag "From")
|
||||
:value-type (key-sequence :tag "To")))
|
||||
((const :tag "Workspace" workspace) integer)
|
||||
((const :tag "Managed" managed) boolean)
|
||||
;; For forward compatibility.
|
||||
((other) sexp))))
|
||||
;; TODO: This is admittedly ugly. We'd be better off with an event type.
|
||||
:get (lambda (symbol)
|
||||
(mapcar (lambda (pair)
|
||||
(let* ((match (car pair))
|
||||
(config (cdr pair))
|
||||
(prefix-keys (plist-get config 'prefix-keys)))
|
||||
(when prefix-keys
|
||||
(setq config (copy-tree config)
|
||||
config (plist-put config 'prefix-keys
|
||||
(mapcar (lambda (i)
|
||||
(if (sequencep i)
|
||||
i
|
||||
(vector i)))
|
||||
prefix-keys))))
|
||||
(cons match config)))
|
||||
(default-value symbol)))
|
||||
:set (lambda (symbol value)
|
||||
(set symbol
|
||||
(mapcar (lambda (pair)
|
||||
(let* ((match (car pair))
|
||||
(config (cdr pair))
|
||||
(prefix-keys (plist-get config 'prefix-keys)))
|
||||
(when prefix-keys
|
||||
(setq config (copy-tree config)
|
||||
config (plist-put config 'prefix-keys
|
||||
(mapcar (lambda (i)
|
||||
(if (sequencep i)
|
||||
(aref i 0)
|
||||
i))
|
||||
prefix-keys))))
|
||||
(cons match config)))
|
||||
value))))
|
||||
|
||||
;; FIXME: Make the following values as small as possible.
|
||||
(defconst exwm-manage--height-delta-min 5)
|
||||
(defconst exwm-manage--width-delta-min 5)
|
||||
|
||||
;; The _MOTIF_WM_HINTS atom (see <Xm/MwmUtil.h> for more details)
|
||||
;; It's currently only used in 'exwm-manage' module
|
||||
(defvar exwm-manage--_MOTIF_WM_HINTS nil "_MOTIF_WM_HINTS atom.")
|
||||
|
||||
(defvar exwm-manage--desktop nil "The desktop X window.")
|
||||
|
||||
(defvar exwm-manage--frame-outer-id-list nil
|
||||
"List of window-outer-id's of all frames.")
|
||||
|
||||
(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)
|
||||
(defvar exwm-workspace--list)
|
||||
(defvar exwm-workspace--switch-history-outdated)
|
||||
(defvar exwm-workspace--workareas)
|
||||
(defvar exwm-workspace-current-index)
|
||||
(declare-function exwm--update-class "exwm.el" (id &optional force))
|
||||
(declare-function exwm--update-hints "exwm.el" (id &optional force))
|
||||
(declare-function exwm--update-normal-hints "exwm.el" (id &optional force))
|
||||
(declare-function exwm--update-protocols "exwm.el" (id &optional force))
|
||||
(declare-function exwm--update-struts "exwm.el" (id))
|
||||
(declare-function exwm--update-title "exwm.el" (id))
|
||||
(declare-function exwm--update-transient-for "exwm.el" (id &optional force))
|
||||
(declare-function exwm--update-desktop "exwm.el" (id &optional force))
|
||||
(declare-function exwm--update-window-type "exwm.el" (id &optional force))
|
||||
(declare-function exwm-floating--set-floating "exwm-floating.el" (id))
|
||||
(declare-function exwm-floating--unset-floating "exwm-floating.el" (id))
|
||||
(declare-function exwm-input-grab-keyboard "exwm-input.el")
|
||||
(declare-function exwm-input-set-local-simulation-keys "exwm-input.el")
|
||||
(declare-function exwm-layout--fullscreen-p "exwm-layout.el" ())
|
||||
(declare-function exwm-layout--iconic-state-p "exwm-layout.el" (&optional id))
|
||||
(declare-function exwm-workspace--position "exwm-workspace.el" (frame))
|
||||
(declare-function exwm-workspace--set-fullscreen "exwm-workspace.el" (frame))
|
||||
(declare-function exwm-workspace--update-struts "exwm-workspace.el" ())
|
||||
(declare-function exwm-workspace--update-workareas "exwm-workspace.el" ())
|
||||
|
||||
(defun exwm-manage--update-geometry (id &optional force)
|
||||
"Update window geometry."
|
||||
(exwm--log "id=#x%x" id)
|
||||
(with-current-buffer (exwm--id->buffer id)
|
||||
(unless (and exwm--geometry (not force))
|
||||
(let ((reply (xcb:+request-unchecked+reply exwm--connection
|
||||
(make-instance 'xcb:GetGeometry :drawable id))))
|
||||
(setq exwm--geometry
|
||||
(or reply
|
||||
;; Provide a reasonable fallback value.
|
||||
(make-instance 'xcb:RECTANGLE
|
||||
:x 0
|
||||
:y 0
|
||||
:width (/ (x-display-pixel-width) 2)
|
||||
:height (/ (x-display-pixel-height) 2))))))))
|
||||
|
||||
(defun exwm-manage--update-ewmh-state (id)
|
||||
"Update _NET_WM_STATE."
|
||||
(exwm--log "id=#x%x" id)
|
||||
(with-current-buffer (exwm--id->buffer id)
|
||||
(unless exwm--ewmh-state
|
||||
(let ((reply (xcb:+request-unchecked+reply exwm--connection
|
||||
(make-instance 'xcb:ewmh:get-_NET_WM_STATE
|
||||
:window id))))
|
||||
(when reply
|
||||
(setq exwm--ewmh-state (append (slot-value reply 'value) nil)))))))
|
||||
|
||||
(defun exwm-manage--update-mwm-hints (id &optional force)
|
||||
"Update _MOTIF_WM_HINTS."
|
||||
(exwm--log "id=#x%x" id)
|
||||
(with-current-buffer (exwm--id->buffer id)
|
||||
(unless (and (not exwm--mwm-hints-decorations) (not force))
|
||||
(let ((reply (xcb:+request-unchecked+reply exwm--connection
|
||||
(make-instance 'xcb:icccm:-GetProperty
|
||||
:window id
|
||||
:property exwm-manage--_MOTIF_WM_HINTS
|
||||
:type exwm-manage--_MOTIF_WM_HINTS
|
||||
:long-length 5))))
|
||||
(when reply
|
||||
;; Check MotifWmHints.decorations.
|
||||
(with-slots (value) reply
|
||||
(setq value (append value nil))
|
||||
(when (and value
|
||||
;; See <Xm/MwmUtil.h> for fields definitions.
|
||||
(/= 0 (logand
|
||||
(elt value 0) ;MotifWmHints.flags
|
||||
2)) ;MWM_HINTS_DECORATIONS
|
||||
(= 0
|
||||
(elt value 2))) ;MotifWmHints.decorations
|
||||
(setq exwm--mwm-hints-decorations nil))))))))
|
||||
|
||||
(defun exwm-manage--set-client-list ()
|
||||
"Set _NET_CLIENT_LIST."
|
||||
(exwm--log)
|
||||
(xcb:+request exwm--connection
|
||||
(make-instance 'xcb:ewmh:set-_NET_CLIENT_LIST
|
||||
:window exwm--root
|
||||
:data (vconcat (mapcar #'car exwm--id-buffer-alist)))))
|
||||
|
||||
(cl-defun exwm-manage--get-configurations ()
|
||||
"Retrieve configurations for this buffer."
|
||||
(exwm--log)
|
||||
(when (derived-mode-p 'exwm-mode)
|
||||
(dolist (i exwm-manage-configurations)
|
||||
(save-current-buffer
|
||||
(when (with-demoted-errors "Problematic configuration: %S"
|
||||
(eval (car i) t))
|
||||
(cl-return-from exwm-manage--get-configurations (cdr i)))))))
|
||||
|
||||
(defun exwm-manage--manage-window (id)
|
||||
"Manage window ID."
|
||||
(exwm--log "Try to manage #x%x" id)
|
||||
(catch 'return
|
||||
;; Ensure it's alive
|
||||
(when (xcb:+request-checked+request-check exwm--connection
|
||||
(make-instance 'xcb:ChangeWindowAttributes
|
||||
:window id :value-mask xcb:CW:EventMask
|
||||
:event-mask (exwm--get-client-event-mask)))
|
||||
(throw 'return 'dead))
|
||||
;; Add this X window to save-set.
|
||||
(xcb:+request exwm--connection
|
||||
(make-instance 'xcb:ChangeSaveSet
|
||||
:mode xcb:SetMode:Insert
|
||||
:window id))
|
||||
(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)))))
|
||||
(exwm-mode)
|
||||
(setq exwm--id id
|
||||
exwm--frame exwm-workspace--current)
|
||||
(exwm--update-window-type id)
|
||||
(exwm--update-class id)
|
||||
(exwm--update-transient-for id)
|
||||
(exwm--update-normal-hints id)
|
||||
(exwm--update-hints id)
|
||||
(exwm-manage--update-geometry id)
|
||||
(exwm-manage--update-mwm-hints id)
|
||||
(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)
|
||||
(exwm--update-struts id))
|
||||
;; Remove all events
|
||||
(xcb:+request exwm--connection
|
||||
(make-instance 'xcb:ChangeWindowAttributes
|
||||
:window id :value-mask xcb:CW:EventMask
|
||||
:event-mask
|
||||
(if (memq xcb:Atom:_NET_WM_WINDOW_TYPE_DOCK
|
||||
exwm-window-type)
|
||||
;; Listen for PropertyChange (struts) and
|
||||
;; UnmapNotify/DestroyNotify event of the dock.
|
||||
(exwm--get-client-event-mask)
|
||||
xcb:EventMask:NoEvent)))
|
||||
;; The window needs to be mapped
|
||||
(xcb:+request exwm--connection
|
||||
(make-instance 'xcb:MapWindow :window id))
|
||||
(with-slots (x y width height) exwm--geometry
|
||||
;; Center window of type _NET_WM_WINDOW_TYPE_SPLASH
|
||||
(when (memq xcb:Atom:_NET_WM_WINDOW_TYPE_SPLASH exwm-window-type)
|
||||
(let* ((workarea (elt exwm-workspace--workareas
|
||||
(exwm-workspace--position exwm--frame)))
|
||||
(x* (aref workarea 0))
|
||||
(y* (aref workarea 1))
|
||||
(width* (aref workarea 2))
|
||||
(height* (aref workarea 3)))
|
||||
(exwm--set-geometry id
|
||||
(+ x* (/ (- width* width) 2))
|
||||
(+ y* (/ (- height* height) 2))
|
||||
nil
|
||||
nil))))
|
||||
;; Check for desktop.
|
||||
(when (memq xcb:Atom:_NET_WM_WINDOW_TYPE_DESKTOP exwm-window-type)
|
||||
;; There should be only one desktop X window.
|
||||
(setq exwm-manage--desktop id)
|
||||
;; Put it at bottom.
|
||||
(xcb:+request exwm--connection
|
||||
(make-instance 'xcb:ConfigureWindow
|
||||
:window id
|
||||
:value-mask xcb:ConfigWindow:StackMode
|
||||
: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)
|
||||
(exwm-input--skip-buffer-list-update t))
|
||||
(kill-buffer (current-buffer)))
|
||||
(throw 'return 'ignored))
|
||||
(let ((index (plist-get exwm--configurations 'workspace)))
|
||||
(when (and index (< index (length exwm-workspace--list)))
|
||||
(setq exwm--frame (elt exwm-workspace--list index))))
|
||||
;; Manage the window
|
||||
(exwm--log "Manage #x%x" id)
|
||||
(xcb:+request exwm--connection ;remove border
|
||||
(make-instance 'xcb:ConfigureWindow
|
||||
:window id :value-mask xcb:ConfigWindow:BorderWidth
|
||||
:border-width 0))
|
||||
(dolist (button ;grab buttons to set focus / move / resize
|
||||
(list xcb:ButtonIndex:1 xcb:ButtonIndex:2 xcb:ButtonIndex:3))
|
||||
(xcb:+request exwm--connection
|
||||
(make-instance 'xcb:GrabButton
|
||||
:owner-events 0 :grab-window id
|
||||
:event-mask xcb:EventMask:ButtonPress
|
||||
:pointer-mode xcb:GrabMode:Sync
|
||||
:keyboard-mode xcb:GrabMode:Async
|
||||
:confine-to xcb:Window:None :cursor xcb:Cursor:None
|
||||
:button button :modifiers xcb:ModMask:Any)))
|
||||
(exwm-manage--set-client-list)
|
||||
(xcb:flush exwm--connection)
|
||||
(if (plist-member exwm--configurations 'floating)
|
||||
;; User has specified whether it should be floating.
|
||||
(if (plist-get exwm--configurations 'floating)
|
||||
(exwm-floating--set-floating id)
|
||||
(with-selected-window (frame-selected-window exwm--frame)
|
||||
(exwm-floating--unset-floating id)))
|
||||
;; Try to determine if it should be floating.
|
||||
(if (and (not exwm-manage-force-tiling)
|
||||
(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-floating--set-floating id)
|
||||
(with-selected-window (frame-selected-window exwm--frame)
|
||||
(exwm-floating--unset-floating id))))
|
||||
(if (plist-get exwm--configurations 'char-mode)
|
||||
(exwm-input-release-keyboard id)
|
||||
(exwm-input-grab-keyboard id))
|
||||
(let ((simulation-keys (plist-get exwm--configurations 'simulation-keys))
|
||||
(prefix-keys (plist-get exwm--configurations 'prefix-keys)))
|
||||
(with-current-buffer (exwm--id->buffer id)
|
||||
(when simulation-keys
|
||||
(exwm-input-set-local-simulation-keys simulation-keys))
|
||||
(when prefix-keys
|
||||
(setq-local exwm-input-prefix-keys prefix-keys))))
|
||||
(setq exwm-workspace--switch-history-outdated t)
|
||||
(exwm--update-desktop id)
|
||||
(exwm-manage--update-ewmh-state id)
|
||||
(with-current-buffer (exwm--id->buffer id)
|
||||
(when (or (plist-get exwm--configurations 'fullscreen)
|
||||
(exwm-layout--fullscreen-p))
|
||||
(setq exwm--ewmh-state (delq xcb:Atom:_NET_WM_STATE_FULLSCREEN
|
||||
exwm--ewmh-state))
|
||||
(exwm-layout-set-fullscreen id))
|
||||
(run-hooks 'exwm-manage-finish-hook)))))
|
||||
|
||||
(defun exwm-manage--unmanage-window (id &optional withdraw-only)
|
||||
"Unmanage window ID.
|
||||
|
||||
If WITHDRAW-ONLY is non-nil, the X window will be properly placed back to the
|
||||
root window. Set WITHDRAW-ONLY to 'quit if this functions is used when window
|
||||
manager is shutting down."
|
||||
(let ((buffer (exwm--id->buffer id)))
|
||||
(exwm--log "Unmanage #x%x (buffer: %s, widthdraw: %s)"
|
||||
id buffer withdraw-only)
|
||||
(setq exwm--id-buffer-alist (assq-delete-all id exwm--id-buffer-alist))
|
||||
;; Update workspaces when a dock is destroyed.
|
||||
(when (and (null withdraw-only)
|
||||
(assq id exwm-workspace--id-struts-alist))
|
||||
(setq exwm-workspace--id-struts-alist
|
||||
(assq-delete-all id exwm-workspace--id-struts-alist))
|
||||
(exwm-workspace--update-struts)
|
||||
(exwm-workspace--update-workareas)
|
||||
(dolist (f exwm-workspace--list)
|
||||
(exwm-workspace--set-fullscreen f)))
|
||||
(when (buffer-live-p buffer)
|
||||
(with-current-buffer buffer
|
||||
;; Unmap the X window.
|
||||
(xcb:+request exwm--connection
|
||||
(make-instance 'xcb:UnmapWindow :window id))
|
||||
;;
|
||||
(setq exwm-workspace--switch-history-outdated t)
|
||||
;;
|
||||
(when withdraw-only
|
||||
(xcb:+request exwm--connection
|
||||
(make-instance 'xcb:ChangeWindowAttributes
|
||||
:window id :value-mask xcb:CW:EventMask
|
||||
:event-mask xcb:EventMask:NoEvent))
|
||||
;; Delete WM_STATE property
|
||||
(xcb:+request exwm--connection
|
||||
(make-instance 'xcb:DeleteProperty
|
||||
:window id :property xcb:Atom:WM_STATE))
|
||||
(cond
|
||||
((eq withdraw-only 'quit)
|
||||
;; Remap the window when exiting.
|
||||
(xcb:+request exwm--connection
|
||||
(make-instance 'xcb:MapWindow :window id)))
|
||||
(t
|
||||
;; Remove _NET_WM_DESKTOP.
|
||||
(xcb:+request exwm--connection
|
||||
(make-instance 'xcb:DeleteProperty
|
||||
:window id
|
||||
:property xcb:Atom:_NET_WM_DESKTOP)))))
|
||||
(when exwm--floating-frame
|
||||
;; Unmap the floating frame before destroying its container.
|
||||
(let ((window (frame-parameter exwm--floating-frame 'exwm-outer-id))
|
||||
(container (frame-parameter exwm--floating-frame
|
||||
'exwm-container)))
|
||||
(xcb:+request exwm--connection
|
||||
(make-instance 'xcb:UnmapWindow :window window))
|
||||
(xcb:+request exwm--connection
|
||||
(make-instance 'xcb:ReparentWindow
|
||||
:window window :parent exwm--root :x 0 :y 0))
|
||||
(xcb:+request exwm--connection
|
||||
(make-instance 'xcb:DestroyWindow :window container))))
|
||||
(when (exwm-layout--fullscreen-p)
|
||||
(let ((window (get-buffer-window)))
|
||||
(when window
|
||||
(set-window-dedicated-p window nil))))
|
||||
(exwm-manage--set-client-list)
|
||||
(xcb:flush exwm--connection))
|
||||
(let ((kill-buffer-func
|
||||
(lambda (buffer)
|
||||
(when (buffer-local-value 'exwm--floating-frame buffer)
|
||||
(select-window
|
||||
(frame-selected-window exwm-workspace--current)))
|
||||
(with-current-buffer buffer
|
||||
(let ((kill-buffer-query-functions nil))
|
||||
(kill-buffer buffer))))))
|
||||
(exwm--defer 0 kill-buffer-func buffer)
|
||||
(when (active-minibuffer-window)
|
||||
(exit-minibuffer))))))
|
||||
|
||||
(defun exwm-manage--scan ()
|
||||
"Search for existing windows and try to manage them."
|
||||
(exwm--log)
|
||||
(let* ((tree (xcb:+request-unchecked+reply exwm--connection
|
||||
(make-instance 'xcb:QueryTree
|
||||
:window exwm--root)))
|
||||
reply)
|
||||
(dolist (i (slot-value tree 'children))
|
||||
(setq reply (xcb:+request-unchecked+reply exwm--connection
|
||||
(make-instance 'xcb:GetWindowAttributes
|
||||
:window i)))
|
||||
;; It's possible the X window has been destroyed.
|
||||
(when reply
|
||||
(with-slots (override-redirect map-state) reply
|
||||
(when (and (= 0 override-redirect)
|
||||
(= xcb:MapState:Viewable map-state))
|
||||
(xcb:+request exwm--connection
|
||||
(make-instance 'xcb:UnmapWindow
|
||||
:window i))
|
||||
(xcb:flush exwm--connection)
|
||||
(exwm-manage--manage-window i)))))))
|
||||
|
||||
(defun exwm-manage--kill-buffer-query-function ()
|
||||
"Run in `kill-buffer-query-functions'."
|
||||
(exwm--log "id=#x%x; buffer=%s" exwm--id (current-buffer))
|
||||
(catch 'return
|
||||
(when (or (not exwm--id)
|
||||
(xcb:+request-checked+request-check exwm--connection
|
||||
(make-instance 'xcb:ChangeWindowAttributes
|
||||
:window exwm--id
|
||||
:value-mask xcb:CW:EventMask
|
||||
:event-mask (exwm--get-client-event-mask))))
|
||||
;; The X window is no longer alive so just close the buffer.
|
||||
(when exwm--floating-frame
|
||||
(let ((window (frame-parameter exwm--floating-frame 'exwm-outer-id))
|
||||
(container (frame-parameter exwm--floating-frame
|
||||
'exwm-container)))
|
||||
(xcb:+request exwm--connection
|
||||
(make-instance 'xcb:UnmapWindow :window window))
|
||||
(xcb:+request exwm--connection
|
||||
(make-instance 'xcb:ReparentWindow
|
||||
:window window
|
||||
:parent exwm--root
|
||||
:x 0 :y 0))
|
||||
(xcb:+request exwm--connection
|
||||
(make-instance 'xcb:DestroyWindow
|
||||
:window container))))
|
||||
(xcb:flush exwm--connection)
|
||||
(throw 'return t))
|
||||
(unless (memq xcb:Atom:WM_DELETE_WINDOW exwm--protocols)
|
||||
;; The X window does not support WM_DELETE_WINDOW; destroy it.
|
||||
(xcb:+request exwm--connection
|
||||
(make-instance 'xcb:DestroyWindow :window exwm--id))
|
||||
(xcb:flush exwm--connection)
|
||||
;; Wait for DestroyNotify event.
|
||||
(throw 'return nil))
|
||||
(let ((id exwm--id))
|
||||
;; Try to close the X window with WM_DELETE_WINDOW client message.
|
||||
(xcb:+request exwm--connection
|
||||
(make-instance 'xcb:icccm:SendEvent
|
||||
:destination id
|
||||
:event (xcb:marshal
|
||||
(make-instance 'xcb:icccm:WM_DELETE_WINDOW
|
||||
:window id)
|
||||
exwm--connection)))
|
||||
(xcb:flush exwm--connection)
|
||||
;;
|
||||
(unless (memq xcb:Atom:_NET_WM_PING exwm--protocols)
|
||||
;; For X windows without _NET_WM_PING support, we'd better just
|
||||
;; wait for DestroyNotify events.
|
||||
(throw 'return nil))
|
||||
;; Try to determine if the X window is dead with _NET_WM_PING.
|
||||
(setq exwm-manage--ping-lock t)
|
||||
(xcb:+request exwm--connection
|
||||
(make-instance 'xcb:SendEvent
|
||||
:propagate 0
|
||||
:destination id
|
||||
:event-mask xcb:EventMask:NoEvent
|
||||
:event (xcb:marshal
|
||||
(make-instance 'xcb:ewmh:_NET_WM_PING
|
||||
:window id
|
||||
:timestamp 0
|
||||
:client-window id)
|
||||
exwm--connection)))
|
||||
(xcb:flush exwm--connection)
|
||||
(with-timeout (exwm-manage-ping-timeout
|
||||
(if (y-or-n-p (format "'%s' is not responding. \
|
||||
Would you like to kill it? "
|
||||
(buffer-name)))
|
||||
(progn (exwm-manage--kill-client id)
|
||||
;; Kill the unresponsive X window and
|
||||
;; wait for DestroyNotify event.
|
||||
(throw 'return nil))
|
||||
;; Give up.
|
||||
(throw 'return nil)))
|
||||
(while (and exwm-manage--ping-lock
|
||||
(exwm--id->buffer id)) ;may have been destroyed.
|
||||
(accept-process-output nil 0.1))
|
||||
;; Give up.
|
||||
(throw 'return nil)))))
|
||||
|
||||
(defun exwm-manage--kill-client (&optional id)
|
||||
"Kill an X client."
|
||||
(unless id (setq id (exwm--buffer->id (current-buffer))))
|
||||
(exwm--log "id=#x%x" id)
|
||||
(let* ((response (xcb:+request-unchecked+reply exwm--connection
|
||||
(make-instance 'xcb:ewmh:get-_NET_WM_PID :window id)))
|
||||
(pid (and response (slot-value response 'value)))
|
||||
(request (make-instance 'xcb:KillClient :resource id)))
|
||||
(if (not pid)
|
||||
(xcb:+request exwm--connection request)
|
||||
;; What if the PID is fake/wrong?
|
||||
(signal-process pid 'SIGKILL)
|
||||
;; Ensure it's dead
|
||||
(run-with-timer exwm-manage-ping-timeout nil
|
||||
(lambda ()
|
||||
(xcb:+request exwm--connection request))))
|
||||
(xcb:flush exwm--connection)))
|
||||
|
||||
(defun exwm-manage--add-frame (frame)
|
||||
"Run in `after-make-frame-functions'."
|
||||
(exwm--log "frame=%s" frame)
|
||||
(when (display-graphic-p frame)
|
||||
(push (string-to-number (frame-parameter frame 'outer-window-id))
|
||||
exwm-manage--frame-outer-id-list)))
|
||||
|
||||
(defun exwm-manage--remove-frame (frame)
|
||||
"Run in `delete-frame-functions'."
|
||||
(exwm--log "frame=%s" frame)
|
||||
(when (display-graphic-p frame)
|
||||
(setq exwm-manage--frame-outer-id-list
|
||||
(delq (string-to-number (frame-parameter frame 'outer-window-id))
|
||||
exwm-manage--frame-outer-id-list))))
|
||||
|
||||
(defun exwm-manage--on-ConfigureRequest (data _synthetic)
|
||||
"Handle ConfigureRequest event."
|
||||
(exwm--log)
|
||||
(let ((obj (make-instance 'xcb:ConfigureRequest))
|
||||
buffer edges width-delta height-delta)
|
||||
(xcb:unmarshal obj data)
|
||||
(with-slots (window x y width height
|
||||
border-width sibling stack-mode value-mask)
|
||||
obj
|
||||
(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)
|
||||
(if (and (setq buffer (exwm--id->buffer window))
|
||||
(with-current-buffer buffer
|
||||
(or (exwm-layout--fullscreen-p)
|
||||
;; Make sure it's a floating X window wanting to resize
|
||||
;; itself.
|
||||
(or (not exwm--floating-frame)
|
||||
(progn
|
||||
(setq edges
|
||||
(window-inside-pixel-edges
|
||||
(get-buffer-window buffer t))
|
||||
width-delta (- width (- (elt edges 2)
|
||||
(elt edges 0)))
|
||||
height-delta (- height (- (elt edges 3)
|
||||
(elt edges 1))))
|
||||
;; We cannot do resizing precisely for now.
|
||||
(and (if (= 0 (logand value-mask
|
||||
xcb:ConfigWindow:Width))
|
||||
t
|
||||
(< (abs width-delta)
|
||||
exwm-manage--width-delta-min))
|
||||
(if (= 0 (logand value-mask
|
||||
xcb:ConfigWindow:Height))
|
||||
t
|
||||
(< (abs height-delta)
|
||||
exwm-manage--height-delta-min))))))))
|
||||
;; Send client message for managed windows
|
||||
(with-current-buffer buffer
|
||||
(setq edges
|
||||
(if (exwm-layout--fullscreen-p)
|
||||
(with-slots (x y width height)
|
||||
(exwm-workspace--get-geometry exwm--frame)
|
||||
(list x y width height))
|
||||
(window-inside-absolute-pixel-edges
|
||||
(get-buffer-window buffer t))))
|
||||
(exwm--log "Reply with ConfigureNotify (edges): %s" edges)
|
||||
(xcb:+request exwm--connection
|
||||
(make-instance 'xcb:SendEvent
|
||||
:propagate 0 :destination window
|
||||
:event-mask xcb:EventMask:StructureNotify
|
||||
:event (xcb:marshal
|
||||
(make-instance
|
||||
'xcb:ConfigureNotify
|
||||
:event window :window window
|
||||
:above-sibling xcb:Window:None
|
||||
:x (elt edges 0) :y (elt edges 1)
|
||||
:width (- (elt edges 2) (elt edges 0))
|
||||
:height (- (elt edges 3) (elt edges 1))
|
||||
:border-width 0 :override-redirect 0)
|
||||
exwm--connection))))
|
||||
(if buffer
|
||||
(with-current-buffer buffer
|
||||
(exwm--log "ConfigureWindow (resize floating X window)")
|
||||
(exwm--set-geometry (frame-parameter exwm--floating-frame
|
||||
'exwm-outer-id)
|
||||
nil
|
||||
nil
|
||||
(+ (frame-pixel-width exwm--floating-frame)
|
||||
width-delta)
|
||||
(+ (frame-pixel-height exwm--floating-frame)
|
||||
height-delta)))
|
||||
(exwm--log "ConfigureWindow (preserve geometry)")
|
||||
;; Configure the unmanaged window.
|
||||
;; But Emacs frames should be excluded. Generally we don't
|
||||
;; receive ConfigureRequest events from Emacs frames since we
|
||||
;; have set OverrideRedirect on them, but this is not true for
|
||||
;; Lucid build (as of 25.1).
|
||||
(unless (memq window exwm-manage--frame-outer-id-list)
|
||||
(xcb:+request exwm--connection
|
||||
(make-instance 'xcb:ConfigureWindow
|
||||
:window window
|
||||
:value-mask value-mask
|
||||
:x x :y y :width width :height height
|
||||
:border-width border-width
|
||||
:sibling sibling
|
||||
:stack-mode stack-mode)))))))
|
||||
(xcb:flush exwm--connection))
|
||||
|
||||
(defun exwm-manage--on-MapRequest (data _synthetic)
|
||||
"Handle MapRequest event."
|
||||
(let ((obj (make-instance 'xcb:MapRequest)))
|
||||
(xcb:unmarshal obj data)
|
||||
(with-slots (parent window) obj
|
||||
(exwm--log "id=#x%x parent=#x%x" window parent)
|
||||
(if (assoc window exwm--id-buffer-alist)
|
||||
(with-current-buffer (exwm--id->buffer window)
|
||||
(if (exwm-layout--iconic-state-p)
|
||||
;; State change: iconic => normal.
|
||||
(when (eq exwm--frame exwm-workspace--current)
|
||||
(pop-to-buffer-same-window (current-buffer)))
|
||||
(exwm--log "#x%x is already managed" window)))
|
||||
(if (/= exwm--root parent)
|
||||
(progn (xcb:+request exwm--connection
|
||||
(make-instance 'xcb:MapWindow :window window))
|
||||
(xcb:flush exwm--connection))
|
||||
(exwm--log "#x%x" window)
|
||||
(exwm-manage--manage-window window))))))
|
||||
|
||||
(defun exwm-manage--on-UnmapNotify (data _synthetic)
|
||||
"Handle UnmapNotify event."
|
||||
(let ((obj (make-instance 'xcb:UnmapNotify)))
|
||||
(xcb:unmarshal obj data)
|
||||
(with-slots (window) obj
|
||||
(exwm--log "id=#x%x" window)
|
||||
(exwm-manage--unmanage-window window t))))
|
||||
|
||||
(defun exwm-manage--on-MapNotify (data _synthetic)
|
||||
"Handle MapNotify event."
|
||||
(let ((obj (make-instance 'xcb:MapNotify)))
|
||||
(xcb:unmarshal obj data)
|
||||
(with-slots (window) obj
|
||||
(when (assoc window exwm--id-buffer-alist)
|
||||
(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.
|
||||
(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)
|
||||
"Handle DestroyNotify event."
|
||||
(unless synthetic
|
||||
(exwm--log)
|
||||
(let ((obj (make-instance 'xcb:DestroyNotify)))
|
||||
(xcb:unmarshal obj data)
|
||||
(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)
|
||||
(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
|
||||
#'exwm-manage--on-ConfigureRequest)
|
||||
(xcb:+event exwm--connection 'xcb:MapRequest #'exwm-manage--on-MapRequest)
|
||||
(xcb:+event exwm--connection 'xcb:UnmapNotify #'exwm-manage--on-UnmapNotify)
|
||||
(xcb:+event exwm--connection 'xcb:MapNotify #'exwm-manage--on-MapNotify)
|
||||
(xcb:+event exwm--connection 'xcb:DestroyNotify
|
||||
#'exwm-manage--on-DestroyNotify))
|
||||
|
||||
(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)
|
||||
(remove-hook 'delete-frame-functions #'exwm-manage--remove-frame)
|
||||
(setq exwm-manage--_MOTIF_WM_HINTS nil))
|
||||
|
||||
|
||||
|
||||
(provide 'exwm-manage)
|
||||
|
||||
;;; exwm-manage.el ends here
|
375
third_party/exwm/exwm-randr.el
vendored
Normal file
375
third_party/exwm/exwm-randr.el
vendored
Normal file
|
@ -0,0 +1,375 @@
|
|||
;;; exwm-randr.el --- RandR Module for EXWM -*- lexical-binding: t -*-
|
||||
|
||||
;; Copyright (C) 2015-2020 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 RandR support for EXWM. Currently it requires external
|
||||
;; tools such as xrandr(1) to properly configure RandR first. This
|
||||
;; dependency may be removed in the future, but more work is needed before
|
||||
;; that.
|
||||
|
||||
;; To use this module, load, enable it and configure
|
||||
;; `exwm-randr-workspace-monitor-plist' and `exwm-randr-screen-change-hook'
|
||||
;; as follows:
|
||||
;;
|
||||
;; (require 'exwm-randr)
|
||||
;; (setq exwm-randr-workspace-monitor-plist '(0 "VGA1"))
|
||||
;; (add-hook 'exwm-randr-screen-change-hook
|
||||
;; (lambda ()
|
||||
;; (start-process-shell-command
|
||||
;; "xrandr" nil "xrandr --output VGA1 --left-of LVDS1 --auto")))
|
||||
;; (exwm-randr-enable)
|
||||
;;
|
||||
;; With above lines, workspace 0 should be assigned to the output named "VGA1",
|
||||
;; staying at the left of other workspaces on the output "LVDS1". Please refer
|
||||
;; to xrandr(1) for the configuration of RandR.
|
||||
|
||||
;; References:
|
||||
;; + RandR (http://www.x.org/archive/X11R7.7/doc/randrproto/randrproto.txt)
|
||||
|
||||
;;; Code:
|
||||
|
||||
(require 'xcb-randr)
|
||||
|
||||
(require 'exwm-core)
|
||||
(require 'exwm-workspace)
|
||||
|
||||
(defgroup exwm-randr nil
|
||||
"RandR."
|
||||
:version "25.3"
|
||||
:group 'exwm)
|
||||
|
||||
(defcustom exwm-randr-refresh-hook nil
|
||||
"Normal hook run when the RandR module just refreshed."
|
||||
:type 'hook)
|
||||
|
||||
(defcustom exwm-randr-screen-change-hook nil
|
||||
"Normal hook run when screen changes."
|
||||
:type 'hook)
|
||||
|
||||
(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)
|
||||
|
||||
xrandr --setmonitor *LVDS-1-L 640/135x800/163+0+0 LVDS-1
|
||||
xrandr --setmonitor LVDS-1-R 640/135x800/163+640+0 none
|
||||
|
||||
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.
|
||||
|
||||
\\='(1 \"HDMI-1\" 3 \"DP-1\")"
|
||||
:type '(plist :key-type integer :value-type string))
|
||||
|
||||
(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.")
|
||||
|
||||
(defvar exwm-randr--compatibility-mode nil
|
||||
"Non-nil when the server does not support RandR 1.5 protocol.")
|
||||
|
||||
(defun exwm-randr--get-monitors ()
|
||||
"Get RandR 1.5 monitors."
|
||||
(exwm--log)
|
||||
(let (monitor-name geometry monitor-geometry-alist primary-monitor)
|
||||
(with-slots (timestamp monitors)
|
||||
(xcb:+request-unchecked+reply exwm--connection
|
||||
(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-geometry-alist (cons (cons monitor-name geometry)
|
||||
monitor-geometry-alist))
|
||||
(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-geometry-alist
|
||||
(exwm-randr--get-monitor-alias primary-monitor
|
||||
monitor-geometry-alist))))
|
||||
|
||||
(defun exwm-randr--get-outputs ()
|
||||
"Get RandR 1.2 outputs.
|
||||
|
||||
Only used when RandR 1.5 is not supported by the server."
|
||||
(exwm--log)
|
||||
(let (output-name geometry output-geometry-alist primary-output)
|
||||
(with-slots (config-timestamp outputs)
|
||||
(xcb:+request-unchecked+reply exwm--connection
|
||||
(make-instance 'xcb:randr:GetScreenResourcesCurrent
|
||||
:window exwm--root))
|
||||
(when (> config-timestamp exwm-randr--last-timestamp)
|
||||
(setq exwm-randr--last-timestamp config-timestamp))
|
||||
(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))
|
||||
(when (and (= connection xcb:randr:Connection:Connected)
|
||||
(/= crtc 0))
|
||||
(with-slots (x y width height)
|
||||
(xcb:+request-unchecked+reply exwm--connection
|
||||
(make-instance 'xcb:randr:GetCrtcInfo
|
||||
:crtc crtc
|
||||
:config-timestamp config-timestamp))
|
||||
(setq output-name (decode-coding-string
|
||||
(apply #'unibyte-string name) 'utf-8)
|
||||
geometry (make-instance 'xcb:RECTANGLE
|
||||
:x x
|
||||
:y y
|
||||
:width width
|
||||
:height height)
|
||||
output-geometry-alist (cons (cons output-name geometry)
|
||||
output-geometry-alist))
|
||||
(exwm--log "%s: %sx%s+%s+%s" output-name x y width height)
|
||||
;; The primary output is the first one.
|
||||
(unless primary-output
|
||||
(setq primary-output output-name)))))))
|
||||
(exwm--log "Primary output: %s" primary-output)
|
||||
(list primary-output output-geometry-alist
|
||||
(exwm-randr--get-monitor-alias primary-output
|
||||
output-geometry-alist))))
|
||||
|
||||
(defun exwm-randr--get-monitor-alias (primary-monitor monitor-geometry-alist)
|
||||
"Generate monitor aliases using PRIMARY-MONITOR MONITOR-GEOMETRY-ALIST.
|
||||
|
||||
In a mirroring setup some monitors overlap and should be treated as one."
|
||||
(let (monitor-position-alist monitor-alias-alist monitor-name geometry)
|
||||
(setq monitor-position-alist (with-slots (x y)
|
||||
(cdr (assoc primary-monitor
|
||||
monitor-geometry-alist))
|
||||
(list (cons primary-monitor (vector x y)))))
|
||||
(setq monitor-alias-alist (list (cons primary-monitor primary-monitor)))
|
||||
(dolist (pair monitor-geometry-alist)
|
||||
(setq monitor-name (car pair)
|
||||
geometry (cdr pair))
|
||||
(unless (assoc monitor-name monitor-alias-alist)
|
||||
(let* ((position (vector (slot-value geometry 'x)
|
||||
(slot-value geometry 'y)))
|
||||
(alias (car (rassoc position monitor-position-alist))))
|
||||
(if alias
|
||||
(setq monitor-alias-alist (cons (cons monitor-name alias)
|
||||
monitor-alias-alist))
|
||||
(setq monitor-position-alist (cons (cons monitor-name position)
|
||||
monitor-position-alist)
|
||||
monitor-alias-alist (cons (cons monitor-name monitor-name)
|
||||
monitor-alias-alist))))))
|
||||
monitor-alias-alist))
|
||||
|
||||
;;;###autoload
|
||||
(defun exwm-randr-refresh ()
|
||||
"Refresh workspaces according to the updated RandR info."
|
||||
(interactive)
|
||||
(exwm--log)
|
||||
(let* ((result (if exwm-randr--compatibility-mode
|
||||
(exwm-randr--get-outputs)
|
||||
(exwm-randr--get-monitors)))
|
||||
(primary-monitor (elt result 0))
|
||||
(monitor-geometry-alist (elt result 1))
|
||||
(monitor-alias-alist (elt result 2))
|
||||
container-monitor-alist container-frame-alist)
|
||||
(when (and primary-monitor monitor-geometry-alist)
|
||||
(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* ((monitor (plist-get exwm-randr-workspace-monitor-plist i))
|
||||
(geometry (cdr (assoc monitor monitor-geometry-alist)))
|
||||
(frame (elt exwm-workspace--list i))
|
||||
(container (frame-parameter frame 'exwm-container)))
|
||||
(if geometry
|
||||
;; Unify monitor names in case it's a mirroring setup.
|
||||
(setq monitor (cdr (assoc monitor monitor-alias-alist)))
|
||||
;; Missing monitors fallback to the primary one.
|
||||
(setq monitor primary-monitor
|
||||
geometry (cdr (assoc primary-monitor
|
||||
monitor-geometry-alist))))
|
||||
(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-monitor monitor)
|
||||
(set-frame-parameter frame 'exwm-geometry geometry)))
|
||||
;; Update workareas.
|
||||
(exwm-workspace--update-workareas)
|
||||
;; Resize workspace.
|
||||
(dolist (f exwm-workspace--list)
|
||||
(exwm-workspace--set-fullscreen f))
|
||||
(xcb:flush exwm--connection)
|
||||
;; Raise the minibuffer if it's active.
|
||||
(when (and (active-minibuffer-window)
|
||||
(exwm-workspace--minibuffer-own-frame-p))
|
||||
(exwm-workspace--show-minibuffer))
|
||||
;; Set _NET_DESKTOP_GEOMETRY.
|
||||
(exwm-workspace--set-desktop-geometry)
|
||||
;; 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 ((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))))
|
||||
|
||||
(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)
|
||||
(when (= 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 5))
|
||||
(cond ((and (= major-version 1) (= minor-version 5))
|
||||
(setq exwm-randr--compatibility-mode nil))
|
||||
((and (= major-version 1) (>= minor-version 2))
|
||||
(setq exwm-randr--compatibility-mode t))
|
||||
(t
|
||||
(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)
|
||||
;; 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
|
||||
#'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 (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))
|
||||
;; Prevent frame parameters introduced by this module from being
|
||||
;; saved/restored.
|
||||
(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."
|
||||
(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))
|
||||
|
||||
|
||||
|
||||
(provide 'exwm-randr)
|
||||
|
||||
;;; exwm-randr.el ends here
|
587
third_party/exwm/exwm-systemtray.el
vendored
Normal file
587
third_party/exwm/exwm-systemtray.el
vendored
Normal file
|
@ -0,0 +1,587 @@
|
|||
;;; exwm-systemtray.el --- System Tray Module for -*- lexical-binding: t -*-
|
||||
;;; EXWM
|
||||
|
||||
;; Copyright (C) 2016-2020 Free Software Foundation, Inc.
|
||||
|
||||
;; Author: Chris Feng <chris.w.feng@gmail.com>
|
||||
|
||||
;; This file is part of GNU Emacs.
|
||||
|
||||
;; GNU Emacs is free software: you can redistribute it and/or modify
|
||||
;; it under the terms of the GNU General Public License as published by
|
||||
;; the Free Software Foundation, either version 3 of the License, or
|
||||
;; (at your option) any later version.
|
||||
|
||||
;; GNU Emacs is distributed in the hope that it will be useful,
|
||||
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
;; GNU General Public License for more details.
|
||||
|
||||
;; You should have received a copy of the GNU General Public License
|
||||
;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
;;; Commentary:
|
||||
|
||||
;; This module adds system tray support for EXWM.
|
||||
|
||||
;; To use this module, load and enable it as follows:
|
||||
;; (require 'exwm-systemtray)
|
||||
;; (exwm-systemtray-enable)
|
||||
|
||||
;;; Code:
|
||||
|
||||
(require 'xcb-icccm)
|
||||
(require 'xcb-xembed)
|
||||
(require 'xcb-systemtray)
|
||||
|
||||
(require 'exwm-core)
|
||||
(require 'exwm-workspace)
|
||||
|
||||
(defclass exwm-systemtray--icon ()
|
||||
((width :initarg :width)
|
||||
(height :initarg :height)
|
||||
(visible :initarg :visible))
|
||||
:documentation "Attributes of a system tray icon.")
|
||||
|
||||
(defclass xcb:systemtray:-ClientMessage
|
||||
(xcb:icccm:--ClientMessage xcb:ClientMessage)
|
||||
((format :initform 32)
|
||||
(type :initform xcb:Atom:MANAGER)
|
||||
(time :initarg :time :type xcb:TIMESTAMP) ;new slot
|
||||
(selection :initarg :selection :type xcb:ATOM) ;new slot
|
||||
(owner :initarg :owner :type xcb:WINDOW)) ;new slot
|
||||
:documentation "A systemtray client message.")
|
||||
|
||||
(defgroup exwm-systemtray nil
|
||||
"System tray."
|
||||
:version "25.3"
|
||||
:group 'exwm)
|
||||
|
||||
(defcustom exwm-systemtray-height nil
|
||||
"System tray height.
|
||||
|
||||
You shall use the default value if using auto-hide minibuffer."
|
||||
:type 'integer)
|
||||
|
||||
(defcustom exwm-systemtray-icon-gap 2
|
||||
"Gap between icons."
|
||||
:type 'integer)
|
||||
|
||||
(defvar exwm-systemtray--embedder-window nil "The embedder window.")
|
||||
|
||||
(defcustom exwm-systemtray-background-color nil
|
||||
"Background color of systemtray.
|
||||
|
||||
This should be a color, or nil for transparent background."
|
||||
:type '(choice (const :tag "Transparent" nil)
|
||||
(color))
|
||||
:initialize #'custom-initialize-default
|
||||
:set (lambda (symbol value)
|
||||
(set-default symbol value)
|
||||
;; Change the background color for embedder.
|
||||
(when (and exwm--connection
|
||||
exwm-systemtray--embedder-window)
|
||||
(let ((background-pixel (exwm--color->pixel value)))
|
||||
(xcb:+request exwm--connection
|
||||
(make-instance 'xcb:ChangeWindowAttributes
|
||||
:window exwm-systemtray--embedder-window
|
||||
:value-mask (logior xcb:CW:BackPixmap
|
||||
(if background-pixel
|
||||
xcb:CW:BackPixel 0))
|
||||
:background-pixmap
|
||||
xcb:BackPixmap:ParentRelative
|
||||
:background-pixel background-pixel))
|
||||
;; Unmap & map to take effect immediately.
|
||||
(xcb:+request exwm--connection
|
||||
(make-instance 'xcb:UnmapWindow
|
||||
:window exwm-systemtray--embedder-window))
|
||||
(xcb:+request exwm--connection
|
||||
(make-instance 'xcb:MapWindow
|
||||
:window exwm-systemtray--embedder-window))
|
||||
(xcb:flush exwm--connection)))))
|
||||
|
||||
;; GTK icons require at least 16 pixels to show normally.
|
||||
(defconst exwm-systemtray--icon-min-size 16 "Minimum icon size.")
|
||||
|
||||
(defvar exwm-systemtray--connection nil "The X connection.")
|
||||
|
||||
(defvar exwm-systemtray--list nil "The icon list.")
|
||||
|
||||
(defvar exwm-systemtray--selection-owner-window nil
|
||||
"The selection owner window.")
|
||||
|
||||
(defvar xcb:Atom:_NET_SYSTEM_TRAY_S0)
|
||||
|
||||
(defun exwm-systemtray--embed (icon)
|
||||
"Embed an icon."
|
||||
(exwm--log "Try to embed #x%x" icon)
|
||||
(let ((info (xcb:+request-unchecked+reply exwm-systemtray--connection
|
||||
(make-instance 'xcb:xembed:get-_XEMBED_INFO
|
||||
:window icon)))
|
||||
width* height* visible)
|
||||
(when info
|
||||
(exwm--log "Embed #x%x" icon)
|
||||
(with-slots (width height)
|
||||
(xcb:+request-unchecked+reply exwm-systemtray--connection
|
||||
(make-instance 'xcb:GetGeometry :drawable icon))
|
||||
(setq height* exwm-systemtray-height
|
||||
width* (round (* width (/ (float height*) height))))
|
||||
(when (< width* exwm-systemtray--icon-min-size)
|
||||
(setq width* exwm-systemtray--icon-min-size
|
||||
height* (round (* height (/ (float width*) width)))))
|
||||
(exwm--log "Resize from %dx%d to %dx%d"
|
||||
width height width* height*))
|
||||
;; Add this icon to save-set.
|
||||
(xcb:+request exwm-systemtray--connection
|
||||
(make-instance 'xcb:ChangeSaveSet
|
||||
:mode xcb:SetMode:Insert
|
||||
:window icon))
|
||||
;; Reparent to the embedder.
|
||||
(xcb:+request exwm-systemtray--connection
|
||||
(make-instance 'xcb:ReparentWindow
|
||||
:window icon
|
||||
:parent exwm-systemtray--embedder-window
|
||||
:x 0
|
||||
;; Vertically centered.
|
||||
:y (/ (- exwm-systemtray-height height*) 2)))
|
||||
;; Resize the icon.
|
||||
(xcb:+request exwm-systemtray--connection
|
||||
(make-instance 'xcb:ConfigureWindow
|
||||
:window icon
|
||||
:value-mask (logior xcb:ConfigWindow:Width
|
||||
xcb:ConfigWindow:Height
|
||||
xcb:ConfigWindow:BorderWidth)
|
||||
:width width*
|
||||
:height height*
|
||||
:border-width 0))
|
||||
;; Set event mask.
|
||||
(xcb:+request exwm-systemtray--connection
|
||||
(make-instance 'xcb:ChangeWindowAttributes
|
||||
:window icon
|
||||
:value-mask xcb:CW:EventMask
|
||||
:event-mask (logior xcb:EventMask:ResizeRedirect
|
||||
xcb:EventMask:KeyPress
|
||||
xcb:EventMask:PropertyChange)))
|
||||
;; Grab all keys and forward them to Emacs frame.
|
||||
(unless (exwm-workspace--minibuffer-own-frame-p)
|
||||
(xcb:+request exwm-systemtray--connection
|
||||
(make-instance 'xcb:GrabKey
|
||||
:owner-events 0
|
||||
:grab-window icon
|
||||
:modifiers xcb:ModMask:Any
|
||||
:key xcb:Grab:Any
|
||||
:pointer-mode xcb:GrabMode:Async
|
||||
:keyboard-mode xcb:GrabMode:Async)))
|
||||
(setq visible (slot-value info 'flags))
|
||||
(if visible
|
||||
(setq visible
|
||||
(/= 0 (logand (slot-value info 'flags) xcb:xembed:MAPPED)))
|
||||
;; Default to visible.
|
||||
(setq visible t))
|
||||
(when visible
|
||||
(exwm--log "Map the window")
|
||||
(xcb:+request exwm-systemtray--connection
|
||||
(make-instance 'xcb:MapWindow :window icon)))
|
||||
(xcb:+request exwm-systemtray--connection
|
||||
(make-instance 'xcb:xembed:SendEvent
|
||||
:destination icon
|
||||
:event
|
||||
(xcb:marshal
|
||||
(make-instance 'xcb:xembed:EMBEDDED-NOTIFY
|
||||
:window icon
|
||||
:time xcb:Time:CurrentTime
|
||||
:embedder
|
||||
exwm-systemtray--embedder-window
|
||||
:version 0)
|
||||
exwm-systemtray--connection)))
|
||||
(push `(,icon . ,(make-instance 'exwm-systemtray--icon
|
||||
:width width*
|
||||
:height height*
|
||||
:visible visible))
|
||||
exwm-systemtray--list)
|
||||
(exwm-systemtray--refresh))))
|
||||
|
||||
(defun exwm-systemtray--unembed (icon)
|
||||
"Unembed an icon."
|
||||
(exwm--log "Unembed #x%x" icon)
|
||||
(xcb:+request exwm-systemtray--connection
|
||||
(make-instance 'xcb:UnmapWindow :window icon))
|
||||
(xcb:+request exwm-systemtray--connection
|
||||
(make-instance 'xcb:ReparentWindow
|
||||
:window icon
|
||||
:parent exwm--root
|
||||
:x 0 :y 0))
|
||||
(setq exwm-systemtray--list
|
||||
(assq-delete-all icon exwm-systemtray--list))
|
||||
(exwm-systemtray--refresh))
|
||||
|
||||
(defun exwm-systemtray--refresh ()
|
||||
"Refresh the system tray."
|
||||
(exwm--log)
|
||||
;; Make sure to redraw the embedder.
|
||||
(xcb:+request exwm-systemtray--connection
|
||||
(make-instance 'xcb:UnmapWindow
|
||||
:window exwm-systemtray--embedder-window))
|
||||
(let ((x exwm-systemtray-icon-gap)
|
||||
map)
|
||||
(dolist (pair exwm-systemtray--list)
|
||||
(when (slot-value (cdr pair) 'visible)
|
||||
(xcb:+request exwm-systemtray--connection
|
||||
(make-instance 'xcb:ConfigureWindow
|
||||
:window (car pair)
|
||||
:value-mask xcb:ConfigWindow:X
|
||||
:x x))
|
||||
(setq x (+ x (slot-value (cdr pair) 'width)
|
||||
exwm-systemtray-icon-gap))
|
||||
(setq map t)))
|
||||
(let ((workarea (elt exwm-workspace--workareas
|
||||
exwm-workspace-current-index)))
|
||||
(xcb:+request exwm-systemtray--connection
|
||||
(make-instance 'xcb:ConfigureWindow
|
||||
:window exwm-systemtray--embedder-window
|
||||
:value-mask (logior xcb:ConfigWindow:X
|
||||
xcb:ConfigWindow:Width)
|
||||
:x (- (aref workarea 2) x)
|
||||
:width x)))
|
||||
(when map
|
||||
(xcb:+request exwm-systemtray--connection
|
||||
(make-instance 'xcb:MapWindow
|
||||
:window exwm-systemtray--embedder-window))))
|
||||
(xcb:flush exwm-systemtray--connection))
|
||||
|
||||
(defun exwm-systemtray--on-DestroyNotify (data _synthetic)
|
||||
"Unembed icons on DestroyNotify."
|
||||
(exwm--log)
|
||||
(let ((obj (make-instance 'xcb:DestroyNotify)))
|
||||
(xcb:unmarshal obj data)
|
||||
(with-slots (window) obj
|
||||
(when (assoc window exwm-systemtray--list)
|
||||
(exwm-systemtray--unembed window)))))
|
||||
|
||||
(defun exwm-systemtray--on-ReparentNotify (data _synthetic)
|
||||
"Unembed icons on ReparentNotify."
|
||||
(exwm--log)
|
||||
(let ((obj (make-instance 'xcb:ReparentNotify)))
|
||||
(xcb:unmarshal obj data)
|
||||
(with-slots (window parent) obj
|
||||
(when (and (/= parent exwm-systemtray--embedder-window)
|
||||
(assoc window exwm-systemtray--list))
|
||||
(exwm-systemtray--unembed window)))))
|
||||
|
||||
(defun exwm-systemtray--on-ResizeRequest (data _synthetic)
|
||||
"Resize the tray icon on ResizeRequest."
|
||||
(exwm--log)
|
||||
(let ((obj (make-instance 'xcb:ResizeRequest))
|
||||
attr)
|
||||
(xcb:unmarshal obj data)
|
||||
(with-slots (window width height) obj
|
||||
(when (setq attr (cdr (assoc window exwm-systemtray--list)))
|
||||
(with-slots ((width* width)
|
||||
(height* height))
|
||||
attr
|
||||
(setq height* exwm-systemtray-height
|
||||
width* (round (* width (/ (float height*) height))))
|
||||
(when (< width* exwm-systemtray--icon-min-size)
|
||||
(setq width* exwm-systemtray--icon-min-size
|
||||
height* (round (* height (/ (float width*) width)))))
|
||||
(xcb:+request exwm-systemtray--connection
|
||||
(make-instance 'xcb:ConfigureWindow
|
||||
:window window
|
||||
:value-mask (logior xcb:ConfigWindow:Y
|
||||
xcb:ConfigWindow:Width
|
||||
xcb:ConfigWindow:Height)
|
||||
;; Vertically centered.
|
||||
:y (/ (- exwm-systemtray-height height*) 2)
|
||||
:width width*
|
||||
:height height*)))
|
||||
(exwm-systemtray--refresh)))))
|
||||
|
||||
(defun exwm-systemtray--on-PropertyNotify (data _synthetic)
|
||||
"Map/Unmap the tray icon on PropertyNotify."
|
||||
(exwm--log)
|
||||
(let ((obj (make-instance 'xcb:PropertyNotify))
|
||||
attr info visible)
|
||||
(xcb:unmarshal obj data)
|
||||
(with-slots (window atom state) obj
|
||||
(when (and (eq state xcb:Property:NewValue)
|
||||
(eq atom xcb:Atom:_XEMBED_INFO)
|
||||
(setq attr (cdr (assoc window exwm-systemtray--list))))
|
||||
(setq info (xcb:+request-unchecked+reply exwm-systemtray--connection
|
||||
(make-instance 'xcb:xembed:get-_XEMBED_INFO
|
||||
:window window)))
|
||||
(when info
|
||||
(setq visible (/= 0 (logand (slot-value info 'flags)
|
||||
xcb:xembed:MAPPED)))
|
||||
(exwm--log "#x%x visible? %s" window visible)
|
||||
(if visible
|
||||
(xcb:+request exwm-systemtray--connection
|
||||
(make-instance 'xcb:MapWindow :window window))
|
||||
(xcb:+request exwm-systemtray--connection
|
||||
(make-instance 'xcb:UnmapWindow :window window)))
|
||||
(setf (slot-value attr 'visible) visible)
|
||||
(exwm-systemtray--refresh))))))
|
||||
|
||||
(defun exwm-systemtray--on-ClientMessage (data _synthetic)
|
||||
"Handle client messages."
|
||||
(let ((obj (make-instance 'xcb:ClientMessage))
|
||||
opcode data32)
|
||||
(xcb:unmarshal obj data)
|
||||
(with-slots (window type data) obj
|
||||
(when (eq type xcb:Atom:_NET_SYSTEM_TRAY_OPCODE)
|
||||
(setq data32 (slot-value data 'data32)
|
||||
opcode (elt data32 1))
|
||||
(exwm--log "opcode: %s" opcode)
|
||||
(cond ((= opcode xcb:systemtray:opcode:REQUEST-DOCK)
|
||||
(unless (assoc (elt data32 2) exwm-systemtray--list)
|
||||
(exwm-systemtray--embed (elt data32 2))))
|
||||
;; Not implemented (rarely used nowadays).
|
||||
((or (= opcode xcb:systemtray:opcode:BEGIN-MESSAGE)
|
||||
(= opcode xcb:systemtray:opcode:CANCEL-MESSAGE)))
|
||||
(t
|
||||
(exwm--log "Unknown opcode message: %s" obj)))))))
|
||||
|
||||
(defun exwm-systemtray--on-KeyPress (data _synthetic)
|
||||
"Forward all KeyPress events to Emacs frame."
|
||||
(exwm--log)
|
||||
;; This function is only executed when there's no autohide minibuffer,
|
||||
;; a workspace frame has the input focus and the pointer is over a
|
||||
;; tray icon.
|
||||
(let ((dest (frame-parameter (selected-frame) 'exwm-outer-id))
|
||||
(obj (make-instance 'xcb:KeyPress)))
|
||||
(xcb:unmarshal obj data)
|
||||
(setf (slot-value obj 'event) dest)
|
||||
(xcb:+request exwm-systemtray--connection
|
||||
(make-instance 'xcb:SendEvent
|
||||
:propagate 0
|
||||
:destination dest
|
||||
:event-mask xcb:EventMask:NoEvent
|
||||
:event (xcb:marshal obj exwm-systemtray--connection))))
|
||||
(xcb:flush exwm-systemtray--connection))
|
||||
|
||||
(defun exwm-systemtray--on-workspace-switch ()
|
||||
"Reparent/Refresh the system tray in `exwm-workspace-switch-hook'."
|
||||
(exwm--log)
|
||||
(unless (exwm-workspace--minibuffer-own-frame-p)
|
||||
(exwm-workspace--update-offsets)
|
||||
(xcb:+request exwm-systemtray--connection
|
||||
(make-instance 'xcb:ReparentWindow
|
||||
:window exwm-systemtray--embedder-window
|
||||
:parent (string-to-number
|
||||
(frame-parameter exwm-workspace--current
|
||||
'window-id))
|
||||
:x 0
|
||||
:y (- (elt (elt exwm-workspace--workareas
|
||||
exwm-workspace-current-index)
|
||||
3)
|
||||
exwm-workspace--frame-y-offset
|
||||
exwm-systemtray-height))))
|
||||
(exwm-systemtray--refresh))
|
||||
|
||||
(defun exwm-systemtray--refresh-all ()
|
||||
"Reposition/Refresh the system tray."
|
||||
(exwm--log)
|
||||
(unless (exwm-workspace--minibuffer-own-frame-p)
|
||||
(exwm-workspace--update-offsets)
|
||||
(xcb:+request exwm-systemtray--connection
|
||||
(make-instance 'xcb:ConfigureWindow
|
||||
:window exwm-systemtray--embedder-window
|
||||
:value-mask xcb:ConfigWindow:Y
|
||||
:y (- (elt (elt exwm-workspace--workareas
|
||||
exwm-workspace-current-index)
|
||||
3)
|
||||
exwm-workspace--frame-y-offset
|
||||
exwm-systemtray-height))))
|
||||
(exwm-systemtray--refresh))
|
||||
|
||||
(cl-defun exwm-systemtray--init ()
|
||||
"Initialize system tray module."
|
||||
(exwm--log)
|
||||
(cl-assert (not exwm-systemtray--connection))
|
||||
(cl-assert (not exwm-systemtray--list))
|
||||
(cl-assert (not exwm-systemtray--selection-owner-window))
|
||||
(cl-assert (not exwm-systemtray--embedder-window))
|
||||
(unless exwm-systemtray-height
|
||||
(setq exwm-systemtray-height (max exwm-systemtray--icon-min-size
|
||||
(line-pixel-height))))
|
||||
;; Create a new connection.
|
||||
(setq exwm-systemtray--connection (xcb:connect))
|
||||
(set-process-query-on-exit-flag (slot-value exwm-systemtray--connection
|
||||
'process)
|
||||
nil)
|
||||
;; Initialize XELB modules.
|
||||
(xcb:xembed:init exwm-systemtray--connection t)
|
||||
(xcb:systemtray:init exwm-systemtray--connection t)
|
||||
;; Acquire the manager selection _NET_SYSTEM_TRAY_S0.
|
||||
(with-slots (owner)
|
||||
(xcb:+request-unchecked+reply exwm-systemtray--connection
|
||||
(make-instance 'xcb:GetSelectionOwner
|
||||
:selection xcb:Atom:_NET_SYSTEM_TRAY_S0))
|
||||
(when (/= owner xcb:Window:None)
|
||||
(xcb:disconnect exwm-systemtray--connection)
|
||||
(setq exwm-systemtray--connection nil)
|
||||
(warn "[EXWM] Other system tray detected")
|
||||
(cl-return-from exwm-systemtray--init)))
|
||||
(let ((id (xcb:generate-id exwm-systemtray--connection)))
|
||||
(setq exwm-systemtray--selection-owner-window id)
|
||||
(xcb:+request exwm-systemtray--connection
|
||||
(make-instance 'xcb:CreateWindow
|
||||
:depth 0
|
||||
:wid id
|
||||
:parent exwm--root
|
||||
:x 0
|
||||
:y 0
|
||||
:width 1
|
||||
:height 1
|
||||
:border-width 0
|
||||
:class xcb:WindowClass:InputOnly
|
||||
:visual 0
|
||||
:value-mask xcb:CW:OverrideRedirect
|
||||
:override-redirect 1))
|
||||
;; Get the selection ownership.
|
||||
(xcb:+request exwm-systemtray--connection
|
||||
(make-instance 'xcb:SetSelectionOwner
|
||||
:owner id
|
||||
:selection xcb:Atom:_NET_SYSTEM_TRAY_S0
|
||||
:time xcb:Time:CurrentTime))
|
||||
;; Send a client message to announce the selection.
|
||||
(xcb:+request exwm-systemtray--connection
|
||||
(make-instance 'xcb:SendEvent
|
||||
:propagate 0
|
||||
:destination exwm--root
|
||||
:event-mask xcb:EventMask:StructureNotify
|
||||
:event (xcb:marshal
|
||||
(make-instance 'xcb:systemtray:-ClientMessage
|
||||
:window exwm--root
|
||||
:time xcb:Time:CurrentTime
|
||||
:selection
|
||||
xcb:Atom:_NET_SYSTEM_TRAY_S0
|
||||
:owner id)
|
||||
exwm-systemtray--connection)))
|
||||
;; Set _NET_WM_NAME.
|
||||
(xcb:+request exwm-systemtray--connection
|
||||
(make-instance 'xcb:ewmh:set-_NET_WM_NAME
|
||||
:window id
|
||||
:data "EXWM: exwm-systemtray--selection-owner-window"))
|
||||
;; Set the _NET_SYSTEM_TRAY_ORIENTATION property.
|
||||
(xcb:+request exwm-systemtray--connection
|
||||
(make-instance 'xcb:xembed:set-_NET_SYSTEM_TRAY_ORIENTATION
|
||||
:window id
|
||||
:data xcb:systemtray:ORIENTATION:HORZ)))
|
||||
;; Create the embedder.
|
||||
(let ((id (xcb:generate-id exwm-systemtray--connection))
|
||||
(background-pixel (exwm--color->pixel exwm-systemtray-background-color))
|
||||
frame parent depth y)
|
||||
(setq exwm-systemtray--embedder-window id)
|
||||
(if (exwm-workspace--minibuffer-own-frame-p)
|
||||
(setq frame exwm-workspace--minibuffer
|
||||
y (if (>= (line-pixel-height) exwm-systemtray-height)
|
||||
;; Bottom aligned.
|
||||
(- (line-pixel-height) exwm-systemtray-height)
|
||||
;; Vertically centered.
|
||||
(/ (- (line-pixel-height) exwm-systemtray-height) 2)))
|
||||
(exwm-workspace--update-offsets)
|
||||
(setq frame exwm-workspace--current
|
||||
;; Bottom aligned.
|
||||
y (- (elt (elt exwm-workspace--workareas
|
||||
exwm-workspace-current-index)
|
||||
3)
|
||||
exwm-workspace--frame-y-offset
|
||||
exwm-systemtray-height)))
|
||||
(setq parent (string-to-number (frame-parameter frame 'window-id))
|
||||
depth (slot-value (xcb:+request-unchecked+reply
|
||||
exwm-systemtray--connection
|
||||
(make-instance 'xcb:GetGeometry
|
||||
:drawable parent))
|
||||
'depth))
|
||||
(xcb:+request exwm-systemtray--connection
|
||||
(make-instance 'xcb:CreateWindow
|
||||
:depth depth
|
||||
:wid id
|
||||
:parent parent
|
||||
:x 0
|
||||
:y y
|
||||
:width 1
|
||||
:height exwm-systemtray-height
|
||||
:border-width 0
|
||||
:class xcb:WindowClass:InputOutput
|
||||
:visual 0
|
||||
:value-mask (logior xcb:CW:BackPixmap
|
||||
(if background-pixel
|
||||
xcb:CW:BackPixel 0)
|
||||
xcb:CW:EventMask)
|
||||
:background-pixmap xcb:BackPixmap:ParentRelative
|
||||
:background-pixel background-pixel
|
||||
:event-mask xcb:EventMask:SubstructureNotify))
|
||||
;; Set _NET_WM_NAME.
|
||||
(xcb:+request exwm-systemtray--connection
|
||||
(make-instance 'xcb:ewmh:set-_NET_WM_NAME
|
||||
:window id
|
||||
:data "EXWM: exwm-systemtray--embedder-window")))
|
||||
(xcb:flush exwm-systemtray--connection)
|
||||
;; Attach event listeners.
|
||||
(xcb:+event exwm-systemtray--connection 'xcb:DestroyNotify
|
||||
#'exwm-systemtray--on-DestroyNotify)
|
||||
(xcb:+event exwm-systemtray--connection 'xcb:ReparentNotify
|
||||
#'exwm-systemtray--on-ReparentNotify)
|
||||
(xcb:+event exwm-systemtray--connection 'xcb:ResizeRequest
|
||||
#'exwm-systemtray--on-ResizeRequest)
|
||||
(xcb:+event exwm-systemtray--connection 'xcb:PropertyNotify
|
||||
#'exwm-systemtray--on-PropertyNotify)
|
||||
(xcb:+event exwm-systemtray--connection 'xcb:ClientMessage
|
||||
#'exwm-systemtray--on-ClientMessage)
|
||||
(unless (exwm-workspace--minibuffer-own-frame-p)
|
||||
(xcb:+event exwm-systemtray--connection 'xcb:KeyPress
|
||||
#'exwm-systemtray--on-KeyPress))
|
||||
;; Add hook to move/reparent the embedder.
|
||||
(add-hook 'exwm-workspace-switch-hook #'exwm-systemtray--on-workspace-switch)
|
||||
(add-hook 'exwm-workspace--update-workareas-hook
|
||||
#'exwm-systemtray--refresh-all)
|
||||
(add-hook 'menu-bar-mode-hook #'exwm-systemtray--refresh-all)
|
||||
(add-hook 'tool-bar-mode-hook #'exwm-systemtray--refresh-all)
|
||||
(when (boundp 'exwm-randr-refresh-hook)
|
||||
(add-hook 'exwm-randr-refresh-hook #'exwm-systemtray--refresh-all))
|
||||
;; The struts can be updated already.
|
||||
(when exwm-workspace--workareas
|
||||
(exwm-systemtray--refresh-all)))
|
||||
|
||||
(defun exwm-systemtray--exit ()
|
||||
"Exit the systemtray module."
|
||||
(exwm--log)
|
||||
(when exwm-systemtray--connection
|
||||
;; Hide & reparent out the embedder before disconnection to prevent
|
||||
;; embedded icons from being reparented to an Emacs frame (which is the
|
||||
;; parent of the embedder).
|
||||
(xcb:+request exwm-systemtray--connection
|
||||
(make-instance 'xcb:UnmapWindow
|
||||
:window exwm-systemtray--embedder-window))
|
||||
(xcb:+request exwm-systemtray--connection
|
||||
(make-instance 'xcb:ReparentWindow
|
||||
:window exwm-systemtray--embedder-window
|
||||
:parent exwm--root
|
||||
:x 0
|
||||
:y 0))
|
||||
(xcb:disconnect exwm-systemtray--connection)
|
||||
(setq exwm-systemtray--connection nil
|
||||
exwm-systemtray--list nil
|
||||
exwm-systemtray--selection-owner-window nil
|
||||
exwm-systemtray--embedder-window nil)
|
||||
(remove-hook 'exwm-workspace-switch-hook
|
||||
#'exwm-systemtray--on-workspace-switch)
|
||||
(remove-hook 'exwm-workspace--update-workareas-hook
|
||||
#'exwm-systemtray--refresh-all)
|
||||
(remove-hook 'menu-bar-mode-hook #'exwm-systemtray--refresh-all)
|
||||
(remove-hook 'tool-bar-mode-hook #'exwm-systemtray--refresh-all)
|
||||
(when (boundp 'exwm-randr-refresh-hook)
|
||||
(remove-hook 'exwm-randr-refresh-hook #'exwm-systemtray--refresh-all))))
|
||||
|
||||
(defun exwm-systemtray-enable ()
|
||||
"Enable system tray support for EXWM."
|
||||
(exwm--log)
|
||||
(add-hook 'exwm-init-hook #'exwm-systemtray--init)
|
||||
(add-hook 'exwm-exit-hook #'exwm-systemtray--exit))
|
||||
|
||||
|
||||
|
||||
(provide 'exwm-systemtray)
|
||||
|
||||
;;; exwm-systemtray.el ends here
|
1750
third_party/exwm/exwm-workspace.el
vendored
Normal file
1750
third_party/exwm/exwm-workspace.el
vendored
Normal file
File diff suppressed because it is too large
Load diff
800
third_party/exwm/exwm-xim.el
vendored
Normal file
800
third_party/exwm/exwm-xim.el
vendored
Normal file
|
@ -0,0 +1,800 @@
|
|||
;;; exwm-xim.el --- XIM Module for EXWM -*- lexical-binding: t -*-
|
||||
|
||||
;; Copyright (C) 2019-2020 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.")
|
||||
|
||||
;; 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)
|
||||
(exwm-xim--handle-forward-event-request req xim:lsb conn
|
||||
client-xwin))
|
||||
((= 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--handle-forward-event-request (req lsb conn client-xwin)
|
||||
(let ((im-func (with-current-buffer (window-buffer)
|
||||
input-method-function))
|
||||
key-event keysym keysyms event result)
|
||||
;; Note: The flag slot is ignored.
|
||||
;; Do conversion in client's byte-order.
|
||||
(let ((xcb:lsb 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)))))))
|
||||
(while (or (slot-value req 'event) unread-command-events)
|
||||
(unless (slot-value req 'event)
|
||||
(setq event (pop unread-command-events))
|
||||
;; Handle events in (t . EVENT) format.
|
||||
(when (and (consp event)
|
||||
(eq (car event) t))
|
||||
(setq event (cdr event))))
|
||||
(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 ((raw-event event)
|
||||
im-id ic-id serial-number)
|
||||
req
|
||||
(if raw-event
|
||||
(setq event raw-event)
|
||||
(setq keysyms (xcb:keysyms:event->keysyms exwm-xim--conn event))
|
||||
(with-slots (detail state) key-event
|
||||
(setf detail (xcb:keysyms:keysym->keycode exwm-xim--conn
|
||||
(caar keysyms))
|
||||
state (cdar keysyms)))
|
||||
(setq event (let ((xcb:lsb lsb))
|
||||
(xcb:marshal key-event conn))))
|
||||
(when event
|
||||
(exwm-xim--make-request
|
||||
(make-instance 'xim:forward-event
|
||||
:im-id im-id
|
||||
:ic-id ic-id
|
||||
:flag xim:commit-flag:synchronous
|
||||
:serial-number serial-number
|
||||
:event event)
|
||||
conn client-xwin)))
|
||||
(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)
|
||||
(exwm-input-line-mode-passthrough t))
|
||||
(setq result (funcall im-func event))
|
||||
;; Clear echo area for the input method.
|
||||
(message nil)
|
||||
;; This also works for portable character encoding.
|
||||
(setq result
|
||||
(encode-coding-string (concat result)
|
||||
'compound-text-with-extensions))
|
||||
(exwm-xim--make-request
|
||||
(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)
|
||||
conn client-xwin)))
|
||||
(when (eq exwm--selected-input-mode 'char-mode)
|
||||
(exwm-input--release-keyboard))))
|
||||
(xcb:flush conn)
|
||||
(setf event nil
|
||||
(slot-value req 'event) nil))))
|
||||
|
||||
(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
|
1019
third_party/exwm/exwm.el
vendored
Normal file
1019
third_party/exwm/exwm.el
vendored
Normal file
File diff suppressed because it is too large
Load diff
20
third_party/exwm/xinitrc
vendored
Normal file
20
third_party/exwm/xinitrc
vendored
Normal file
|
@ -0,0 +1,20 @@
|
|||
# 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
|
||||
|
||||
# Set default cursor.
|
||||
xsetroot -cursor_name left_ptr
|
||||
|
||||
# Set keyboard repeat rate.
|
||||
xset r rate 200 60
|
||||
|
||||
# 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
|
Loading…
Reference in a new issue