-
-
Notifications
You must be signed in to change notification settings - Fork 58
/
emacs-everywhere.el
803 lines (722 loc) · 35.3 KB
/
emacs-everywhere.el
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
;;; emacs-everywhere.el --- System-wide popup windows for quick edits -*- lexical-binding: t; -*-
;; Copyright (C) 2021 TEC
;; Author: TEC <https://github.com/tecosaur>
;; Maintainer: TEC <[email protected]>
;; Created: February 06, 2021
;; Modified: February 06, 2021
;; Version: 0.1.0
;; Keywords: convenience, frames
;; Homepage: https://github.com/tecosaur/emacs-everywhere
;; Package-Requires: ((emacs "26.3"))
;;; License:
;; This file is part of org-pandoc-import, which is not part of GNU Emacs.
;; SPDX-License-Identifier: GPL-3.0-or-later
;;; Commentary:
;; System-wide popup Emacs windows for quick edits
;;; Code:
(require 'cl-lib)
(require 'server)
(defgroup emacs-everywhere ()
"Customise group for Emacs-everywhere."
:group 'convenience)
(define-obsolete-variable-alias
'emacs-everywhere-paste-p 'emacs-everywhere-paste-command "0.1.0")
(defalias 'emacs-everywhere-call 'emacs-everywhere--call)
(make-obsolete 'emacs-everywhere-call "Now private API" "0.2.0")
(define-obsolete-variable-alias
'emacs-everywhere-return-converted-org-to-gfm
'emacs-everywhere-convert-org-to-gfm "0.2.0")
(defvaralias 'emacs-everywhere-mode-initial-map 'emacs-everywhere--initial-mode-map)
(make-obsolete-variable 'emacs-everywhere-mode-initial-map "Now private API" "0.2.0")
(defalias 'emacs-everywhere-erase-buffer 'emacs-everywhere--erase-buffer)
(make-obsolete 'emacs-everywhere-erase-buffer "Now private API" "0.2.0")
(defalias 'emacs-everywhere-finish-or-ctrl-c-ctrl-c 'emacs-everywhere--finish-or-ctrl-c-ctrl-c)
(make-obsolete 'emacs-everywhere-finish-or-ctrl-c-ctrl-c "Now private API" "0.2.0")
(defalias 'emacs-everywhere-app-info-linux 'emacs-everywhere--app-info-linux)
(make-obsolete 'emacs-everywhere-app-info-linux "Now private API" "0.2.0")
(defalias 'emacs-everywhere-app-info-osx 'emacs-everywhere--app-info-osx)
(make-obsolete 'emacs-everywhere-app-info-osx "Now private API" "0.2.0")
(defalias 'emacs-everywhere-app-info-windows 'emacs-everywhere--app-info-windows)
(make-obsolete 'emacs-everywhere-app-info-windows "Now private API" "0.2.0")
(defalias 'emacs-everywhere-ensure-oscascript-compiled 'emacs-everywhere--ensure-oscascript-compiled)
(make-obsolete 'emacs-everywhere-ensure-oscascript-compiled "Now private API" "0.2.0")
(defvar emacs-everywhere--display-server
(cond
((eq system-type 'darwin) '(quartz . nil))
((memq system-type '(ms-dos windows-nt cygwin)) '(windows . nil))
((eq system-type 'gnu/linux)
(cons
(if (getenv "WAYLAND_DISPLAY") 'wayland 'x11)
(intern (or (getenv "XDG_CURRENT_DESKTOP") "unknown"))))
(t '(unknown . nil)))
"The detected display server.")
(defcustom emacs-everywhere-paste-command
(pcase (car emacs-everywhere--display-server)
('quartz (list "osascript" "-e" "tell application \"System Events\" to keystroke \"v\" using command down"))
('windows
(list "powershell" "-NoProfile" "-Command"
"& {(New-Object -ComObject wscript.shell).SendKeys(\"^v\")}"))
('x11 (list "xdotool" "key" "--clearmodifiers" "Shift+Insert"))
('wayland (list "ydotool" "key" "42:1" "110:1" "42:0" "110:0"))
('unknown
(list "notify-send"
"No paste command defined for emacs-everywhere"
"-a" "Emacs" "-i" "emacs")))
"Command to trigger a system paste from the clipboard.
This is given as a list in the form (CMD ARGS...).
To not run any command, set to nil."
:type '(set (repeat string) (const nil))
:group 'emacs-everywhere)
(defcustom emacs-everywhere-copy-command
(pcase (car emacs-everywhere--display-server)
('x11 (list "xclip" "-selection" "clipboard" "%f"))
('wayland
(list "sh" "-c" "wl-copy < %f"))
('windows
(list "Powershell" "-NoProfile" "-Command" "& { Get-Content %f | clip }")))
"Command to write to the system clipboard from a file (%f).
This is given as a list in the form (CMD ARGS...).
In the arguments, \"%f\" is treated as a placeholder for the path
to the file.
When nil, nothing is executed.
`gui-select-text' is always called on the buffer content, however experience
suggests that this can be somewhat flakey, and so an extra step to make sure
it worked can be a good idea."
:type '(set (repeat string) (const nil))
:group 'emacs-everywhere)
(defcustom emacs-everywhere-window-focus-command
(pcase emacs-everywhere--display-server
(`(quartz . ,_) (list "osascript" "-e" "tell application \"%w\" to activate"))
(`(windows . ,_) (list "powershell" "-NoProfile" "-command"
"& {Add-Type 'using System; using System.Runtime.InteropServices; public class Tricks { [DllImport(\"user32.dll\")] public static extern bool SetForegroundWindow(IntPtr hWnd); }'; [tricks]::SetForegroundWindow(%w) }"))
(`(x11 . ,_) (list "xdotool" "windowactivate" "--sync" "%w"))
(`(wayland . KDE) (list "kdotool" "windowactivate" "%w"))) ; No --sync
"Command to refocus the active window when emacs-everywhere was triggered.
This is given as a list in the form (CMD ARGS...).
In the arguments, \"%w\" is treated as a placeholder for the window ID,
as returned by `emacs-everywhere-app-id'.
When nil, nothing is executed, and pasting is not attempted."
:type '(set (repeat string) (const nil))
:group 'emacs-everywhere)
(defcustom emacs-everywhere-markdown-windows
'("Reddit" "Stack Exchange" "Stack Overflow" ; Sites
"Discord" "Element" "Slack" "HedgeDoc" "HackMD" "Zulip" ; Web Apps
"Pull Request" "Issue" "Comparing .*\\.\\.\\.") ; Github
"For use with `emacs-everywhere-markdown-p'.
Patterns which are matched against the window title."
:type '(rep string)
:group 'emacs-everywhere)
(defcustom emacs-everywhere-markdown-apps
'("Discord" "Element" "Fractal" "NeoChat" "Slack")
"For use with `emacs-everywhere-markdown-p'.
Patterns which are matched against the app name."
:type '(rep string)
:group 'emacs-everywhere)
(defcustom emacs-everywhere-frame-name-format "Emacs Everywhere :: %s — %s"
"Format string used to produce the frame name.
Formatted with the app name, and truncated window name."
:type 'string
:group 'emacs-everywhere)
(defcustom emacs-everywhere-major-mode-function
(cond
((executable-find "pandoc") #'org-mode)
((fboundp 'markdown-mode) #'emacs-everywhere-major-mode-org-or-markdown)
(t #'text-mode))
"Function which sets the major mode for the Emacs Everywhere buffer.
When set to `org-mode', pandoc is used to convert from markdown to Org
when applicable."
:type 'function
:options '(org-mode
emacs-everywhere-major-mode-org-or-markdown
text-mode)
:group 'emacs-everywhere)
(defcustom emacs-everywhere-init-hooks
'(emacs-everywhere-set-frame-name
emacs-everywhere-set-frame-position
emacs-everywhere-apply-major-mode
emacs-everywhere-insert-selection
emacs-everywhere-remove-trailing-whitespace
emacs-everywhere-init-spell-check)
"Hooks to be run before function `emacs-everywhere-mode'."
:type 'hook
:group 'emacs-everywhere)
(defcustom emacs-everywhere-final-hooks
'(emacs-everywhere-convert-org-to-gfm
emacs-everywhere-remove-trailing-whitespace)
"Hooks to be run just before content is copied."
:type 'hook
:group 'emacs-everywhere)
(defcustom emacs-everywhere-frame-parameters
`((name . "emacs-everywhere")
(fullscreen . nil) ; Helps on GNOME at least
(width . 80)
(height . 12))
"Parameters `make-frame' recognises to apply to the emacs-everywhere frame."
:type 'list
:group 'emacs-everywhere)
(defcustom emacs-everywhere-top-padding 0.2
"Use the header-line to introduce this fraction of a line as padding.
Set to nil to disable."
:type '(choice (const nil :tag "No padding") number)
:group 'emacs-everywhere)
(defcustom emacs-everywhere-file-dir
temporary-file-directory
"The default dir for`emacs-everywhere-filename-function'-generated temp files."
:type 'string
:group 'emacs-everywhere)
(defcustom emacs-everywhere-file-patterns
(let ((default-directory emacs-everywhere-file-dir))
(list (concat "^" (regexp-quote (file-truename "emacs-everywhere-")))
;; For qutebrowser 'editor.command' support
(concat "^" (regexp-quote (file-truename "qutebrowser-editor-")))))
"A list of file regexps to activate `emacs-everywhere-mode' for."
:type '(repeat regexp)
:group 'emacs-everywhere)
(defcustom emacs-everywhere-pandoc-md-args
'("-f" "markdown-auto_identifiers" "-t" "org")
"Arguments supplied to pandoc when converting text from Markdown to Org."
:type '(repeat string)
:group 'emacs-everywhere)
(defcustom emacs-everywhere-clipboard-sleep-delay 0.01
"Waiting period to wait to propagate clipboard actions."
:type 'number
:group 'emacs-everywhere)
(defun emacs-everywhere-temp-filename (app-info)
"Generate a temp file based on APP-INFO."
(concat "emacs-everywhere-"
(format-time-string "%Y%m%d-%H%M%S-" (current-time))
(emacs-everywhere-app-class app-info)))
(defcustom emacs-everywhere-filename-function
#'emacs-everywhere-temp-filename
"A function which generates a file name for the buffer.
The function is passed the result of `emacs-everywhere-app-info'.
Make sure that it will be matched by `emacs-everywhere-file-patterns'."
:type 'function
:group 'emacs-everywhere)
(defcustom emacs-everywhere-app-info-function
(pcase emacs-everywhere--display-server
(`(quartz . ,_) #'emacs-everywhere--app-info-osx)
(`(windows . ,_) #'emacs-everywhere--app-info-windows)
(`(x11 . ,_) #'emacs-everywhere--app-info-linux-x11)
(`(wayland . KDE) #'emacs-everywhere--app-info-linux-kde))
"Function that asks the system for information on the current foreground app.
On most systems, this should be set to a sensible default, but it
may not be set on less common configurations. If unset, a custom
app-info function can be used — see the various
emacs-everywhere--app-info-* functions for reference."
:type 'function
:group 'emacs-everywhere)
;; Semi-internal variables
(defconst emacs-everywhere-osascript-accessibility-error-message
"osascript is not allowed assistive access"
"String to search for to determine if Emacs does not have accessibility rights.")
(defvar-local emacs-everywhere-current-app nil
"The current `emacs-everywhere-app'.")
;; Prevents buffer-local variable from being unset by major mode changes
(put 'emacs-everywhere-current-app 'permanent-local t)
(defvar-local emacs-everywhere--contents nil)
;; Make the byte-compiler happier
(declare-function org-in-src-block-p "org")
(declare-function org-ctrl-c-ctrl-c "org")
(declare-function org-export-to-buffer "ox")
(declare-function evil-insert-state "evil-states")
(declare-function spell-fu-buffer "spell-fu")
(declare-function markdown-mode "markdown-mode")
(declare-function w32-shell-execute "w32fns.c")
;;; Primary functionality
;;;###autoload
(defun emacs-everywhere (&optional file line column)
"Launch the emacs-everywhere frame from emacsclient.
This may open FILE at a particular LINE and COLUMN, if specified."
(let* ((app-info (emacs-everywhere-app-info))
(param (emacs-everywhere-command-param app-info file line column))
(param-string (combine-and-quote-strings param)))
(pcase system-type
((or 'ms-dos 'windows-nt 'cygwin)
(w32-shell-execute "open" "emacsclientw" param-string 1))
(_ (apply #'call-process "emacsclient" nil 0 nil param)))))
(defun emacs-everywhere-command-param (app-info &optional file line column)
"Generate arguments for calling emacsclient.
The arguments are based on a particular APP-INFO. Optionally, a FILE can be
specified, and also a particular LINE and COLUMN."
(delq
nil (list
(when (server-running-p)
(if server-use-tcp
(concat "--server-file="
(if (memq system-type '(ms-dos windows-nt cygwin))
(expand-file-name server-name server-auth-dir)
(shell-quote-argument
(expand-file-name server-name server-auth-dir))))
(concat "--socket-name="
(if (memq system-type '(ms-dos windows-nt cygwin))
(expand-file-name server-name server-auth-dir)
(shell-quote-argument
(expand-file-name server-name server-socket-dir))))))
"-c" "-F"
(prin1-to-string
(cons (cons 'emacs-everywhere-app app-info)
emacs-everywhere-frame-parameters))
(cond ((and line column) (format "+%d:%d" line column))
(line (format "+%d" line)))
(or file
(expand-file-name
(funcall emacs-everywhere-filename-function app-info)
emacs-everywhere-file-dir)))))
(defun emacs-everywhere-file-p (file)
"Return non-nil if FILE should be handled by emacs-everywhere.
This matches FILE against `emacs-everywhere-file-patterns'."
(let ((file (file-truename file)))
(cl-some (lambda (pattern) (string-match-p pattern file))
emacs-everywhere-file-patterns)))
;;;###autoload
(defun emacs-everywhere-initialise ()
"Entry point for the executable.
APP is an `emacs-everywhere-app' struct."
(let ((file (buffer-file-name (buffer-base-buffer))))
(when (and file (emacs-everywhere-file-p file))
(let ((app (or (frame-parameter nil 'emacs-everywhere-app)
(emacs-everywhere-app-info))))
(setq-local emacs-everywhere-current-app app)
(with-demoted-errors "Emacs Everywhere: error running init hooks, %s"
(run-hooks 'emacs-everywhere-init-hooks))
(emacs-everywhere-mode 1)
(setq emacs-everywhere--contents (buffer-string))))))
;;;###autoload
(add-hook 'server-visit-hook #'emacs-everywhere-initialise)
(add-hook 'server-done-hook #'emacs-everywhere-finish)
(defvar emacs-everywhere--initial-mode-map
(let ((keymap (make-sparse-keymap)))
(define-key keymap (kbd "DEL") #'emacs-everywhere--erase-buffer)
(define-key keymap (kbd "C-SPC") #'emacs-everywhere--erase-buffer)
keymap)
"Transient keymap invoked when an emacs-everywhere buffer is first created.
Set to nil to prevent this transient map from activating in emacs-everywhere
buffers.")
(define-minor-mode emacs-everywhere-mode
"Tweak the current buffer to add some emacs-everywhere considerations."
:init-value nil
:lighter " EE"
:keymap `((,(kbd "C-c C-c") . emacs-everywhere--finish-or-ctrl-c-ctrl-c)
(,(kbd "C-x 5 0") . emacs-everywhere-finish)
(,(kbd "C-c C-k") . emacs-everywhere-abort))
(when emacs-everywhere-mode
;; line breaking
(turn-off-auto-fill)
(visual-line-mode t)
;; DEL/C-SPC to clear (first keystroke only)
(when (keymapp emacs-everywhere--initial-mode-map)
(set-transient-map emacs-everywhere--initial-mode-map))
;; Header line
(when emacs-everywhere-top-padding
(setq-local header-line-format "")
(face-remap-set-base
'header-line (list :height emacs-everywhere-top-padding)))
;; Replace "When done with a buffer type 'C-x #'" message
(run-at-time
nil nil
(lambda ()
(message "When done with this buffer type %s (or %s to abort)"
(propertize "C-c C-c" 'face 'help-key-binding)
(propertize "C-c C-k" 'face 'help-key-binding))))))
(defun emacs-everywhere-apply-major-mode ()
"Call `emacs-everywhere-major-mode-function'."
(funcall emacs-everywhere-major-mode-function))
(defun emacs-everywhere--erase-buffer ()
"Delete the contents of the current buffer."
(interactive)
(delete-region (point-min) (point-max)))
(defun emacs-everywhere--finish-or-ctrl-c-ctrl-c ()
"Finish emacs-everywhere session or invoke `org-ctrl-c-ctrl-c' in `org-mode'."
(interactive)
(if (and (eq major-mode 'org-mode)
(org-in-src-block-p))
(org-ctrl-c-ctrl-c)
(emacs-everywhere-finish)))
(defun emacs-everywhere-finish (&optional abort)
"Copy buffer content, close emacs-everywhere window, and maybe paste.
Must only be called within a emacs-everywhere buffer.
Never paste content when ABORT is non-nil."
(interactive)
(when emacs-everywhere-mode
(when (equal emacs-everywhere--contents (buffer-string))
(setq abort t))
(unless abort
(run-hooks 'emacs-everywhere-final-hooks)
(gui-select-text (buffer-string))
(gui-backend-set-selection 'PRIMARY (buffer-string))
(when emacs-everywhere-copy-command ; handle clipboard finicklyness
(let ((inhibit-message t)
(require-final-newline nil)
write-file-functions)
(with-file-modes #o600
(write-file buffer-file-name))
(apply #'call-process (car emacs-everywhere-copy-command)
nil nil nil
(mapcar (lambda (arg)
(replace-regexp-in-string "%f" buffer-file-name arg))
(cdr emacs-everywhere-copy-command))))))
(sleep-for emacs-everywhere-clipboard-sleep-delay) ; prevents weird multi-second pause, lets clipboard info propagate
(when emacs-everywhere-window-focus-command
(let ((window-id (emacs-everywhere-app-id emacs-everywhere-current-app)))
(apply #'call-process (car emacs-everywhere-window-focus-command)
nil nil nil
(mapcar (lambda (arg)
(replace-regexp-in-string "%w" window-id arg))
(cdr emacs-everywhere-window-focus-command)))
;; The frame only has this parameter if this package initialized the temp
;; file its displaying. Otherwise, it was created by another program, likely
;; a browser with direct EDITOR support, like qutebrowser.
(when (and (frame-parameter nil 'emacs-everywhere-app)
emacs-everywhere-paste-command
(not abort))
(apply #'call-process (car emacs-everywhere-paste-command)
nil nil nil (cdr emacs-everywhere-paste-command)))))
;; Clean up after ourselves in case the buffer survives `server-buffer-done'
;; (b/c `server-existing-buffer' is non-nil).
(emacs-everywhere-mode -1)
(server-buffer-done (current-buffer))))
(defun emacs-everywhere-abort ()
"Abort current emacs-everywhere session."
(interactive)
(set-buffer-modified-p nil)
(emacs-everywhere-finish t))
;;; Window info
(cl-defstruct emacs-everywhere-app
"Metadata about the last focused window before emacs-everywhere was invoked."
id class title geometry)
(defun emacs-everywhere-app-info ()
"Return information on the active window.
This runs `emacs-everywhere-app-info-function' and lightly reformats the app title."
(if (functionp emacs-everywhere-app-info-function)
(let ((w (funcall emacs-everywhere-app-info-function)))
(setf (emacs-everywhere-app-title w)
(replace-regexp-in-string
(format " ?-[A-Za-z0-9 ]*%s"
(regexp-quote (emacs-everywhere-app-class w)))
""
(replace-regexp-in-string
"[^[:ascii:]]+" "-" (emacs-everywhere-app-title w))))
w)
(user-error "No app-info function is set, see `emacs-everywhere-app-info-function'")))
(defun emacs-everywhere--call (command &rest args)
"Execute COMMAND with ARGS synchronously."
(with-temp-buffer
(apply #'call-process command nil t nil (remq nil args))
(when (and (eq system-type 'darwin)
(string-match-p emacs-everywhere-osascript-accessibility-error-message (buffer-string)))
(call-process "osascript" nil nil nil
"-e" (format "display alert \"emacs-everywhere\" message \"Emacs has not been granted accessibility permissions, cannot run emacs-everywhere!
Please go to 'System Preferences > Security & Privacy > Privacy > Accessibility' and allow Emacs.\"" ))
(error "MacOS accessibility error, aborting"))
(string-trim (buffer-string))))
(defun emacs-everywhere--app-info-linux ()
"Return information on the active window, on Linux."
(pcase emacs-everywhere--display-server
(`(x11 . ,_) (emacs-everywhere--app-info-linux-x11))
(`(wayland . KDE) (emacs-everywhere--app-info-linux-kde))
(_ (user-error "Unable to fetch app info with display server %S" emacs-everywhere--display-server))))
(defun emacs-everywhere--app-info-linux-x11 ()
"Return information on the current active window, on a Linux X11 sessions."
(let ((window-id (emacs-everywhere--call "xdotool" "getactivewindow")))
(let ((app-name
(car (split-string-and-unquote
(string-trim-left
(emacs-everywhere--call "xprop" "-id" window-id "WM_CLASS")
"[^ ]+ = \"[^\"]+\", "))))
(window-title
(car (split-string-and-unquote
(string-trim-left
(emacs-everywhere--call "xprop" "-id" window-id "_NET_WM_NAME")
"[^ ]+ = "))))
(window-geometry
(let ((info (mapcar (lambda (line)
(split-string line ":" nil "[ \t]+"))
(split-string
(emacs-everywhere--call "xwininfo" "-id" window-id) "\n"))))
(mapcar #'string-to-number
(list (cadr (assoc "Absolute upper-left X" info))
(cadr (assoc "Absolute upper-left Y" info))
(cadr (assoc "Relative upper-left X" info))
(cadr (assoc "Relative upper-left Y" info))
(cadr (assoc "Width" info))
(cadr (assoc "Height" info)))))))
(setq window-geometry
(list
(if (= (nth 0 window-geometry) (nth 2 window-geometry))
(nth 0 window-geometry)
(- (nth 0 window-geometry) (nth 2 window-geometry)))
(if (= (nth 1 window-geometry) (nth 3 window-geometry))
(nth 1 window-geometry)
(- (nth 1 window-geometry) (nth 3 window-geometry)))
(nth 4 window-geometry)
(nth 5 window-geometry)))
(make-emacs-everywhere-app
:id window-id
:class app-name
:title window-title
:geometry window-geometry))))
(defun emacs-everywhere--app-info-linux-kde ()
"Return information on the current active window, on a Linux KDE sessions."
(let ((window-id (emacs-everywhere--call "kdotool" "getactivewindow")))
(let ((app-name
(car (last (split-string (emacs-everywhere--call "kdotool" "getwindowclassname" window-id) "\\."))))
(window-title
(emacs-everywhere--call "kdotool" "getwindowname" window-id))
(window-geometry
(let ((geom (mapcar
(lambda (line) (split-string line "[:,x] ?"))
(split-string (string-trim-left
(emacs-everywhere--call "kdotool" "getwindowgeometry" window-id) "[^P]+")
"\n" nil " +"))))
(mapcar #'string-to-number
(list (cadr (assoc "Position" geom))
(caddr (assoc "Position" geom))
(cadr (assoc "Geometry" geom))
(caddr (assoc "Geometry" geom)))))))
(make-emacs-everywhere-app
:id window-id
:class app-name
:title window-title
:geometry window-geometry))))
(defvar emacs-everywhere--dir (file-name-directory load-file-name))
(defun emacs-everywhere--app-info-osx ()
"Return information on the active window, on osx."
(emacs-everywhere--ensure-oscascript-compiled)
(let ((default-directory emacs-everywhere--dir))
(let ((app-name (emacs-everywhere--call
"osascript" "app-name"))
(window-title (emacs-everywhere--call
"osascript" "window-title"))
(window-geometry (mapcar #'string-to-number
(split-string
(emacs-everywhere--call
"osascript" "window-geometry") ", "))))
(make-emacs-everywhere-app
:id app-name
:class app-name
:title window-title
:geometry window-geometry))))
(defun emacs-everywhere--ensure-oscascript-compiled (&optional force)
"Ensure that compiled oscascript files are present.
Will always compile when FORCE is non-nil."
(unless (and (file-exists-p "app-name")
(file-exists-p "window-geometry")
(file-exists-p "window-title")
(not force))
(let ((default-directory emacs-everywhere--dir)
(app-name
"tell application \"System Events\"
set frontAppName to name of first application process whose frontmost is true
end tell
return frontAppName")
(window-geometry
"tell application \"System Events\"
set frontWindow to front window of (first application process whose frontmost is true)
set windowPosition to (get position of frontWindow)
set windowSize to (get size of frontWindow)
end tell
return windowPosition & windowSize")
(window-title
"set windowTitle to \"\"
tell application \"System Events\"
set frontAppProcess to first application process whose frontmost is true
end tell
tell frontAppProcess
if count of windows > 0 then
set windowTitle to name of front window
end if
end tell
return windowTitle"))
(dolist (script `(("app-name" . ,app-name)
("window-geometry" . ,window-geometry)
("window-title" . ,window-title)))
(write-region (cdr script) nil (concat (car script) ".applescript"))
(shell-command (format "osacompile -r scpt:128 -t osas -o %s %s"
(car script) (concat (car script) ".applescript")))))))
(defun emacs-everywhere--app-info-windows ()
"Return information on the active window, on Windows."
(let* ((window-id (emacs-everywhere--call
"powershell"
"-NoProfile"
"-Command"
"& {Add-Type 'using System; using System.Runtime.InteropServices; public class Tricks { [DllImport(\"user32.dll\")] public static extern IntPtr GetForegroundWindow(); }'; [tricks]::GetForegroundWindow() }"))
(window-title (emacs-everywhere--call
"powershell"
"-NoProfile"
"-Command"
(format "& {Add-Type 'using System; using System.Runtime.InteropServices; public class Tricks { [DllImport(\"user32.dll\")] public static extern int GetWindowText(IntPtr hWnd, System.Text.StringBuilder text, int count); [DllImport(\"user32.dll\")] public static extern int GetWindowTextLength(IntPtr hWnd); }'; $length = ([tricks]::GetWindowTextLength(%s)); $sb = New-Object System.Text.StringBuilder $length; [tricks]::GetWindowText(%s, $sb, $length + 1) > $null; $sb.ToString() }" window-id window-id)))
(window-class (emacs-everywhere--call
"powershell"
"-NoProfile"
"-Command"
(format "(Get-Item (Get-Process | ? { $_.mainwindowhandle -eq %s }).Path).VersionInfo.ProductName" window-id)))
(window-geometry (split-string
(emacs-everywhere--call
"powershell"
"-NoProfile"
"-Command"
(format "& {Add-Type 'using System; using System.Runtime.InteropServices; public struct tagRECT { public int left; public int top; public int right; public int bottom; } public class Tricks { [DllImport(\"user32.dll\")] public static extern int GetWindowRect(IntPtr hWnd, out tagRECT lpRect); }'; $rect = New-Object -TypeName tagRECT; [tricks]::GetWindowRect(%s, [ref]$rect) > $null; $rect.left; $rect.top; $rect.right - $rect.left; $rect.bottom - $rect.top }" window-id)))))
(make-emacs-everywhere-app
:id window-id
:class window-class
:title window-title
:geometry window-geometry)))
;;; Secondary functionality
(defun emacs-everywhere-set-frame-name ()
"Set the frame name based on `emacs-everywhere-frame-name-format'."
(set-frame-name
(format emacs-everywhere-frame-name-format
(emacs-everywhere-app-class emacs-everywhere-current-app)
(truncate-string-to-width
(emacs-everywhere-app-title emacs-everywhere-current-app)
45 nil nil "…"))))
(defun emacs-everywhere-remove-trailing-whitespace ()
"Move point to the end of the buffer, and remove all trailing whitespace."
(goto-char (max-char))
(delete-trailing-whitespace)
(delete-char (- (skip-chars-backward "\n"))))
(defun emacs-everywhere-set-frame-position ()
"Set the size and position of the emacs-everywhere frame."
(cl-destructuring-bind (x . y) (mouse-absolute-pixel-position)
(set-frame-position (selected-frame)
(- x 100)
(- y 50))))
(defun emacs-everywhere-insert-selection--windows ()
"Insert selection on MS-Windows by simulating C-c and C-v."
(let ((window-id (emacs-everywhere-app-id emacs-everywhere-current-app))
(emacs-window-id (emacs-everywhere--call
"powershell"
"-NoProfile"
"-Command"
"& {Add-Type 'using System; using System.Runtime.InteropServices; public class Tricks { [DllImport(\"user32.dll\")] public static extern IntPtr GetForegroundWindow(); }'; [tricks]::GetForegroundWindow() }")))
(apply #'call-process (car emacs-everywhere-window-focus-command)
nil nil nil
(mapcar (lambda (arg)
(replace-regexp-in-string "%w" window-id arg))
(cdr emacs-everywhere-window-focus-command)))
(apply #'call-process "powershell"
nil nil nil '("-NoProfile" "-Command" "& {(New-Object -ComObject wscript.shell).SendKeys('^c')}"))
(apply #'call-process (car emacs-everywhere-window-focus-command)
nil nil nil
(mapcar (lambda (arg)
(replace-regexp-in-string "%w" emacs-window-id arg))
(cdr emacs-everywhere-window-focus-command))))
(yank))
(defun emacs-everywhere-insert-selection ()
"Insert the last text selection into the buffer."
(pcase system-type
('darwin (progn
(call-process "osascript" nil nil nil
"-e" "tell application \"System Events\" to keystroke \"c\" using command down")
(sleep-for emacs-everywhere-clipboard-sleep-delay) ; lets clipboard info propagate
(yank)))
((or 'ms-dos 'windows-nt 'cygwin)
(emacs-everywhere-insert-selection--windows))
(_ (when-let ((selection (gui-get-selection 'PRIMARY 'UTF8_STRING)))
(gui-backend-set-selection 'PRIMARY "")
(insert selection))))
(when (and (eq major-mode 'org-mode)
(emacs-everywhere-markdown-p)
(executable-find "pandoc"))
(apply #'call-process-region
(point-min) (point-max) "pandoc"
nil nil nil
emacs-everywhere-pandoc-md-args)
(deactivate-mark) (goto-char (point-max)))
(cond ((bound-and-true-p evil-local-mode) (evil-insert-state))))
(defun emacs-everywhere-init-spell-check ()
"Run a spell check function on the buffer, using a relevant enabled mode."
(cond ((bound-and-true-p spell-fu-mode) (spell-fu-buffer))
((bound-and-true-p flyspell-mode) (flyspell-buffer))))
(defun emacs-everywhere-markdown-p ()
"Return t if the original window is recognised as markdown-flavoured."
(let ((title (emacs-everywhere-app-title emacs-everywhere-current-app))
(class (emacs-everywhere-app-class emacs-everywhere-current-app)))
(or (cl-some (lambda (pattern)
(string-match-p pattern title))
emacs-everywhere-markdown-windows)
(cl-some (lambda (pattern)
(string-match-p pattern class))
emacs-everywhere-markdown-apps))))
(defun emacs-everywhere-major-mode-org-or-markdown ()
"Use markdow-mode, when window is recognised as markdown-flavoured.
Otherwise use `org-mode'."
(if (emacs-everywhere-markdown-p)
(markdown-mode)
(org-mode)))
(defcustom emacs-everywhere-org-export-options
"#+property: header-args :exports both
#+options: toc:nil\n"
"A string inserted at the top of the Org buffer prior to export.
This is with the purpose of setting #+property and #+options parameters.
Should end in a newline to avoid interfering with the buffer content."
:type 'string
:group 'emacs-everywhere)
(defvar org-export-show-temporary-export-buffer)
(defun emacs-everywhere-convert-org-to-gfm ()
"When appropriate, convert org buffer to markdown."
(when (and (eq major-mode 'org-mode)
(emacs-everywhere-markdown-p))
(goto-char (point-min))
(insert emacs-everywhere-org-export-options)
(let (org-export-show-temporary-export-buffer)
(require 'ox-md)
(org-export-to-buffer (if (featurep 'ox-gfm) 'gfm 'md) (current-buffer)))))
(defun emacs-everywhere--required-executables ()
"Return a list of cons cells, each giving a required executable and its purpose."
(let* ((var-cmds
(list (cons "paste" emacs-everywhere-paste-command)
(cons "copy" emacs-everywhere-copy-command)
(cons "focus window" emacs-everywhere-window-focus-command)
(list "pandoc conversion" "pandoc")))
(de-cmds
(pcase emacs-everywhere--display-server
(`(x11 . ,_)
(list (list "app info" "xdotool")
(list "app info" "xprop")
(list "app info" "xwininfo")))
(`(wayland . KDE)
(list (list "app info" "kdotool")))))
(feat-cmds
(append var-cmds de-cmds))
executable-list)
(dolist (feat-cmd (delq nil feat-cmds))
(when (cdr feat-cmd)
(when (and (equal (cadr feat-cmd) "sh")
(equal (caddr feat-cmd) "-c"))
(setcdr feat-cmd (split-string (cadddr feat-cmd))))
(push (cons (cadr feat-cmd) (car feat-cmd))
executable-list)))
executable-list))
(defun emacs-everywhere-check-health ()
"Check whether emacs-everywhere has everything it needs."
(interactive)
(switch-to-buffer
(get-buffer-create "*Emacs Everywhere health check*"))
(read-only-mode 1)
(with-silent-modifications
(erase-buffer)
(let ((required-cmds
(emacs-everywhere--required-executables)))
(insert (propertize "Emacs Everywhere system health check\n" 'face 'outline-1)
"operating system: " (propertize (symbol-name system-type) 'face 'font-lock-type-face)
", display server: " (propertize (symbol-name (car emacs-everywhere--display-server)) 'face 'font-lock-type-face)
(and (cdr emacs-everywhere--display-server)
(concat "/" (propertize (symbol-name (cdr emacs-everywhere--display-server)) 'face 'font-lock-type-face)))
"\n")
(dolist (req-cmd required-cmds)
(if (not (cdr req-cmd))
(insert
(propertize (format "• %s (unavailible)\n" (cdr req-cmd)) 'face 'font-lock-comment-face))
(insert
(propertize (format "• %s " (cdr req-cmd))
'face `(:inherit outline-4 :height ,(face-attribute 'default :height)))
"requires "
(propertize (car req-cmd) 'face 'font-lock-constant-face)
(if (executable-find (car req-cmd))
(propertize " ✓ installed" 'face 'success)
(propertize " ✗ missing" 'face 'error))
"\n"))))))
(provide 'emacs-everywhere)
;;; emacs-everywhere.el ends here