From cbce743a980fc3793bdcf134d73dde3b4bf0c64a Mon Sep 17 00:00:00 2001 From: David Shepherd Date: Sun, 29 Jan 2017 09:21:03 +0000 Subject: [PATCH] Use wmctrl to re-pop-up frames which are only visible on other virtual desktops Fixes #2 --- Cask | 1 + frames-only-mode.el | 68 +++++++++++++++++++++++++++++- test/frames-only-mode-test.el | 7 ---- test/test-helper.el | 13 ++++++ test/wmctrl-interactions-test.el | 71 ++++++++++++++++++++++++++++++++ 5 files changed, 151 insertions(+), 9 deletions(-) create mode 100644 test/wmctrl-interactions-test.el diff --git a/Cask b/Cask index c28e54e..1d9357e 100644 --- a/Cask +++ b/Cask @@ -11,6 +11,7 @@ (development (depends-on "ert-runner") + (depends-on "el-mock") (depends-on "f") (depends-on "ecukes") (depends-on "espuds") diff --git a/frames-only-mode.el b/frames-only-mode.el index 0588902..9b9c448 100644 --- a/frames-only-mode.el +++ b/frames-only-mode.el @@ -4,7 +4,7 @@ ;; Author: David Shepherd ;; Version: 1.0.0 -;; Package-Requires: ((emacs "24.4") (dash "2.13.0")) +;; Package-Requires: ((emacs "24.4") (dash "2.13.0") (s "1.11.0")) ;; Keywords: frames, windows ;; URL: https://github.com/davidshepherd7/frames-only-mode @@ -14,6 +14,7 @@ ;;; Code: (require 'dash) +(require 's) @@ -99,6 +100,20 @@ mode please open an issue at https://github.com/davidshepherd7/frames-only-mode/ to let me know." :group 'frames-only-mode) +(defcustom frames-only-mode-reopen-frames-from-hidden-x11-virtual-desktops + ;; TODO: enable by default when this has had a bit more testing + ;; (not (null (and (eq window-system 'x) + ;; (executable-find "wmctrl")))) + nil + "When a frame is visible on a hidden virtual desktop, open a new copy of the frame. + +This will only work under X11 and when you have the wmctrl binary +available on your path (you can probably install wmctrl from your +operating system's package manager). + +It's a bit of a hack, so there may be some issues." + :group 'frames-only-mode) + (defun frames-only-mode-revertable-set (var-vals) @@ -198,6 +213,46 @@ Only if there are no other windows in the frame, and if the buffer is in frames- (defun frames-only-mode-flycheck-display-errors (errors) (message "%s" (mapcar 'flycheck-error-format-message-and-id errors))) + + + +;;; Interactions with wmctrl + +(defun frames-only-mode--call-process (process &rest args) + "Call a process with sensible error handling and output to a string" + (with-output-to-string + (let* ((exit-code (apply #'call-process process nil standard-output nil args))) + (when (not (equal exit-code 0)) + (error "Process %s %s exited with error code %s" process args exit-code))))) + +(defun frames-only-mode--x-current-desktop () + "Get the number of the X11 desktop which is visible" + (--> (frames-only-mode--call-process "wmctrl" "-d") + (s-split "\n" it) + (--map (split-string it "\\s-+") it) + (--first (equal (nth 1 it) "*") it) + (car it))) + +(defun frames-only-mode--x-visible-window-names () + "Get a list of X11 windows which are on the visible desktop." + (let ((current-desktop (frames-only-mode--x-current-desktop))) + (--> (frames-only-mode--call-process "wmctrl" "-l") + (s-split "\n" it) + (--map (s-split-up-to "\\s-+" it 3) it) + (--filter (equal (nth 1 it) current-desktop) it) + (--map (nth 3 it) it)))) + +(defun frames-only-mode--x-buffer-window-visible (buffer-name) + "Check buffer is currently displayed on a visible X11 virtual desktop." + (--some (equal it buffer-name) + (frames-only-mode--x-visible-window-names))) + +(defun frames-only-mode--display-buffer-fn (buffer property-alist) + "See `frames-only-mode-reopen-frames-from-hidden-x11-virtual-desktops'." + (when (and frames-only-mode-reopen-frames-from-hidden-x11-virtual-desktops + (not (frames-only-mode--x-buffer-window-visible (buffer-name buffer)))) + (display-buffer-pop-up-frame buffer property-alist))) + (defvar frames-only-mode-mode-map @@ -243,7 +298,16 @@ Only if there are no other windows in the frame, and if the buffer is in frames- ;; Make sure completions buffer is buried after we are done with the minibuffer (if frames-only-mode (add-hook 'minibuffer-exit-hook #'frames-only-mode-bury-completions) - (remove-hook 'minibuffer-exit-hook #'frames-only-mode-bury-completions))) + (remove-hook 'minibuffer-exit-hook #'frames-only-mode-bury-completions)) + + ;; Set up hacks to pop up new frames for buffers when they are displayed on a + ;; virtual desktop which is not currently visible (X11 only). + (if frames-only-mode + (add-to-list 'display-buffer-alist + (cons "\*compilation\*" (cons #'frames-only-mode--display-buffer-fn nil))) + (setq display-buffer-alist + (--remove (equal (car (cdr it)) #'frames-only-mode--display-buffer-fn) + display-buffer-alist)))) diff --git a/test/frames-only-mode-test.el b/test/frames-only-mode-test.el index c756ea1..3777cb0 100644 --- a/test/frames-only-mode-test.el +++ b/test/frames-only-mode-test.el @@ -7,13 +7,6 @@ (require 'frames-only-mode) -(defmacro with-frames-only-mode (&rest body) - `(unwind-protect - (progn - (frames-only-mode 1) - ,@body) - (frames-only-mode 0))) - (defmacro fom-rollback-test (expr) "Check expr before, during and after toggling frames-only-mode. diff --git a/test/test-helper.el b/test/test-helper.el index 5924164..0eac4e9 100644 --- a/test/test-helper.el +++ b/test/test-helper.el @@ -1,3 +1,16 @@ (require 'f) (require 'frames-only-mode (f-expand "frames-only-mode.el" (f-parent (f-dirname (f-this-file))))) + +;; For el-mock +(eval-when-compile + (require 'cl)) + +(require 'el-mock) + +(defmacro with-frames-only-mode (&rest body) + `(unwind-protect + (progn + (frames-only-mode 1) + ,@body) + (frames-only-mode 0))) diff --git a/test/wmctrl-interactions-test.el b/test/wmctrl-interactions-test.el new file mode 100644 index 0000000..1e97105 --- /dev/null +++ b/test/wmctrl-interactions-test.el @@ -0,0 +1,71 @@ + +(ert-deftest current-desktop () + (with-mock + (mock (frames-only-mode--call-process "wmctrl" "-d") => " +0 - DG: N/A VP: N/A WA: N/A 1 +1 * DG: N/A VP: N/A WA: 1920,0 1920x1200 2 +2 - DG: N/A VP: N/A WA: N/A 3 +3 - DG: N/A VP: N/A WA: N/A dump +") + (should (equal (frames-only-mode--x-current-desktop) "1")))) + + +(ert-deftest visible-windows () + (with-mock + (mock (frames-only-mode--x-current-desktop) => "1") + (mock (frames-only-mode--call-process "wmctrl" "-l") => " +0x0200013c 1 cantor Extended Window Manager Hints - Mozilla Firefox +0x01c08469 1 cantor frames-only-mode.el +0x01c0867d 1 cantor *compilation* +0x01c06151 1 cantor wmctrl-interactions-test.el +0x01c0548c 1 cantor *scratch* +0x04000009 1 cantor x-terminal-emulator +0x0200001e 3 cantor Nowena - Amenra - Google Play Music - Mozilla Firefox +0x01c05f98 3 cantor main.c +") + (should (equal (frames-only-mode--x-visible-window-names) + '("Extended Window Manager Hints - Mozilla Firefox" + "frames-only-mode.el" + "*compilation*" + "wmctrl-interactions-test.el" + "*scratch*" + "x-terminal-emulator" + ))) + )) + + +(ert-deftest compilation-window-display () + (with-frames-only-mode + + ;; This is not running graphically, so we need to override the usual + ;; frames-only-mode setting of 'graphic-only and set pop-up-frames to true. + (let ((pop-up-frames t) + (frames-only-mode-reopen-frames-from-hidden-x11-virtual-desktops t)) + + (get-buffer-create "*compilation*") + + ;; When the compilation buffer is visible + (with-mock + (mock (frames-only-mode--x-buffer-window-visible "*compilation*") => t) + (mock (make-frame *)) + (display-buffer "*compilation*")) + + ;; When the compilation buffer is not currently visible + (with-mock + (mock (frames-only-mode--x-buffer-window-visible "*compilation*") => nil) + (mock (make-frame *)) + (display-buffer "*compilation*"))))) + + +(defun fom/display-buffer-alist-contains-settings () + (--find (equal (car (cdr it)) + #'frames-only-mode--display-buffer-fn) + display-buffer-alist)) + +(ert-deftest settings-reverted-for-compilation-window-display () + (should (not (fom/display-buffer-alist-contains-settings))) + + (with-frames-only-mode + (should (fom/display-buffer-alist-contains-settings))) + + (should (not (fom/display-buffer-alist-contains-settings))))