add emacs config

This commit is contained in:
lucy 2026-08-21 23:49:25 +02:00
commit c1363180d7
761 changed files with 46444 additions and 0 deletions

View file

@ -0,0 +1,755 @@
;;; elcord.el --- Allows you to integrate Rich Presence from Discord
;; Copyright (C) 2017 heatingdevice
;; Author: heatingdevice
;; Wilfredo Velázquez-Rodríguez <zulu.inuoe@gmail.com>
;; Created: 21 Nov 2017
;; Package-Version: 1.1.0.0.20260506.47
;; Package-Revision: 033be3dc076f
;; Keywords: games
;; Homepage: https://github.com/Mstrodl/elcord
;; Package-Requires: ((emacs "25.1"))
;; License: MIT
;;; Commentary:
;; elcord allows you to show off your buffer with all your Discord friends via the new rich presence feature
;; To use, enable the global minor mode `elcord-mode'
;; When enabled, elcord will communicate with the local Discord client in order to display information under
;; the 'Playing a Game' status, updating this information at a regular interval.
;;
;; elcord will display an Emacs title, icon, as well as information about your current buffer:
;; 1) The name and an icon (if available) for the current major mode
;; 2) The name of the current buffer
;; 3) The line number of the cursor, as well as total line count for the buffer
;; `elcord-display-buffer-details' can be customized so that buffer name and line number are omitted.
;;; Code:
(require 'bindat)
(require 'cl-lib)
(require 'json)
(require 'subr-x)
(defgroup elcord nil
"Options for elcord."
:prefix "elcord-"
:group 'external)
(defcustom elcord-client-id '"388338871475240965"
"ID of elcord client (Application ID).
See <https://discordapp.com/developers/applications/me>."
:type '(choice (const :tag "'Native' Application ID" "388338871475240965")
(string :tag "Use the specified ID")
(function :tag "Call the function with no args to get the ID."))
:group 'elcord)
(defcustom elcord-icon-base
'"https://raw.githubusercontent.com/Mstrodl/elcord/master/icons/"
"Base URL for icon images. Mode icons will be loaded from this URL + the icon name + '.png'"
:type '(choice (const :tag "Elcord GitHub Repository"
"https://raw.githubusercontent.com/Mstrodl/elcord/master/icons/")
(string :tag "Use the specified URL base")
(function :tag "Call the function with icon name as an arg to get the URL base."))
:group 'elcord)
(defcustom elcord-refresh-rate 15
"How often to send updates to Discord, in seconds."
:type 'integer
:group 'elcord)
(defcustom elcord-idle-timer 300
"How long to wait before setting the status to idle."
:type 'integer
:group 'elcord)
(defcustom elcord-idle-message "Getting something to drink..."
"Message to show when elcord status is idle."
:type 'string)
(defcustom elcord-quiet 'nil
"Whether or not to supress elcord messages (connecting, disconnecting, etc.)"
:type 'boolean
:group 'elcord)
(defcustom elcord-mode-icon-alist '((agda-mode . "agda-mode_icon")
(assembly-mode . "assembly-mode_icon")
(bqn-mode . "bqn-mode_icon")
(c-mode . "c-mode_icon")
(c++-mode . "cpp-mode_icon")
(clojure-mode . "clojure-mode_icon")
(csharp-mode . "csharp-mode_icon")
(comint-mode . "comint-mode_icon")
(cperl-mode . "cperl-mode_icon")
(dart-mode . "dart-mode_icon")
(dockerfile-mode . "dockerfile-mode_icon")
(elixir-mode . "elixir-mode_icon")
(emacs-lisp-mode . (elcord--editor-icon))
(enh-ruby-mode . "ruby-mode_icon")
(erc-mode . "irc-mode_icon")
(erlang-mode . "erlang-mode_icon")
(forth-mode . "forth-mode_icon")
(fortran-mode . "fortran-mode_icon")
(fsharp-mode . "fsharp-mode_icon")
(gdscript-mode . "gdscript-mode_icon")
(haskell-mode . "haskell-mode_icon")
(haskell-interactive-mode . "haskell-mode_icon")
(hy-mode . "hy-mode_icon")
(java-mode . "java-mode_icon")
(julia-mode . "julia-mode_icon")
(js-mode . "javascript-mode_icon")
(kotlin-mode . "kotlin-mode_icon")
(go-mode . "go-mode_icon")
(latex-mode . "latex-mode_icon")
(lisp-mode . "lisp-mode_icon")
(lua-mode . "lua-mode_icon")
(magit-mode . "magit-mode_icon")
(markdown-mode . "markdown-mode_icon")
(meson-mode . "meson-mode_icon")
(nasm-mode . "nasm-mode_icon")
(nim-mode . "nim-mode_icon")
(nix-mode . "nix-mode_icon")
(ocaml-mode . "ocaml-mode_icon")
(octave-mode . "octave-mode_icon")
(org-mode . "org-mode_icon")
(pascal-mode . "pascal-mode_icon")
(php-mode . "php-mode_icon")
(prolog-mode . "prolog-mode_icon")
(puml-mode . "puml-mode_icon")
(puppet-mode . "puppet-mode_icon")
(python-mode . "python-mode_icon")
(racket-mode . "racket-mode_icon")
(ruby-mode . "ruby-mode_icon")
(rust-mode . "rust-mode_icon")
(rustic-mode . "rust-mode_icon")
(scala-mode . "scala-mode_icon")
(solidity-mode . "solidity-mode_icon")
(sh-mode . "comint-mode_icon")
(terraform-mode . "terraform-mode_icon")
(typescript-mode . "typescript-mode_icon")
(zig-mode . "zig-mode_icon")
(janet-mode . "janet-mode_icon")
("^slime-.*" . "lisp-mode_icon")
("^sly-.*$" . "lisp-mode_icon"))
"Mapping alist of major modes to icon names to have elcord use.
Note, these icon names must be available as 'small_image' in Discord."
:type '(alist :key-type (choice (symbol :tag "Mode name")
(regexp :tag "Regex"))
:value-type (choice (string :tag "Icon name")
(function :tag "Mapping function")))
:group 'elcord)
(defcustom elcord-mode-text-alist '((agda-mode . "Agda")
(assembly-mode . "Assembly")
(bqn-mode . "BQN")
(c-mode . "C ")
(c++-mode . "C++")
(csharp-mode . "C#")
(cperl-mode . "Perl")
(dart-mode . "Dart")
(elixir-mode . "Elixir")
(enh-ruby-mode . "Ruby")
(erlang-mode . "Erlang")
(fsharp-mode . "F#")
(gdscript-mode . "GDScript")
(hy-mode . "Hy")
(java-mode . "Java")
(julia-mode . "Julia")
(lisp-mode . "Common Lisp")
(markdown-mode . "Markdown")
(magit-mode . "It's Magit!")
("mhtml-mode" . "HTML")
(nasm-mode . "NASM")
(nim-mode . "Nim")
(ocaml-mode . "OCaml")
(pascal-mode . "Pascal")
(prolog-mode . "Prolog")
(puml-mode . "UML")
(scala-mode . "Scala")
(sh-mode . "Shell")
(slime-repl-mode . "SLIME-REPL")
(sly-mrepl-mode . "Sly-REPL")
(solidity-mode . "Solidity")
(terraform-mode . "Terraform")
(typescript-mode . "Typescript")
(php-mode "PHP"))
"Mapping alist of major modes to text labels to have elcord use."
:type '(alist :key-type (choice (symbol :tag "Mode name")
(regexp :tag "Regex"))
:value-type (choice (string :tag "Text label")
(function :tag "Mapping function")))
:group 'elcord)
(defcustom elcord-display-elapsed 't
"When enabled, Discord status will display the elapsed time since Emacs \
has been started."
:type 'boolean
:group 'elcord)
(defvar elcord--startup-time (string-to-number (format-time-string "%s" (current-time))))
(defcustom elcord-display-buffer-details 't
"When enabled, Discord status will display buffer name and line numbers:
\"Editing <buffer-name>\"
\"Line <line-number> (<line-number> of <line-count>)\"
Otherwise, it will display:
\"Editing\"
\"<elcord-mode-text>\"
The mode text is the same found by `elcord-mode-text-alist'"
:type 'boolean
:group 'elcord)
(defcustom elcord-display-line-numbers 't
"When enabled, shows the total line numbers of current buffer.
Including the position of the cursor in the buffer."
:type 'boolean
:group 'elcord)
(defcustom elcord-buffer-details-format-function 'elcord-buffer-details-format
"Function to return the buffer details string shown on discord.
Swap this with your own function if you want a custom buffer-details message."
:type 'function
:group 'elcord)
(defcustom elcord-use-major-mode-as-main-icon 'nil
"When enabled, the major mode determines the main icon, rather than it being the editor."
:type 'boolean
:group 'elcord)
(defcustom elcord-show-small-icon 't
"When enabled, show the small icon as well as the main icon."
:type 'boolean
:group 'elcord)
(defcustom elcord-editor-icon 'nil
"Icon to use for the text editor. When nil, use the editor's native icon."
:type '(choice (const :tag "Editor Default" nil)
(const :tag "Emacs" "emacs_icon")
(const :tag "Emacs (Pen)" "emacs_pen_icon")
(const :tag "Emacs (Material)" "emacs_material_icon")
(const :tag "Emacs (Legacy)" "emacs_legacy_icon")
(const :tag "Emacs (Dragon)" "emacs_dragon_icon")
(const :tag "Spacemacs" "spacemacs_icon")
(const :tag "Doom" "doom_icon")
(const :tag "Doom Cute" "doom_cute_icon"))
:group 'elcord)
(defcustom elcord-boring-buffers-regexp-list '("^ "
"\\\\*Messages\\\\*")
"A list of regexp's to match boring buffers.
When visiting a boring buffer, it will not show in the elcord presence."
:type '(repeat regexp)
:group 'elcord)
(defcustom elcord-discord-ipc-path 'nil
"Path to the Discord IPC pipe. When nil, the default path is used."
:type 'string
:group 'elcord)
;;;###autoload
(define-minor-mode elcord-mode
"Global minor mode for displaying Rich Presence in Discord."
nil nil nil
:require 'elcord
:global t
:group 'elcord
:after-hook
(progn
(cond
(elcord-mode
(elcord--enable))
(t
(elcord--disable)))))
(defvar elcord--editor-name
(cond
((boundp 'spacemacs-version) "Spacemacs")
((boundp 'doom-version) "DOOM Emacs")
(t "Emacs"))
"The name to use to represent the current editor.")
(defvar elcord--discord-ipc-pipe-format "discord-ipc-%d"
"The name of the discord IPC pipe.")
(defvar elcord--update-presence-timer nil
"Timer which periodically updates Discord Rich Presence.
nil when elcord is not active.")
(defvar elcord--reconnect-timer nil
"Timer used by elcord to attempt connection periodically, when active but disconnected.")
(defvar elcord--sock nil
"The process used to communicate with Discord IPC.")
(defvar elcord--last-known-position (count-lines (point-min) (point))
"Last known position (line number) recorded by elcord.")
(defvar elcord--last-known-buffer-name (buffer-name)
"Last known buffer recorded by elcord.")
(defvar elcord--stdpipe-path (expand-file-name
"stdpipe.ps1"
(file-name-directory (file-truename load-file-name)))
"Path to the 'stdpipe' script.
On Windows, this script is used as a proxy for the Discord named pipe.
Unused on other platforms.")
(defvar elcord--idle-status nil
"Current idle status.")
(defun elcord--find-discord-ipc-pipe ()
"Find the path to the Discord IPC pipe."
(if elcord-discord-ipc-path
;; If the user has specified the pipe location, trust them
elcord-discord-ipc-path
;; Otherwise, try to find the pipe in the usual places
(let ((candidates
(mapcan
(lambda (dir)
(mapcar
(lambda (num)
(expand-file-name (format elcord--discord-ipc-pipe-format num) dir))
(number-sequence 0 9)))
(list (expand-file-name "app/com.discordapp.Discord"
(getenv "XDG_RUNTIME_DIR"))
(getenv "XDG_RUNTIME_DIR")
(getenv "TMPDIR")
(getenv "TMP")
(getenv "TEMP")
"/tmp"))))
(cl-loop for candidate in candidates
until (file-exists-p candidate)
finally return candidate))))
(defun elcord--make-process ()
"Make the asynchronous process that communicates with Discord IPC."
(let ((default-directory "~/"))
(cl-case system-type
(windows-nt
(make-process
:name "*elcord-sock*"
:command (list
"PowerShell"
"-NoProfile"
"-ExecutionPolicy" "Bypass"
"-Command" elcord--stdpipe-path "." (format elcord--discord-ipc-pipe-format 0))
:connection-type 'pipe
:sentinel 'elcord--connection-sentinel
:filter 'elcord--connection-filter
:noquery t))
(t
(make-network-process
:name "*elcord-sock*"
:remote (elcord--find-discord-ipc-pipe)
:service nil
:sentinel 'elcord--connection-sentinel
:filter 'elcord--connection-filter
:noquery t)))))
(defun elcord--enable ()
"Called when variable ‘elcord-mode’ is enabled."
(setq elcord--startup-time (string-to-number (format-time-string "%s" (current-time))))
(unless (elcord--resolve-client-id)
(warn "elcord: no elcord-client-id available"))
(when (eq system-type 'windows-nt)
(unless (executable-find "powershell")
(warn "elcord: powershell not available"))
(unless (file-exists-p elcord--stdpipe-path)
(warn "elcord: 'stdpipe' script does not exist (%s)" elcord--stdpipe-path)))
(when elcord-idle-timer
(run-with-idle-timer
elcord-idle-timer t 'elcord--start-idle))
;;Start trying to connect
(elcord--start-reconnect))
(defun elcord--disable ()
"Called when variable ‘elcord-mode’ is disabled."
;;Cancel updates
(elcord--cancel-updates)
;;Cancel any reconnect attempt
(elcord--cancel-reconnect)
;;If we're currently connected
(when elcord--sock
;;Empty our presence
(elcord--empty-presence))
;;Stop running idle hook
(cancel-function-timers 'elcord--start-idle)
(elcord--disconnect))
(defun elcord--empty-presence ()
"Sends an empty presence for when elcord is disabled."
(let* ((nonce (format-time-string "%s%N"))
(presence
`(("cmd" . "SET_ACTIVITY")
("args" . (("activity" . nil)
("pid" . ,(emacs-pid))))
("nonce" . ,nonce))))
(elcord--send-packet 1 presence)))
(defun elcord--resolve-client-id ()
"Evaluate `elcord-client-id' and return the client ID to use."
(cl-typecase elcord-client-id
(null
nil)
(string
elcord-client-id)
(function
(funcall elcord-client-id))))
(defun elcord--resolve-icon-base (icon)
"Evaluate `elcord-icon-base' and return the URL to use.
Argument ICON the name of the icon we're resolving."
(cl-typecase elcord-icon-base
(null
nil)
(string
(concat elcord-icon-base icon ".png"))
(function
(funcall elcord-icon-base icon))))
(defun elcord--connection-sentinel (process evnt)
"Track connection state change on Discord connection.
Argument PROCESS The process this sentinel is attached to.
Argument EVNT The event which triggered the sentinel to run."
(cl-case (process-status process)
((closed exit)
(elcord--handle-disconnect))
(t)))
(defun elcord--connection-filter (process evnt)
"Track incoming data from Discord connection.
Argument PROCESS The process this filter is attached to.
Argument EVNT The available output from the process."
(elcord--start-updates))
(defun elcord--connect ()
"Connects to the Discord socket."
(or elcord--sock
(ignore-errors
(unless elcord-quiet
(message "elcord: attempting reconnect.."))
(setq elcord--sock (elcord--make-process))
(condition-case nil
(elcord--send-packet 0 `(("v" . 1) ("client_id" . ,(elcord--resolve-client-id))))
(error
(delete-process elcord--sock)
(setq elcord--sock nil)))
elcord--sock)))
(defun elcord--disconnect ()
"Disconnect elcord."
(when elcord--sock
(delete-process elcord--sock)
(setq elcord--sock nil)))
(defun elcord--reconnect ()
"Attempt to reconnect elcord."
(when (elcord--connect)
;;Reconnected.
;; Put a pending message unless we already got first handshake
(unless (or elcord--update-presence-timer elcord-quiet)
(message "elcord: connecting..."))
(elcord--cancel-reconnect)))
(defun elcord--start-reconnect ()
"Start attempting to reconnect."
(unless (or elcord--sock elcord--reconnect-timer)
(setq elcord--reconnect-timer (run-at-time 0 15 'elcord--reconnect))))
(defun elcord--cancel-reconnect ()
"Cancels any ongoing reconnection attempt."
(when elcord--reconnect-timer
(cancel-timer elcord--reconnect-timer)
(setq elcord--reconnect-timer nil)))
(defun elcord--handle-disconnect ()
"Handles reconnecting when socket disconnects."
(unless elcord-quiet
(message "elcord: disconnected by remote host"))
;;Stop updating presence for now
(elcord--cancel-updates)
(setq elcord--sock nil)
;;Start trying to reconnect
(when elcord-mode
(elcord--start-reconnect)))
(defun elcord--send-packet (opcode obj)
"Packs and sends a packet to the IPC server.
Argument OPCODE OP code to send.
Argument OBJ The data to send to the IPC server."
(let* ((jsonstr
(encode-coding-string
(json-encode obj)
'utf-8))
(datalen (length jsonstr))
(message-spec
`((:op u32r)
(:len u32r)
(:data str ,datalen)))
(packet
(bindat-pack
message-spec
`((:op . ,opcode)
(:len . ,datalen)
(:data . ,jsonstr)))))
(when elcord--sock
(process-send-string elcord--sock packet))))
(defun elcord--test-match-p (test mode)
"Test `MODE' against `TEST'.
if `test' is a symbol, it is compared directly to `mode'.
if `test' is a string, it is a regex to compare against the name of `mode'."
(cl-typecase test
(symbol (eq test mode))
(string (string-match-p test (symbol-name mode)))))
(defun elcord--entry-value (entry mode)
"Test `ENTRY' against `MODE'. Return the value of `ENTRY'.
`entry' is a cons who's `car' is `elcord--test-match-p' with `mode''
When `mode' matches, if the `cdr' of `entry' is a string, return that,
otherwise if it is a function, call it with `mode' and return that value."
(when (elcord--test-match-p (car entry) mode)
(let ((mapping (cdr entry)))
(cl-typecase mapping
(string mapping)
(function (funcall mapping mode))))))
(defun elcord--find-mode-entry (alist mode)
"Get the first entry in `ALIST' matching `MODE'.
`alist' Should be an alist like `elcord-mode-icon-alist' where each value is
either a string,or a function of one argument `mode'.
If it is a function, it should return a string, or nil if no match."
(let ((cell alist)
(result nil))
(while cell
(setq result (elcord--entry-value (car cell) mode)
cell (if result nil (cdr cell))))
result))
(defun elcord--editor-icon ()
"The icon to use to represent the current editor."
(elcord--resolve-icon-base
(cond
((progn elcord-editor-icon) elcord-editor-icon)
((boundp 'spacemacs-version) "spacemacs_icon")
((boundp 'doom-version) "doom_icon")
(t "emacs_icon"))))
(defun elcord--mode-icon ()
"Figure out what icon to use for the current major mode.
If an icon is mapped by `elcord-mode-icon-alist', then that is used.
Otherwise, if the mode is a derived mode, try to find an icon for it.
If no icon is available, use the default icon."
(let* ((modes (derived-mode-all-parents major-mode))
(mode (pop modes))
(ret (elcord--editor-icon)))
(while mode
(if-let ((icon (elcord--find-mode-entry elcord-mode-icon-alist mode)))
(setq ret (elcord--resolve-icon-base icon)
mode nil)
(setq mode (pop modes))))
ret))
(defun elcord--mode-text ()
"Figure out what text to use for the current major mode.
If an icon is mapped by `elcord-mode-text-alist', then that is used.
Otherwise, if the mode is a derived mode, try to find text for its parent,
If no text is available, use the value of `mode-name'."
(let ((mode major-mode)
(ret mode-name))
(while mode
(if-let ((text (elcord--find-mode-entry elcord-mode-text-alist mode)))
(setq ret text
mode nil)
(setq mode (get mode 'derived-mode-parent))))
(unless (stringp ret)
(setq ret (format-mode-line ret)))
ret))
(defun elcord--mode-icon-and-text ()
"Obtain the icon & text to use for the current major mode.
\((\"large_text\" . <text>)
(\"large_image\" . <icon-name>)
(\"small_text\" . <text>)
(\"small_image\" . <icon-name>))"
(let ((text (elcord--mode-text))
(icon (elcord--mode-icon))
large-text large-image
small-text small-image)
(cond
(elcord-use-major-mode-as-main-icon
(setq large-text text
large-image icon
small-text elcord--editor-name
small-image (elcord--editor-icon)))
(t
(setq large-text elcord--editor-name
large-image (elcord--editor-icon)
small-text text
small-image icon)))
(cond
(elcord-show-small-icon
(list
(cons "large_text" large-text)
(cons "large_image" large-image)
(cons "small_text" small-text)
(cons "small_image" small-image)))
(t
(list
(cons "large_text" large-text)
(cons "large_image" large-image)
(cons "small_text" small-text))))))
(defun elcord-buffer-details-format ()
"Return the buffer details string shown on discord."
(format "Editing %s" (buffer-name)))
(defun elcord--details-and-state ()
"Obtain the details and state to use for Discord's Rich Presence."
(let ((activity (if elcord-display-buffer-details
(if elcord-display-line-numbers
(list
(cons "details" (funcall elcord-buffer-details-format-function))
(cons "state" (format "Line %s of %S"
(format-mode-line "%l")
(+ 1 (count-lines (point-min) (point-max))))))
(list
(cons "details" (funcall elcord-buffer-details-format-function))))
(list
(cons "details" "Editing")
(cons "state" (elcord--mode-text))))))
(when elcord-display-elapsed
(push (list "timestamps" (cons "start" elcord--startup-time)) activity))
activity))
(defun elcord--set-presence ()
"Set presence."
(let* ((activity
`(("assets" . (,@(elcord--mode-icon-and-text)))
,@(elcord--details-and-state)))
(nonce (format-time-string "%s%N"))
(presence
`(("cmd" . "SET_ACTIVITY")
("args" . (("activity" . ,activity)
("pid" . ,(emacs-pid))))
("nonce" . ,nonce))))
(elcord--send-packet 1 presence)))
(defun elcord--buffer-boring-p (buffer-name)
"Return non-nil if `BUFFER-NAME' is non-boring per `ELCORD-BORING-BUFFERS-REGEXP-LIST'."
(let ((cell elcord-boring-buffers-regexp-list)
(result nil))
(while cell
(if (string-match-p (car cell) buffer-name)
(setq result t
cell nil)
(setq cell (cdr cell))))
result))
(defun elcord--find-non-boring-window ()
"Try to find a live window displaying a non-boring buffer."
(let ((cell (window-list))
(result nil))
(while cell
(let ((window (car cell)))
(if (not (elcord--buffer-boring-p (buffer-name (window-buffer window))))
(setq result window
cell nil)
(setq cell (cdr cell)))))
result))
(defun elcord--try-update-presence (new-buffer-name new-buffer-position)
"Try updating presence with `NEW-BUFFER-NAME' and `NEW-BUFFER-POSITION' while handling errors and disconnections."
(setq elcord--last-known-buffer-name new-buffer-name
elcord--last-known-position new-buffer-position)
(condition-case err
;;Try and set the presence
(elcord--set-presence)
(error
(message "elcord: error setting presence: %s" (error-message-string err))
;;If we hit an error, cancel updates
(elcord--cancel-updates)
;; Disconnect
(elcord--disconnect)
;; and try reconnecting
(elcord--start-reconnect))))
(defun elcord--update-presence ()
"Conditionally update presence by testing the current buffer/line.
If there is no 'previous' buffer attempt to find a non-boring buffer to initialize to."
(if (= elcord--last-known-position -1)
(when-let ((window (elcord--find-non-boring-window)))
(with-current-buffer (window-buffer window)
(elcord--try-update-presence (buffer-name) (count-lines (point-min) (point)))))
(let ((new-buffer-name (buffer-name (current-buffer))))
(unless (elcord--buffer-boring-p new-buffer-name)
(let ((new-buffer-position (count-lines (point-min) (point))))
(unless (and (string= new-buffer-name elcord--last-known-buffer-name)
(= new-buffer-position elcord--last-known-position))
(elcord--try-update-presence new-buffer-name new-buffer-position)))))))
(defun elcord--start-updates ()
"Start sending periodic update to Discord Rich Presence."
(unless elcord--update-presence-timer
(unless elcord-quiet
(message "elcord: connected. starting updates"))
;;Start sending updates now that we've heard from discord
(setq elcord--last-known-position -1
elcord--last-known-buffer-name ""
elcord--update-presence-timer (run-at-time 0 elcord-refresh-rate 'elcord--update-presence))))
(defun elcord--cancel-updates ()
"Stop sending periodic update to Discord Rich Presence."
(when elcord--update-presence-timer
(cancel-timer elcord--update-presence-timer)
(setq elcord--update-presence-timer nil)))
(defun elcord--start-idle ()
"Set presence to idle, pause update and timer."
(unless elcord--idle-status
(unless elcord-quiet
(message (format "elcord: %s" elcord-idle-message)))
;;hacky way to stop updates and store elapsed time
(cancel-timer elcord--update-presence-timer)
(setq elcord--startup-time (string-to-number (format-time-string "%s" (time-subtract nil elcord--startup-time)))
elcord--idle-status t)
(let* ((activity
`(("assets" . (,@(elcord--mode-icon-and-text)))
("timestamps" ("start" ,@(string-to-number (format-time-string "%s" (current-time)))))
("details" . "Idle") ("state" . ,elcord-idle-message)))
(nonce (format-time-string "%s%N"))
(presence
`(("cmd" . "SET_ACTIVITY")
("args" . (("activity" . ,activity)
("pid" . ,(emacs-pid))))
("nonce" . ,nonce))))
(elcord--send-packet 1 presence))
(add-hook 'pre-command-hook 'elcord--cancel-idle)))
(defun elcord--cancel-idle ()
"Resume presence update and timer."
(when elcord--idle-status
(remove-hook 'pre-command-hook 'elcord--cancel-idle)
;;resume timer with elapsed time
(setq elcord--startup-time (string-to-number (format-time-string "%s" (time-subtract nil elcord--startup-time)))
elcord--idle-status nil
;;hacky way to resume updates
elcord--update-presence-timer nil)
(elcord--start-updates)
(unless elcord-quiet
(message "elcord: welcome back"))))
(provide 'elcord)
;;; elcord.el ends here