Imported from0368127976git-subtree-dir: third_party/emacs/exwm git-subtree-mainline:e84f9ef0adgit-subtree-split:0368127976Change-Id: Id3af5610254180f42947d71265aad89def7c6a3d
		
			
				
	
	
		
			800 lines
		
	
	
	
		
			35 KiB
		
	
	
	
		
			EmacsLisp
		
	
	
	
	
	
			
		
		
	
	
			800 lines
		
	
	
	
		
			35 KiB
		
	
	
	
		
			EmacsLisp
		
	
	
	
	
	
| ;;; 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
 |