diff --git a/discord-ipc.rkt b/discord-ipc.rkt new file mode 100644 index 0000000..ceda88e --- /dev/null +++ b/discord-ipc.rkt @@ -0,0 +1,67 @@ +#lang racket/base +(require racket/unix-socket + json + racket/list + racket/os) + +(provide connect-discord update-presence) + +(define ipc-out #f) + +(define (get-socket-path) + (define base-dirs + (list (getenv "XDG_RUNTIME_DIR") + (getenv "TMPDIR") + "/tmp" + (build-path "/run/user" (number->string (getpid))))) + + (for/first ([dir base-dirs] + #:when dir + [i (in-range 10)] + #:do [(define p (build-path dir (format "discord-ipc-~a" i)))] + #:when (file-exists? p)) + p)) + +(define (write-packet opcode payload) + (when ipc-out + (define json-bytes (string->bytes/utf-8 (jsexpr->string payload))) + (define len (bytes-length json-bytes)) + + (write-bytes (integer->integer-bytes opcode 4 #f #f) ipc-out) ; Opcode + (write-bytes (integer->integer-bytes len 4 #f #f) ipc-out) ; Length + (write-bytes json-bytes ipc-out) + (flush-output ipc-out))) + +(define (connect-discord client-id) + (define path (get-socket-path)) + (if path + (with-handlers ([exn:fail? (lambda (e) (printf "Discord connection failed.\n"))]) + (define-values (in out) (unix-socket-connect path)) + (set! ipc-out out) + (write-packet 0 (hash 'v 1 'client_id client-id)) + (printf "Connected to Discord at ~a\n" path)) + (printf "Discord not found.\n"))) + +(define (update-presence details state + #:start [start-timestamp #f] + #:small-text [small-text #f]) + + (define activity + (hash 'details details + 'state state + 'assets (hash + 'large_image "racket-logo-ezgif_com-svg-to-png-converter" + 'large_text "DrRacket" + + 'small_image (if small-text "lambda" #f) + 'small_text small-text) + + 'timestamps (if start-timestamp + (hash 'start start-timestamp) + #f))) + + (write-packet 1 + (hash 'cmd "SET_ACTIVITY" + 'nonce (number->string (current-milliseconds)) + 'args (hash 'pid (getpid) + 'activity activity)))) diff --git a/info.rkt b/info.rkt new file mode 100644 index 0000000..27c5197 --- /dev/null +++ b/info.rkt @@ -0,0 +1,16 @@ +#lang info + +(define name "Discord Rich Presence") +(define version "0.1") +(define pkg-desc "Updates Discord status with filename, line count, and running state from DrRacket.") +(define pkg-authors '(joren)) +(define license 'MIT) + +(define drracket-tools '(("tool.rkt"))) +(define drracket-tool-names '("Discord Presence")) + +(define deps '("base" + "drracket-plugin-lib" + "gui-lib" + "unix-socket-lib")) + diff --git a/tool.rkt b/tool.rkt new file mode 100644 index 0000000..5bb5e22 --- /dev/null +++ b/tool.rkt @@ -0,0 +1,66 @@ +#lang racket/gui +(require drracket/tool + racket/unit + racket/class + "discord-ipc.rkt") + +(provide tool@) + +(define CLIENT-ID "1456391630356746343") + +(define tool@ + (unit + (import drracket:tool^) + (export drracket:tool-exports^) + (define phase1 void) + (define phase2 void) + + (connect-discord CLIENT-ID) + + (define start-time (current-seconds)) + (define (update-status tab [override-running #f]) + (define defs (send tab get-defs)) + (define filename + (let ([path (send defs get-filename)]) + (if path (path->string (file-name-from-path path)) "Untitled"))) + (define line-count (add1 (send defs last-paragraph))) + (define is-running? (or override-running (send tab is-running?))) + (define details (if is-running? + (format "Running ~a" filename) + (format "Editing ~a" filename))) + (define state (format "~a lines" line-count)) + (update-presence details state + #:start start-time + #:small-text (if is-running? "Running" "Editing"))) + + (drracket:get/extend:extend-tab + (mixin (drracket:unit:tab<%>) () + (super-new) + (define/override (disable-evaluation) + (super disable-evaluation) + (update-status this #t)) + + (define/override (enable-evaluation) + (super enable-evaluation) + (update-status this #f)))) + + (drracket:get/extend:extend-definitions-text + (mixin (drracket:unit:definitions-text<%> editor<%>) () + (super-new) + (define/augment (after-save-file success?) + (inner (void) after-save-file success?) + (when success? + (update-status (send this get-tab)))))) + + (drracket:get/extend:extend-unit-frame + (mixin (drracket:unit:frame<%>) () + (super-new) + (define/augment (on-tab-change old-tab new-tab) + (inner (void) on-tab-change old-tab new-tab) + (update-status new-tab)) + + (define/override (on-activate active?) + (super on-activate active?) + (when active? + (update-status (send this get-current-tab)))))))) +