;; -*- coding: utf-8; lexical-binding: t; -*- ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; open file ;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (defun creature/open-init-file () "Open init file." (interactive) (find-file (expand-file-name "init.el" creature/config-dir))) (defun creature/open-in-external-app (file-path) (if (eq system-type 'windows-nt) (w32-shell-execute "open" (replace-regexp-in-string "/" "\\\\" file-path)) (call-process "xdg-open" nil 0 nil file-path))) (defun creature/open-file-or-directory-in-external-app (arg) (interactive "P") (if arg (creature/open-in-external-app (expand-file-name default-directory)) (let ((file-path (if (derived-mode-p 'dired-mode) (dired-get-file-for-visit) buffer-file-name))) (if file-path (creature/open-in-external-app file-path) (message "No file associated to this buffer"))))) (defun creature/sudo-edit (&optional arg) (interactive "P") (let ((fname (if (or arg (not buffer-file-name)) (read-file-name "File: ") buffer-file-name))) (find-file (cond ((string-match-p "^/ssh:" fname) (with-temp-buffer (insert fname) (search-backward ":") (let ((last-match-end nil) (last-ssh-hostname nil)) (while (string-match "@\\\([^:|]+\\\)" fname last-match-end) (setq last-ssh-hostname (or (match-string 1 fname) last-ssh-hostname)) (setq last-match-end (match-end 0))) (insert (format "|sudo:%s" (or last-ssh-hostname "localhost")))) (buffer-string))) (t (concat "/sudo:root@localhost:" fname)))))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; mpv ;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (defun creature/file-mime-type (file &optional deref-symlinks) "Return FILE's MIME type as reported by the external `file' program. When DEREF-SYMLINKS is non-nil and FILE is a symbolic link, inspect the link target instead of the link itself. Signal a `user-error' if FILE does not exist, is unreadable, or its MIME type cannot be determined." (unless (executable-find "file") (user-error "MIME detection requires the `file' program")) (unless (file-exists-p file) (user-error "File does not exist: %s" file)) (unless (file-readable-p file) (user-error "File is not readable: %s" file)) (let ((args (append (when deref-symlinks '("-L")) '("--brief" "--mime-type" "--") (list file))) (process-file-side-effects nil)) (with-temp-buffer (let* ((status (apply #'process-file "file" nil t nil args)) (mime-type (replace-regexp-in-string "\n\\'" "" (buffer-string)))) (unless (eq status 0) (user-error "Failed to detect MIME type for %s: %s" file (if (string-empty-p mime-type) (format "`file' exited with status %s" status) mime-type))) (when (string-empty-p mime-type) (user-error "Empty MIME type for %s" file)) mime-type)))) (defun creature/7z-executable () "Return the available 7-Zip executable path, or nil if not found." (or (executable-find "7z") (executable-find "7zz"))) (defun creature/image-mime-type-p (mime-type) "Return t when MIME-TYPE names an optical disc image format. This accepts the MIME types returned by `file' for ISO 9660 and UDF images, plus the corresponding shared-mime-info aliases." (not (null (member mime-type '("application/x-udf-image" "application/x-iso9660-image" "application/vnd.efi.iso" "application/x-cd-image"))))) (defun creature/image-files (image) "Return a list of file paths stored in IMAGE using `7z l -slt'. Directory entries are excluded from the returned list." (let ((7z-exe (creature/7z-executable))) (unless 7z-exe (user-error "Listing image contents requires `7z' or `7zz'")) (unless (file-exists-p image) (user-error "Image does not exist: %s" image)) (unless (file-readable-p image) (user-error "Image is not readable: %s" image)) (let ((process-file-side-effects nil)) (with-temp-buffer (let ((status (process-file 7z-exe nil t nil "l" "-slt" "--" image)) (current-path nil) files) (unless (eq status 0) (user-error "Failed to list image contents for %s" image)) (goto-char (point-min)) (while (not (eobp)) (cond ((looking-at "^Path = \\(.*\\)$") (setq current-path (match-string 1))) ((looking-at "^Folder = -$") (when current-path (push current-path files)))) (forward-line 1)) (nreverse files)))))) (defun creature/bluray-iso-p (image) "Return non-nil when IMAGE looks like a Blu-ray ISO." (let (has-index has-movie-object has-playlist has-stream) (dolist (path (creature/image-files image)) (cond ((string= path "BDMV/index.bdmv") (setq has-index t)) ((string= path "BDMV/MovieObject.bdmv") (setq has-movie-object t)) ((and (string-prefix-p "BDMV/PLAYLIST/" path) (string-suffix-p ".mpls" path)) (setq has-playlist t)) ((and (string-prefix-p "BDMV/STREAM/" path) (string-suffix-p ".m2ts" path)) (setq has-stream t)))) (and has-index has-movie-object has-playlist has-stream))) (defun creature/bluray-directory-p (dir) "Return non-nil when DIR looks like a Blu-ray directory tree." (let* ((bdmv-dir (file-name-concat dir "BDMV")) (index-file (file-name-concat bdmv-dir "index.bdmv")) (movie-object-file (file-name-concat bdmv-dir "MovieObject.bdmv")) (playlist-dir (file-name-concat bdmv-dir "PLAYLIST")) (stream-dir (file-name-concat bdmv-dir "STREAM")) (playlist-files (and (file-directory-p playlist-dir) (directory-files playlist-dir t nil t))) (stream-files (and (file-directory-p stream-dir) (directory-files stream-dir t nil t)))) (and (file-regular-p index-file) (file-regular-p movie-object-file) (seq-some (lambda (path) (and (file-regular-p path) (string-suffix-p ".mpls" path t))) playlist-files) (seq-some (lambda (path) (and (file-regular-p path) (string-suffix-p ".m2ts" path t))) stream-files)))) (defun creature/play-with-mpv (filename) "Play local media FILENAME with mpv, including Blu-ray directories and images." (cond ((not (executable-find "mpv")) (user-error "mpv: executable not found")) ((tramp-tramp-file-p filename) (user-error "mpv: cannot play remote file: %s" filename)) ((creature/bluray-directory-p filename) (call-process "mpv" nil 0 nil filename)) (t (let ((mime-type (creature/file-mime-type filename t))) (cond ((string-prefix-p "video/" mime-type t) (call-process "mpv" nil 0 nil filename)) ((string-prefix-p "audio/" mime-type t) (call-process "mpv" nil 0 nil "--no-video" filename)) ((creature/image-mime-type-p mime-type) (if (or (not (creature/7z-executable)) (creature/bluray-iso-p filename)) (call-process "mpv" nil 0 nil "bd://" (concat "--bluray-device=" filename)) (user-error "mpv: unsupported image %s, MIME: %s" filename mime-type))) (t (user-error "mpv: unsupported file %s, MIME: %s" filename mime-type))))))) (defun creature/mpv () (interactive) (let ((filename (if (derived-mode-p 'dired-mode) (dired-get-file-for-visit) (buffer-file-name)))) (if filename (creature/play-with-mpv filename) (user-error "mpv: no file to play")))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; indentation ;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (defvar creature/indent-sensitive-modes '(asm-mode coffee-mode elm-mode haml-mode haskell-mode slim-mode makefile-mode makefile-bsdmake-mode makefile-gmake-mode makefile-imake-mode python-mode yaml-mode) "Modes which disable auto-indenting.") (defun creature/fallback-indent-func () (unless (member major-mode creature/indent-sensitive-modes) (save-excursion (if (region-active-p) (progn (indent-region (region-beginning) (region-end)) (message "Indented selected region.")) (progn (indent-region (point-min) (point-max)) (message "Indented buffer."))) (whitespace-cleanup)))) (defun creature/indent-region-or-buffer () "Indent a region if selected, otherwise the whole buffer." (interactive) (if (and (bound-and-true-p prettier-mode) (bound-and-true-p prettier-version) (not (region-active-p))) (progn (save-excursion (prettier-prettify)) (message "Indented prettier buffer.")) (creature/fallback-indent-func))) (defvar creature/drink-timer nil "Timer for drink water notifition.") (defun creature/start-drink-timer () "Start timer for notice drink water." (interactive) (when (timerp creature/drink-timer) (cancel-timer creature/drink-timer) (setq creature/drink-timer nil)) (setq creature/drink-timer (run-with-timer 1200 1200 (lambda () (require 'notifications) (let ((msg (format-time-string "drink time: %Y-%m-%d %H:%M:%S"))) (notifications-notify :title msg :timeout 0 :app-icon nil)) )))) (defun creature/kill-this-buffer () "Kill the current buffer quietly." (interactive) (kill-buffer (current-buffer))) (provide 'init-func)