Helps you control your GNU/Linux computer from Emacs
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

502 lines
18 KiB

;;; desktop-environment.el --- Helps you control your GNU/Linux computer -*- lexical-binding: t; -*-
;; Copyright (C) 2018 Damien Cassou
;; Author: Damien Cassou <damien@cassou.me>, Nicolas Petton <nicolas@petton.fr>
;; Url: https://gitlab.petton.fr/DamienCassou/desktop-environment
;; Package-requires: ((emacs "25.1"))
;; Version: 0.4.0
;; 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 <https://www.gnu.org/licenses/>.
;;; Commentary:
;; This package helps you control your GNU/Linux desktop from Emacs.
;; With desktop-environment, you can control the brightness and volume
;; as well as take screenshots and lock your screen. The package
;; depends on the availability of shell commands to do the hard work
;; for us. These commands can be changed by customizing the
;; appropriate variables.
;; The global minor mode `desktop-environment-mode' binds standard
;; keys to provided commands: e.g., <XF86AudioRaiseVolume> to raise
;; the volume, <print> to take a screenshot, and <s-l> to lock the
;; screen.
;;; Code:
(require 'dbus)
(defgroup desktop-environment nil
"Configure desktop-environment."
:group 'environment)
;;; Customization - keyboard backlight
(defcustom desktop-environment-keyboard-backlight-normal-increment 1
"Normal keyboard increment value."
:type 'integer)
(defcustom desktop-environment-keyboard-backlight-normal-decrement -1
"Normal keyboard decrement value."
:type 'integer)
;;; Customization - brightness
(defcustom desktop-environment-brightness-normal-increment "10%+"
"Normal brightness increment value."
:type 'string)
(defcustom desktop-environment-brightness-normal-decrement "10%-"
"Normal brightness decrement value."
:type 'string)
(defcustom desktop-environment-brightness-small-increment "5%+"
"Small brightness increment value."
:type 'string)
(defcustom desktop-environment-brightness-small-decrement "5%-"
"Small brightness decrement value."
:type 'string)
(defcustom desktop-environment-brightness-get-command "brightnessctl"
"Shell command getting current screen brightness level.
If you change this variable, you might want to change
`desktop-environment-brightness-get-regexp' as well."
:type 'string)
(defcustom desktop-environment-brightness-get-regexp "\\([0-9]+%\\)"
"Regular expression matching brightness value.
This regular expression will be tested against the result of
`desktop-environment-brightness-get-command' and group 1 must
match the current brightness level."
:type 'regexp)
(defcustom desktop-environment-brightness-set-command "brightnessctl set %s"
"Shell command setting the brightness level.
The value must contain 1 occurrence of '%s' that will be
replaced by the desired new brightness level."
:type 'string)
;;; Customization - volume
(defcustom desktop-environment-volume-normal-increment "5%+"
"Normal volume increment value."
:type 'string)
(defcustom desktop-environment-volume-normal-decrement "5%-"
"Normal volume decrement value."
:type 'string)
(defcustom desktop-environment-volume-small-increment "1%+"
"Small volume increment value."
:type 'string)
(defcustom desktop-environment-volume-small-decrement "1%-"
"Small volume decrement value."
:type 'string)
(defcustom desktop-environment-volume-get-command "amixer get Master"
"Shell command getting current volume level.
If you change this variable, you might want to change
`desktop-environment-volume-get-regexp' as well."
:type 'string)
(defcustom desktop-environment-volume-get-regexp "\\([0-9]+%\\)"
"Regular expression matching volume value.
This regular expression will be tested against the result of
`desktop-environment-volume-get-command' and group 1 must
match the current volume level."
:type 'regexp)
(defcustom desktop-environment-volume-set-command "amixer set Master %s"
"Shell command setting the volume level.
The value must contain 1 occurrence of '%s' that will be
replaced by the desired new volume level."
:type 'string)
(defcustom desktop-environment-volume-toggle-command "amixer set Master toggle"
"Shell command toggling between muted and unmuted."
:type 'string)
(defcustom desktop-environment-volume-toggle-microphone-command "amixer set Capture toggle"
"Shell command toggling microphone between muted and unmuted."
:type 'string)
;;; Customization - screenshots
(defcustom desktop-environment-screenshot-command "scrot"
"Shell command taking a screenshot in the current working directory."
:type 'string)
(defcustom desktop-environment-screenshot-partial-command "scrot -s"
"Shell command taking a partial screenshot in the current working directory.
The shell command should let the user interactively select the
portion of the screen."
:type 'string)
(defcustom desktop-environment-screenshot-directory "~/Pictures"
"Directory where to save screenshots."
:type 'directory)
;;; Customization - screen locking
(defcustom desktop-environment-screenlock-command "slock"
"Shell command locking the screen."
:type 'string)
;;; Customization - wifi
(defcustom desktop-environment-wifi-command "wifi toggle"
"Shell command toggling wifi."
:type 'string)
;;; Customization - bluetooth
(defcustom desktop-environment-bluetooth-command "bluetooth toggle"
"Shell command toggling bluetooth."
:type 'string)
;;; Customization - music
(defcustom desktop-environment-music-toggle-command "playerctl play-pause"
"Shell command toggling the music player."
:type 'string)
(defcustom desktop-environment-music-previous-command "playerctl previous"
"Shell command for going to previous song."
:type 'string)
(defcustom desktop-environment-music-next-command "playerctl next"
"Shell command for going to next song."
:type 'string)
;;; Customization - EXWM keybindings
(defcustom desktop-environment-update-exwm-global-keys :global
"Determines interacting with EXWM bindings when enabling/disabling the mode."
:type '(radio
(const :tag "Global" :doc "Use `exwm-input-set-key' on mode activation to set bindings." :global)
(const :tag "Prefix" :doc "Add/Remove keys to `exwm-input-prefix-keys' when enabling/disabling the mode." :prefix)
(const :tag "Off" :doc "Do not touch EXWM key bindings." nil)))
;;; Helper functions - brightness
(defun desktop-environment-brightness-get ()
"Return a string representing current brightness level."
(let ((output (shell-command-to-string desktop-environment-brightness-get-command)))
(save-match-data
(string-match desktop-environment-brightness-get-regexp output)
(match-string 1 output))))
(defun desktop-environment-brightness-set (value)
"Set brightness to VALUE."
(shell-command-to-string (format desktop-environment-brightness-set-command value))
(message "New brightness value: %s" (desktop-environment-brightness-get)))
;;; Helper functions - volume
(defun desktop-environment-volume-get ()
"Return a string representing current volume level."
(let ((output (shell-command-to-string desktop-environment-volume-get-command)))
(save-match-data
(string-match desktop-environment-volume-get-regexp output)
(match-string 1 output))))
(defun desktop-environment-volume-set (value)
"Set volume to VALUE."
(shell-command-to-string (format desktop-environment-volume-set-command value))
(message "New volume value: %s" (desktop-environment-volume-get)))
;;; Helper functions - keyboard backlight
(defun desktop-environment-keyboard-backlight-percent ()
"Return the new keyboard backlight value as a % of maximum backlight."
(let ((backlight-level (desktop-environment-keyboard-backlight-get)))
(if (eq backlight-level 0)
"0.0"
(*
(/ (* backlight-level 1.0)
(* (desktop-environment-keyboard-backlight-get-max) 1.0))
100))))
(defun desktop-environment-keyboard-backlight-get ()
"Return a number representing keyboard backlight current level."
(dbus-call-method :system
"org.freedesktop.UPower"
"/org/freedesktop/UPower/KbdBacklight"
"org.freedesktop.UPower.KbdBacklight"
"GetBrightness"))
(defun desktop-environment-keyboard-backlight-get-max ()
"Return a number representing keyboard backlight maximum level."
(dbus-call-method :system
"org.freedesktop.UPower"
"/org/freedesktop/UPower/KbdBacklight"
"org.freedesktop.UPower.KbdBacklight"
"GetMaxBrightness"))
(defun desktop-environment-keyboard-backlight-set (value)
"Set keyboard backlight to VALUE."
(dbus-call-method :system
"org.freedesktop.UPower"
"/org/freedesktop/UPower/KbdBacklight"
"org.freedesktop.UPower.KbdBacklight"
"SetBrightness"
:int32 value)
(message "New keyboard value: %s%%" (desktop-environment-keyboard-backlight-percent)))
;;; Commands - brightness
;;;###autoload
(defun desktop-environment-brightness-increment ()
"Increment brightness by `desktop-environment-brightness-normal-increment'."
(interactive)
(desktop-environment-brightness-set desktop-environment-brightness-normal-increment))
;;;###autoload
(defun desktop-environment-brightness-decrement ()
"Decrement brightness by `desktop-environment-brightness-normal-decrement'."
(interactive)
(desktop-environment-brightness-set desktop-environment-brightness-normal-decrement))
;;;###autoload
(defun desktop-environment-brightness-increment-slowly ()
"Increment brightness by `desktop-environment-brightness-small-increment'."
(interactive)
(desktop-environment-brightness-set desktop-environment-brightness-small-increment))
;;;###autoload
(defun desktop-environment-brightness-decrement-slowly ()
"Decrement brightness by `desktop-environment-brightness-small-decrement'."
(interactive)
(desktop-environment-brightness-set desktop-environment-brightness-small-decrement))
;;; Commands - volume
;;;###autoload
(defun desktop-environment-volume-increment ()
"Increment volume by `desktop-environment-volume-normal-increment'."
(interactive)
(desktop-environment-volume-set desktop-environment-volume-normal-increment))
;;;###autoload
(defun desktop-environment-volume-decrement ()
"Decrement volume by `desktop-environment-volume-normal-decrement'."
(interactive)
(desktop-environment-volume-set desktop-environment-volume-normal-decrement))
;;;###autoload
(defun desktop-environment-volume-increment-slowly ()
"Increment volume by `desktop-environment-volume-small-increment'."
(interactive)
(desktop-environment-volume-set desktop-environment-volume-small-increment))
;;;###autoload
(defun desktop-environment-volume-decrement-slowly ()
"Decrement volume by `desktop-environment-volume-small-decrement'."
(interactive)
(desktop-environment-volume-set desktop-environment-volume-small-decrement))
;;;###autoload
(defun desktop-environment-toggle-mute ()
"Toggle between muted and un-muted."
(interactive)
(message "%s"
(shell-command-to-string desktop-environment-volume-toggle-command)))
;;;###autoload
(defun desktop-environment-toggle-microphone-mute ()
"Toggle microphone between muted and un-muted."
(interactive)
(message "%s"
(shell-command-to-string desktop-environment-volume-toggle-microphone-command)))
;;; Commands - keyboard backlight
;;;###autoload
(defun desktop-environment-keyboard-backlight-increment ()
"Increment keyboard backlight by `desktop-environment-keyboard-backlight-normal-increment'."
(interactive)
(desktop-environment-keyboard-backlight-set
(+ desktop-environment-keyboard-backlight-normal-increment
(desktop-environment-keyboard-backlight-get))))
(defun desktop-environment-keyboard-backlight-decrement ()
"Decrement keyboard backlight by `desktop-environment-keyboard-backlight-normal-decrement'."
(interactive)
(desktop-environment-keyboard-backlight-set
(+ desktop-environment-keyboard-backlight-normal-decrement
(desktop-environment-keyboard-backlight-get))))
;;; Commands - screenshots
;;;###autoload
(defun desktop-environment-screenshot ()
"Take a screenshot of the screen in the current working directory."
(interactive)
(let ((default-directory (expand-file-name desktop-environment-screenshot-directory)))
(start-process-shell-command "desktop-environment-screenshot" nil desktop-environment-screenshot-command)))
;;;###autoload
(defun desktop-environment-screenshot-part ()
"Take a partial screenshot in the current working directory.
The command asks the user to interactively select a portion of
the screen."
(interactive)
(let ((default-directory (expand-file-name desktop-environment-screenshot-directory)))
(message "Please select the part of your screen to shoot.")
(start-process-shell-command "desktop-environment-screenshot" nil desktop-environment-screenshot-partial-command)))
;;; Commands - screen locking
;;;###autoload
(defun desktop-environment-lock-screen ()
"Lock the screen, preventing anyone without a password from using the system."
(interactive)
;; Run command asynchronously so that Emacs does not wait in the background.
(start-process-shell-command "lock" nil desktop-environment-screenlock-command))
;;; Commands - wifi
;;;###autoload
(defun desktop-environment-toggle-wifi ()
"Toggle wifi adapter on and off."
(interactive)
(let ((async-shell-command-buffer 'new-buffer))
(async-shell-command desktop-environment-wifi-command)))
;;; Commands - bluetooth
;;;###autoload
(defun desktop-environment-toggle-bluetooth ()
"Toggle bluetooth on and off."
(interactive)
(let ((async-shell-command-buffer 'new-buffer))
(async-shell-command desktop-environment-bluetooth-command)))
;;; Commands - music
(defun desktop-environment-toggle-music ()
"Play/pause the music player."
(interactive)
(message "%s"
(shell-command-to-string desktop-environment-music-toggle-command)))
(defun desktop-environment-music-previous ()
"Play the previous song."
(interactive)
(message "%s"
(shell-command-to-string desktop-environment-music-previous-command)))
(defun desktop-environment-music-next()
"Play the next song."
(interactive)
(message "%s"
(shell-command-to-string desktop-environment-music-next-command)))
;;; Minor mode
(defvar desktop-environment-mode-map
(let ((desktop-environment--keybindings
`(;; Brightness
(,(kbd "<XF86MonBrightnessUp>") . ,(function desktop-environment-brightness-increment))
(,(kbd "<XF86MonBrightnessDown>") . ,(function desktop-environment-brightness-decrement))
(,(kbd "S-<XF86MonBrightnessUp>") . ,(function desktop-environment-brightness-increment-slowly))
(,(kbd "S-<XF86MonBrightnessDown>") . ,(function desktop-environment-brightness-decrement-slowly))
;; Volume
(,(kbd "<XF86AudioRaiseVolume>") . ,(function desktop-environment-volume-increment))
(,(kbd "<XF86AudioLowerVolume>") . ,(function desktop-environment-volume-decrement))
(,(kbd "S-<XF86AudioRaiseVolume>") . ,(function desktop-environment-volume-increment-slowly))
(,(kbd "S-<XF86AudioLowerVolume>") . ,(function desktop-environment-volume-decrement-slowly))
(,(kbd "<XF86AudioMute>") . ,(function desktop-environment-toggle-mute))
(,(kbd "<XF86AudioMicMute>") . ,(function desktop-environment-toggle-microphone-mute))
;; Volume
(,(kbd "S-<print>") . ,(function desktop-environment-screenshot-part))
(,(kbd "<print>") . ,(function desktop-environment-screenshot))
;; Screen locking
(,(kbd "s-l") . ,(function desktop-environment-lock-screen))
;; Wifi controls
(,(kbd "<XF86WLAN>") . ,(function desktop-environment-toggle-wifi))
;; Bluetooth controls
(,(kbd "<XF86Bluetooth>") . ,(function desktop-environment-toggle-bluetooth))
;; Music controls
(,(kbd "<XF86AudioPlay>") . ,(function desktop-environment-toggle-music))
(,(kbd "<XF86AudioPrev>") . ,(function desktop-environment-music-previous))
(,(kbd "<XF86AudioNext>") . ,(function desktop-environment-music-next))))
(map (make-sparse-keymap)))
(dolist (keybinding desktop-environment--keybindings)
(define-key map (car keybinding) (cdr keybinding)))
map)
"Keymap for `desktop-environment-mode'.")
(declare-function exwm-input-set-key "ext:exwm-input")
(defun desktop-environment-exwm-set-global-keybindings (enable)
"When using EXWM, add `desktop-environment-mode-map' to global keys.
When ENABLE is non-nil, the bindings will be installed depending
on the value of `desktop-environment-update-exwm-global-keys'.
If set to `:prefix', the bindings will be disabled when ENABLE is
nil."
(when (featurep 'exwm-input)
(cl-case desktop-environment-update-exwm-global-keys
(:global
(when enable
(map-keymap (lambda (event definition)
(exwm-input-set-key (vector event) definition))
desktop-environment-mode-map)))
(:prefix
(when (boundp 'exwm-input-prefix-keys)
(map-keymap (lambda (event definition)
(ignore definition)
(setq exwm-input-prefix-keys (if enable
(cons event exwm-input-prefix-keys)
(delq event exwm-input-prefix-keys))))
desktop-environment-mode-map)))
((nil) nil)
(t
(message "Ignoring unknown value %s for `desktop-environment-update-exwm-global-keys'"
desktop-environment-update-exwm-global-keys)))))
;;;###autoload
(define-minor-mode desktop-environment-mode
"Activate keybindings to control your desktop environment.
\\{desktop-environment-mode-map}"
:global t
:require 'desktop-environment
:lighter " DE"
(desktop-environment-exwm-set-global-keybindings desktop-environment-mode))
(provide 'desktop-environment)
;;; desktop-environment.el ends here