diff --git a/init.el b/init.el index 540611c..8ef3624 100644 --- a/init.el +++ b/init.el @@ -865,6 +865,202 @@ :config (setq prettier-js-use-modules-bin t)) +;; Emacs desktop environment +; Emacs Wayland Manager (EWM) +(when (and (executable-find "ewm-launch") (string= (getenv "XDG_SESSION_DESKTOP") "ewm")) + ; Dynamic Tab (Workspace) Setup + (use-package tab-line + :ensure nil) + (setq tab-bar-show 1) ; Hide the bar if only 1 tab exists + (setq tab-bar-new-tab-choice "*scratch*") + + (defun dynamic-tab-ensure-scratch-tab () + "Ensures there is always a tab dedicated to *scratch*." + (let* ((tabs (funcall tab-bar-tabs-function)) + (tab-names (mapcar (lambda (tab) (alist-get 'name tab)) tabs)) + (spare-exists (member "dynamic *scratch*" tab-names)) + (is-child-frame (frame-parent))) + + ;; If we are currently IN the spare tab and it's not *scratch* anymore, + ;; or if we just want to ensure it exists at the end: + (unless (or spare-exists is-child-frame) + (let ((current-tab (tab-bar--current-tab-index))) + (tab-bar-new-tab 999) + (tab-bar-rename-tab "dynamic *scratch*") + ;; Return to original tab if we were just initializing + (tab-bar-select-tab (1+ current-tab)))))) + + (defun dynamic-tab-handle-tab-persistence () + "Create new tab if current scratch tab is being used." + (when (string= (alist-get 'name (tab-bar--current-tab)) "dynamic *scratch*") + (unless (or (string= (buffer-name) "*scratch*") (minibufferp)) + ;; User switched away from scratch in the spare tab, rename it + (tab-bar-rename-tab ""))) + ;; and create a new spare. + (dynamic-tab-ensure-scratch-tab)) + + (defvar already-redirecting-p nil) + (defun dynamic-tab-redirect-to-scratch-tab (&rest _) + "Redirect to dedicated scratch tab if scratch buffer accessed on other tab." + (when (and (not already-redirecting-p) + (string= (buffer-name) "*scratch*") + (not (string= (alist-get 'name (tab-bar--current-tab)) "dynamic *scratch*"))) + (setq already-redirecting-p t) + (unwind-protect + (switch-to-buffer nil) + (tab-bar-select-tab-by-name "dynamic *scratch*") + (setq already-redirecting-p nil)))) + + (defun dynamic-tab-handle-buffer-check-and-close-tab () + "Check if we need to close the tab and action accordingly." + (let* ((is-last-tab (<= (length (tab-bar-tabs)) 2)) + (is-last-buffer (<= (length (tab-line-tabs-window-buffers)) 2))) + (when (and (not is-last-tab) is-last-buffer) + (tab-close)))) + + (defun dynamic-tab-handle-tab-cleanup-kill (orig-fun &rest args) + "Free tab if all reserved in use buffers for that tab killed." + (let* ((is-last-tab (<= (length (tab-bar-tabs)) 2)) + (is-child-buffer (frame-parent)) + (is-mini-buffer (minibufferp)) + (is-dynamic-buffer (string= (alist-get 'name (tab-bar--current-tab)) "dynamic *scratch*")) + (is-scratch-buffer (string= (alist-get 'name (tab-bar--current-tab)) "*scratch*")) + (is-last-buffer (<= (length (tab-line-tabs-window-buffers)) 2)) + (is-overriden (nth 1 args)) + (is-target-focused (and (eq (current-buffer) (car args)))) + (is-called-interactively (called-interactively-p 'any))) + + (cond + ((and (not is-overriden) (not is-called-interactively)) (apply orig-fun args)) + (is-child-buffer nil) + (is-mini-buffer nil) + (is-dynamic-buffer nil) + (is-scratch-buffer nil) + ((and (not is-overriden) is-called-interactively) (apply orig-fun (nbutlast args))) + + ((and is-last-tab is-last-buffer) (apply orig-fun (nbutlast args))) + ((and is-last-tab (not is-last-buffer)) (apply orig-fun (nbutlast args))) + ;((and is-last-tab (not is-last-buffer) (bury-buffer))) + + ((and (not is-last-tab) (not is-last-buffer) (bury-buffer))) + + (t nil)) + + (when (and (not is-overriden) is-target-focused) + (dynamic-tab-handle-buffer-check-and-close-tab)))) + + (defun dynamic-tab-handle-tab-cleanup-close (&rest _) + "Free tab if last window open is attempted to be closed." + (if (and (> (length (tab-bar-tabs)) 2) + (= (length (window-list)) 1) + (not (string= (alist-get 'name (tab-bar--current-tab)) "dynamic *scratch*"))) + (tab-close) + t)) + + ;; Helper functions for EWM + ; Switch monitor + (defun ewm-switch-to-monitor (target) + (interactive) + (select-frame-set-input-focus (car (last (seq-filter (lambda (f) + (string= target + (cdr (assq 'name (frame-monitor-attributes f))))) + (frame-list)))))) + + ; EWM - Emacs Wayland Manager + (use-package ewm + :ensure nil + :custom + (ewm-output-config + '(("DP-1" + :width 1920 + :height 1080 + :scale 1.0 + :x 0 + :y 0 + :refresh 240) + ("HDMI-A-1" + :width 1920 + :height 1080 + :scale 1.0 + :x 1920 + :y 0 + :transform 3 + :refresh 75))) + (ewm-input-config + '((touchpad :natural-scroll t :tap t :dwt t) + (mouse :accel-profile "flat") + (keyboard :repeat-delay 150 :repeat-rate 35 + :xkb-layouts "gb"))) + ;; Per-device override (exact name from libinput) + ;("ELAN0676:00 04F3:3195 Touchpad" :tap nil :accel-speed -0.2))) + :bind (:map ewm-mode-map + ("M-d" . ewm-launch-app) + ("s-l" . ewm-lock-session) + ("M-" . eshell) + ("" . (lambda () + (interactive) + (start-process-shell-command + "screenshot-process" + nil + "grim -g \"$(slurp)\" - | wl-copy"))) + ("M-w" . (lambda () + (interactive) + (kill-buffer))) + + ; Monitor switching, find current focused monitor name via + ;(cdr (assq 'name (frame-monitor-attributes)) + ("M-o" . (lambda () + (interactive) + (ewm-switch-to-monitor "XG2431"))) + ("M-p" . (lambda () + (interactive) + (ewm-switch-to-monitor "24B2W1G5"))) + + ;;("M-f" . (lambda () + ;; (interactive) + ;; (start-process-shell-command + ;; "wlkbptr-process" + ;; nil + ;; "wl-kbptr -o modes=floating,click -o mode_floating.source=detect")) + + ("M-h" . tab-bar-switch-to-prev-tab) + ("M-l" . tab-bar-switch-to-next-tab) + ("M-k" . windmove-up) + ("M-j" . windmove-down)) + :config + (add-to-list 'ewm-intercept-prefixes ?\M-d) + (add-to-list 'ewm-intercept-prefixes ?\M-w) + (add-to-list 'ewm-intercept-prefixes ?\M-o) + (add-to-list 'ewm-intercept-prefixes ?\M-p) + (add-to-list 'ewm-intercept-prefixes ?\M-h) + (add-to-list 'ewm-intercept-prefixes ?\M-j) + (add-to-list 'ewm-intercept-prefixes ?\M-k) + (add-to-list 'ewm-intercept-prefixes ?\M-l) + :init + ;; Dynamic Tabs/Workspaces setup + ; Hooks + (advice-add 'consult-buffer + :after (lambda (&rest _) + (dynamic-tab-handle-tab-persistence) + (dynamic-tab-redirect-to-scratch-tab))) + (advice-add 'delete-window :before-while #'dynamic-tab-handle-tab-cleanup-close) + (advice-add 'kill-buffer :around #'dynamic-tab-handle-tab-cleanup-kill) + ;(add-hook 'buffer-list-update-hook #'dynamic-tab-redirect-to-scratch-tab) + (add-hook 'window-configuration-change-hook #'dynamic-tab-handle-tab-persistence) + ; Initialize on startup + (dynamic-tab-ensure-scratch-tab)) + + ; Desktop notifications in Emacs + (use-package ednc + :ensure t + :init + (ednc-mode))) + ;(use-package ednc-popup + ; :ensure (:host git :repo "https://codeberg.org/akib/emacs-ednc-popup.git") + ; :after ednc + ; :config + ; (add-hook 'ednc-notification-presentation-functions + ; #'ednc-popup-presentation-function)) (custom-set-variables ;; custom-set-variables was added by Custom.