;;; jao-mullvad.el --- Calling the mullvad cli -*- lexical-binding: t; -*- ;; Copyright (C) 2021 jao ;; Author: jao ;; Keywords: processes ;; This program 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. ;; This program 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 this program. If not, see . ;;; Commentary: ;; Simple helpers to call mullvad and parse its output ;;; Code: (defvar jao-mullvad-mode-map (let ((map (make-keymap))) (suppress-keymap map) (define-key map [?q] 'bury-buffer) (define-key map [?n] 'next-line) (define-key map [?p] 'previous-line) (define-key map [?g] 'jao-mullvad-status) (define-key map [?l] 'jao-mullvad-list) (define-key map [?u] 'jao-mullvad-update-list) (define-key map [?r] 'jao-mullvad-reconnect) (define-key map [?R] 'jao-mullvad-set-relay) (define-key map [?d] 'jao-mullvad-disconnect) (define-key map [?c] 'jao-mullvad-connect) map)) (defvar jao-mullvad--buffer "*mullvad*") ;;;###autoload (defun jao-mullvad-mode () "A very simple mode to show the output of mulvad commands." (interactive) (kill-all-local-variables) (buffer-disable-undo) (use-local-map jao-mullvad-mode-map) ;; (setq-local font-lock-defaults '(jao-jao-mullvad-font-lock-keywords)) (setq-local truncate-lines t) (setq-local next-line-add-newlines nil) (setq major-mode 'jao-mullvad-mode) (setq mode-name "mullvad") (read-only-mode 1)) (defun jao-mullvad--do (things &optional buffer) "Execute a mullvad command THINGS in the given BUFFER." (let ((b (or buffer (pop-to-buffer (get-buffer-create jao-mullvad--buffer))))) (let ((inhibit-read-only t) (cmd (format "mullvad %s" things))) (delete-region (point-min) (point-max)) (message "Running: %s ...." cmd) (shell-command cmd b) (message "")) (jao-mullvad-mode))) (defconst jao-mullvad--country-rx "^\\([^\t\n]+ (...?)\\)$") (defconst jao-mullvad--city-rx " \\([^\t(]+ (...)\\)") (defun jao-mullvad--list () "Compute an alist of available relay countries with their cities." (with-temp-buffer (jao-mullvad--do "relay list" (current-buffer)) (goto-char (point-min)) (let ((countries) (country)) (while (not (eobp)) (cond ((looking-at jao-mullvad--country-rx) (when country (setq countries (cons (reverse country) countries))) (setq country (list (match-string 1)))) ((looking-at jao-mullvad--city-rx) (setq country (cons (match-string 1) country)))) (forward-line 1)) (reverse (cons (reverse country) countries))))) ;;;###autoload (defun jao-mullvad-status () "Display mullvad connection status, including country, city and server." (interactive) (jao-mullvad--do "status") (when (re-search-forward "[0-9]+\\.[0-9]+\\.[0-9]+\\.[0-9]+" nil t) (let ((ip (match-string 0)) (country "") (city "") (server "")) (with-temp-buffer (jao-mullvad--do "relay list" (current-buffer)) (goto-char (point-min)) (when (search-forward ip nil t) (beginning-of-line) (when (looking-at "\t\t\\([^( ]+\\) (") (setq server (match-string 1))) (when (re-search-backward jao-mullvad--city-rx nil t) (setq city (string-trim (match-string 0)))) (when (re-search-backward jao-mullvad--country-rx nil t) (setq country (match-string 0))))) (let ((inhibit-read-only t)) (forward-line 1) (insert "Connected to " country " " city " - " server "\n") (goto-char (point-min)))))) ;;;###autoload (defun jao-mullvad-set-relay () "Choose a country and city and set them to the default relay location." (interactive) (let* ((cc (jao-mullvad--list)) (countries (cons "any" (mapcar #'car cc))) (country (completing-read "Country: " countries nil t)) (cities (unless (string= "any" country) (cons "any" (cdr (assoc country cc #'string=))))) (city (if cities (completing-read "City: " cities nil t) "any")) (cntr (if (string-match "(\\(...?\\))" country) (match-string 1 country) country)) (code (if (string-match "(\\(...\\))" city) (match-string 1 city) ""))) (when (y-or-n-p (format "Set location to %s / %s?" country city)) (jao-mullvad--do (format "relay set location %s %s" cntr code))))) ;;;###autoload (defun jao-mullvad-connect () "Connect to mullvad VPM, with the current configuration." (interactive) (jao-mullvad--do "connect")) ;;;###autoload (defun jao-mullvad-reconnect () "Ask mullvad to reconnect." (interactive) (jao-mullvad--do "reconnect")) ;;;###autoload (defun jao-mullvad-disconnect () "Ask mullvad to disconnect, after confirmation." (interactive) (when (y-or-n-p "Disconnect?") (jao-mullvad--do "disconnect"))) ;;;###autoload (defun jao-mullvad-list () "List all available relay locations." (interactive) (jao-mullvad--do "relay list")) ;;;###autoload (defun jao-mullvad-update-list () "Update list of available relay locations (background operation)." (interactive) (jao-mullvad--do "relay update")) (provide 'jao-mullvad) ;;; jao-mullvad.el ends here