emacs-elpa-diffs
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[nongnu] elpa/dslide 1123a4ecbe 11/21: Keyboard MACRO ACTION


From: ELPA Syncer
Subject: [nongnu] elpa/dslide 1123a4ecbe 11/21: Keyboard MACRO ACTION
Date: Tue, 17 Dec 2024 13:00:53 -0500 (EST)

branch: elpa/dslide
commit 1123a4ecbe93d75816faa3ba9539d6aef4ed7ca3
Author: Psionik K <73710933+psionic-k@users.noreply.github.com>
Commit: Psionik K <73710933+psionic-k@users.noreply.github.com>

    Keyboard MACRO ACTION
---
 NEWS.org       |   2 +
 doc/README.org |   1 +
 doc/manual.org |  41 +++++++++++-----
 dslide.el      | 149 +++++++++++++++++++++++++++++++++++++++++++++++++++++++--
 test/demo.org  |  29 +++++++++++
 5 files changed, 208 insertions(+), 14 deletions(-)

diff --git a/NEWS.org b/NEWS.org
index 014c6be377..7b18247e42 100644
--- a/NEWS.org
+++ b/NEWS.org
@@ -12,7 +12,9 @@
 #+export_file_name: RELEASE.md
 
 * v0.6.0 Fighting Spam ๐Ÿ’Œ :latest:
+- New actions (*KMACROS!*) fit a more clear pattern
 ** Added โž•
+- ๐Ÿงฌ Experimental new kmacro action can run keyboard kmacros to script "live 
demonstrations".  Describe ~dslide-action-kmacro~ to view the documentation.  
There is a demo in the usual [] file.  All related functions and variables are 
under the =dslide-action-kmacro= or =dslide-kmacro= prefixes.
 - Babel blocks, which will now all be executed by default, respect the =:eval= 
parameter.  Values such as =never= or =never-export= will skip the block.  
Other values are equivalent to =yes=.  Values like =query= do *not* ask yet.  
Expect that in 0.7.0. ๐Ÿšง
   #+begin_src org
     ,#+begin_src elisp :eval never
diff --git a/doc/README.org b/doc/README.org
index e104cf3c0d..2272d610c1 100644
--- a/doc/README.org
+++ b/doc/README.org
@@ -19,6 +19,7 @@ 
https://github.com/positron-solutions/dslide/assets/73710933/06a66e42-a172-48ba-
 ** Programmable Org Presentation ๐Ÿฆ„
 - Per-element configurable behavior through extensible actions
 - Script steps in your presentation with *Org babel blocks*
+- Incorporate anything Emacs does with *keyboard macro playback*
 - Convenient API for quickly writing reliable custom actions for reuse
 - Decent out-of-the-box results with existing org documents
 *** Status ๐Ÿ’น
diff --git a/doc/manual.org b/doc/manual.org
index 4794e6ed20..f17c1dc405 100644
--- a/doc/manual.org
+++ b/doc/manual.org
@@ -27,7 +27,7 @@ Dslide is conducts presentations in Emacs and can utilize 
anything and everythin
 What dslide primarily adds to Emacs and Org Mode:
 
 - Simplified user interface suitable for a presentation remote controller
-- Data model that adapts Emacs to this interface
+- Data model that adapts Emacs to this interface, including Org babel and 
keyboard macros
 - Method of attaching reusable, configurable programmed behavior to org mode 
content
 - Extensible framework for creating custom programmed behavior
 - Presenter tools such as viewing hidden content invisible to the audience
@@ -39,8 +39,10 @@ Granular configurability was the first goal.  Dslide's 
predecessor, ~org-tree-sl
 
 Programmability quickly became primary motivation to develop dslide further.  
Org babel blocks can be used as steps of a dslide presentation.  The action 
system is a framework for attaching reusable, configurable programmed behavior 
to multiple org elements.
 
-High productivity was another goal.  With a decent org configuration, use 
basic markup to obtain a decent presentation.  Org mode's properties and 
keywords are used to attach and configure reusable behavior to elements and 
headings.  Dslide respects export settings, allowing content to vary between 
presentation and export.  You can use the presentation org document itself the 
same way you use other org documents, to store, organize, and publish 
information.
-** Strengths
+If Emacs does it, dslide can present it.  As Emacs can use all of your 
programs and programming tools, so can dslide.  This includes both scripting 
through Elisp and command-based keyboard macros.  Dslide aims to integrate with 
other buffers and Emacs automation seemlessly.
+
+High productivity was another goal.  By respecting export settings and 
cleaning up markup, dslide aims to re-use the same documents you already use 
for storing, organizing, and publishing information.  Dslide adds a bit of 
flair so that, with a decent org configuration, you get a decent result with 
little extra effort.
+** Strengths ๐Ÿฆพ
 :PROPERTIES:
 :DESCRIPTION: When Dslide is Perfect
 :END:
@@ -102,6 +104,7 @@ Select org mode terms more frequently used by dslide.  
Don't miss [[info:org#Org
 Documents should "just work" and obtain decent results.
 
 - Add behavior to elements by enabling and configuring [[Actions ๐Ÿช„][actions]]
+- Add custom behavior with babel blocks and keyboard macros
 - Create custom actions to use different kinds of data in reusable ways
 ** Actions ๐Ÿช„
 :PROPERTIES:
@@ -120,6 +123,7 @@ To browse all actions, because they are all EIEIO classes, 
you can use ~eieio-br
             +--dslide-action-propertize
             +--dslide-action-image
             +--dslide-action-babel
+            +--dslide-action-kmacro
             +--dslide-action-item-reveal
             +--dslide-action-hide-markup
             +--dslide-slide-action
@@ -147,13 +151,13 @@ Some actions, such as ~dslide-action-propertize~, can't 
decide which elements to
 ๐Ÿšง This is the preferred style of configuration moving forward.
 
 โ„น๏ธ Affiliated keywords /must/ have the =attr= prefix or they will not apply to 
the content they precede.  Affiliated keywords cannot be attached to headings, 
which must use their property drawer to attach data.
+*** Keyword
+:PROPERTIES:
+:DESCRIPTION: The markup is the content
+:END:
+For some actions, the configuration /is/ the content.  They use a keyword.  
The value of the keyword is the content for the action.  As elsewhere, use 
plist =:key value= style configuration.
 #+begin_src org
-  ,* Full Screen Images
-  :PROPERTIES:
-  :DSLIDE_ACTIONS: dslide-action-images
-  :END:
-  ,#+attr_html: :width 50%
-  [[./images/emacsen4.jpeg]] [[./images/before-google3.jpeg]]
+  ,#+dslide_kmacro: :keys [134217848 97 110]
 #+end_src
 *** Property Drawer
 :PROPERTIES:
@@ -205,8 +209,12 @@ Configuring is usually done by adding plist-style =:key 
value= arguments after t
   :DSLIDE_ACTIONS: dslide-action-item-reveal :inline t
   :END:
 
-  ,#+attr_dslide_propertize: face (:background "#ddddff" :foreground "#000000" 
:weight bold :height 2.0)
-  This is some fancy text
+  # A keyword configuration
+  ,#+dslide_kmacro: :frequency 0.08 :jitter 0.5 :keys [134217848 97 110 ]
+
+  # An affiliated keyword configuration
+  ,#+attr_dslide_propertize: face '(:background "#ddddff")
+  This text will be propertized
 #+end_src
 ๐Ÿšง After class names, the current plist read implementation splits the string 
rather than using ~read-string~ and is therefore not smart enough to parse 
lists as arguments.  However ~dslide-action-propertize~ demonstrates doing this 
correctly and shows that it will be possible if needed.
 ** Babel Scripting ๐Ÿง‘โ€๐Ÿ’ป
@@ -313,6 +321,17 @@ The callback function should accept a DIRECTION argument.  
DIRECTION is forward,
 
 #+end_src
 โ„น๏ธ You can also use ~dslide-push-step~ in actions for implementing tricky 
action behaviors.  The image action uses this currently.
+** Keyboard Macros ๐Ÿค–
+:PROPERTIES:
+:DESCRIPTION: Acting like you type well while talking
+:END:
+๐Ÿงช Experimental new feature.  Hopefully the configuration argument names are 
good.  Hopefully.
+
+The ~dslide-action-kmacro~ will run pre-recorded sequences of keystrokes as if 
you are controlling the computer.  Through =:frequency= and =:jitter=, it plays 
back strokes at a human-feeling pace.
+
+By playing back keyboard macros, you can encode real Emacs workflows as steps 
in a presentation.  Don't just talk about how your software works.  Use the 
software with fully reproducible steps that users can understand in a tactile, 
human way.
+
+๐Ÿ†’ The jitter uses a Laplace distribution to sample a perturbation power.  This 
power is mapped onto the zero-to-infinity factor range by raising e to the 
power of jitter.  This is multiplied by =:frequency=, which is a duration.  As 
a result, while the jitter is usually pretty small, it does have some wild 
variation, which does look a bit more human.
 ** Hiding Markup ๐Ÿฅท๐Ÿฟ
 :PROPERTIES:
 :DESCRIPTION: No More Ugly
diff --git a/dslide.el b/dslide.el
index c0afa2a469..da9af4f6d6 100644
--- a/dslide.el
+++ b/dslide.el
@@ -41,9 +41,10 @@
 ;; DSL IDE creates presentations out of org mode documents.  Every single step
 ;; in a presentation can be individually configured or customized.  Org
 ;; headings and elements are configured with extensible actions.  Custom steps
-;; can be scripted with babel blocks.  Frequent customizations can be made
-;; into custom actions.  DSL IDE achieves a good result with no preparation
-;; but can achieve anything Emacs can display if you need it to.
+;; can be scripted with babel blocks.  Keyboard macros can play back real
+;; command sequences.  Frequent customizations can be made into custom
+;; actions.  DSL IDE achieves a good result with no preparation but can
+;; achieve anything Emacs can display if you need it to.
 ;;
 ;; To try it out, install this package and load the demo.org found in the test
 ;; directory of the repository.  `dslide-deck-start' will begin the
@@ -319,6 +320,7 @@ keyword."
 ;; TODO test the use of plist args
 (defcustom dslide-default-actions '(dslide-action-hide-markup
                                     dslide-action-propertize
+                                    dslide-action-kmacro
                                     dslide-action-babel
                                     dslide-action-image)
   "Actions that run within the section display action lifecycle.
@@ -400,6 +402,9 @@ See `dslide-base-follows-slide'.")
 (defvar dslide--animation-timers nil)
 (defvar-local dslide--animation-overlays nil)
 
+(defvar dslide--kmacro-timer nil
+  "Allow cleanup and prevent macros from running concurrently.")
+
 ;; Tell the compiler that these variables exist
 (defvar dslide-mode)
 
@@ -1854,6 +1859,144 @@ Only affects standalone-display.")
          (overlay-put overlay 'invisible t)
          (push overlay dslide-overlays))))))
 
+;; ** Kmacro Action
+
+(defclass dslide-action-kmacro (dslide-action)
+  ((frequency
+    :initform 0.07
+    :initarg :frequency
+    :documentation "Peak duration between keystrokes.
+Peak is not a maximum.  Most durations will be around this value.  Jitter will
+result in some being larger and smaller.")
+   (jitter
+    :initform 0.4
+    :initarg :jitter
+    :documentation "Shape parameter for jitter between keystrokes.
+This simulates a human typing.  The math uses a long-tailed
+Laplace distribution (the only good distribution for serious
+work).  Larger values of jitter will result in more variation in
+frequency.  Values of 0.1 to 1.0 are reasonable.  Must be
+greater than 0.0.  Values of 0.0 will result in no jitter."))
+  "๐Ÿšง UNSTABLE! Playback keyboard macros stored in keywords.
+We hope the new format is flexible enough to add features.  But
+we will break it as soon as possible to avoid pain if necessary
+and then advise you how to update inside the NEWS (Release notes
+on Github)
+
+Beware!  Macros run amok will sell your entire stock portfoloio,
+upload all your personal data to pastebin, and confess to
+multiple crimes you probably didn't commit.  They are not
+portable from configuration to configuration.  They are however
+very special in that they replicate how you use Emacs.")
+
+(cl-defmethod dslide-final :after ((_obj dslide-action-kmacro))
+  ;; todo clean up any animation
+  (when dslide--kmacro-timer
+    (cancel-timer dslide--kmacro-timer)
+    (setq dslide--kmacro-timer nil)))
+
+;; Default end method behavior plays all steps forward, which would wreck the
+;; last hope of every nightmare.
+(cl-defmethod dslide-end ((obj dslide-action-kmacro))
+  (dslide-marker obj (org-element-property :end (dslide-heading obj))))
+
+(cl-defmethod dslide-forward ((obj dslide-action-kmacro))
+  ;; TODO Erroring within an action usually allows retry.  Let's find out.  ๐Ÿค 
+  (when dslide--kmacro-timer
+    (user-error "Dslide keyboard macro already running"))
+  (when-let ((keyword (dslide-section-next
+                       obj 'keyword
+                       (lambda (e)
+                         (when (string= (org-element-property :key e)
+                                        "DSLIDE_KMACRO")
+                           e)))))
+    (let* ((value (org-element-property :value keyword))
+           (params (dslide-read-plist value)))
+      ;; TODO warn when direction is wrong.
+      (when (or (eq 'forward (plist-get params :direction))
+                (not (member :direction params)))
+        (dslide--kmacro-reanimate
+         :last-input last-command-event
+         :frequency (or (plist-get params :frequency) (oref obj frequency))
+         :jitter (or (plist-get params :jitter) (oref obj jitter))
+         :index 0
+         :keys (plist-get params :keys))
+        (org-element-property :begin keyword)))))
+
+(cl-defmethod dslide-backward ((obj dslide-action-kmacro))
+  (when dslide--kmacro-timer
+    (user-error "Dslide keboard macro already running"))
+  (when-let ((keyword (dslide-section-next
+                       obj 'keyword
+                       (lambda (e) (when (string= (org-element-property :key e)
+                                             "DSLIDE_KMACRO")
+                                     e)))))
+    (let* ((value (org-element-property :value keyword))
+           (params (dslide-read-plist value)))
+      (when (eq 'backwards (plist-get params :direction))
+        (dslide--kmacro-reanimate
+         :last-input last-command-event
+         :frequency (or (plist-get params :frequency) (oref obj frequency))
+         :jitter (or (plist-get params :jitter) (oref obj jitter))
+         :index 0
+         :keys (plist-get params :keys))
+        (org-element-property :begin keyword)))))
+
+;; โš ๏ธ The implementation needs to return or else the normal command loop is in
+;; the way of the keys being read and only the last event pops out.  Thanks,
+;; Elisp.  For now, a recursive timer is the only way I've found to make this
+;; work.  Maybe threads will work.  We'll see.
+(defun dslide--kmacro-reanimate (&rest args)
+  "Play keys in ARGS back like the piano man.
+This function recursively calls itself via a timer until it runs
+out of keys in ARGS.  It compares the most recent input with
+`last-input-event' so that it can detect if it has been
+interrupted by some other input event and quit."
+  (let ((frequency (plist-get args :frequency))
+        (jitter (plist-get args :jitter))
+        (keys (plist-get args :keys))
+        (last-input (plist-get args :last-input))
+        (index (plist-get args :index)))
+    (if (eq last-input last-input-event)
+        (if (length> keys index)
+            (let ((k (aref keys index)))
+              (setq unread-command-events (list k))
+              (setq dslide--kmacro-timer
+                    (run-with-timer
+                     (dslide--laplace-jitter frequency jitter)
+                     nil #'dslide--kmacro-reanimate
+                     :last-input k
+                     :keys keys :index (1+ index)
+                     :frequency frequency :jitter jitter)))
+          (setq dslide--kmacro-timer nil))
+      ;; TODO attempt to block unwanted input.  Test other implementations.
+      (setq dslide--kmacro-timer nil)
+      (message "Out-of-band input detected: %s" last-input-event)
+      (message "Aborting playback."))))
+
+(defun dslide--laplace-jitter (freq jitter)
+  "Mutate FREQ by JITTER shape parameter.
+Jitter is the b parameter of the Laplace distribution, the only
+good distribution for serious work.  Larger b causes a fatter
+tail.  In practice, small JITTER will be more predictable, but
+never without some larger variations.
+
+Because the Laplace distribution has a fat tail, you aren't
+forced to choose between good local refinement and good mixing
+speeds and lower auto-correlation.  ๐Ÿ’ก"
+  (cond ((integerp jitter) (error "Jitter was integer: %s" jitter))
+        ((= jitter 0.0) freq)
+        ((< jitter 0.0) (error "Jitter too low: %s" jitter))
+        ;; Elisp has no random float because ๐Ÿ’ฉ
+        (t (let* ((p (/ (random 1000000) 1000000.0))
+                  (p (min 0.99 (max 0.01 p))) ; clamp extreme 2%
+                  (sample (if (> p 0.5)
+                              ;; - (self.b * (2.0 - 2.0 * p).ln())
+                              (- (* jitter (log (- 2.0 (* 2.0 p)) float-e)))
+                            ;; self.b * (2.0 * p).ln()
+                            (* jitter (log (* 2.0 p) float-e)))))
+             (* freq (expt float-e sample))))))
+
 ;; * Slide Actions
 ;; A slide action will generally control the restriction, hydrate children, and
 ;; pass through `dslide-stateful-sequence' calls to children.  There could be
diff --git a/test/demo.org b/test/demo.org
index b62780d058..f4e6efcc95 100644
--- a/test/demo.org
+++ b/test/demo.org
@@ -438,6 +438,35 @@ Massachusetts, in particular, has always been one of the 
laboratories of democra
 Democracy depends on an informed citizenry and the social cohesion that those 
citizens can show even when they disagree.
 
 The essence of democracy is the resolve of individuals working together to 
shape our institutions and our society in ways that allow all of us to flourish.
+* Keyboard Macros
+/New feature!/  You can play back keyboard macros.
+
+** Keyboard Macro Example โœจ
+Press โžก๏ธ to run the (invisible) recorded macro ๐Ÿค .
+
+# M-x animate-birthday-present dslide
+#+dslide_kmacro: :frequency 0.09 :jitter 0.4 :keys [134217848 97 110 105 109 
97 116 101 45 98 105 114 116 104 100 97 121 45 112 114 101 115 101 110 116 
return 100 115 108 105 100 101 return]
+
+๐Ÿšง Please try this feature.  Open issues.  Keep in mind that keyboard macros 
are not very reproducible.  They likely only work on your configuration.  It is 
a great way to demonstrate using your shell etc while not having to type. ๐Ÿ’ก
+
+# If the user has M-x remapped, we should at least warn them.
+#+begin_src elisp :direction init :exports none :results none
+  (unless (or (eq (key-binding (kbd "M-x")) #'execute-extended-command)
+              (eq (command-remapping 'execute-extended-command)
+                  (key-binding (kbd "M-x"))))
+    (org-back-to-heading)
+    (re-search-forward "๐Ÿค ." nil t)
+    (let ((o (make-overlay (1- (point)) (point))))
+      (overlay-put o 'after-string
+                   (propertize "  โš ๏ธ Your M-x appears remapped!  Press โฌ†๏ธ and 
skip this example." 'face 'warning))
+      (push o dslide-overlays)))
+#+end_src
+
+# Clean up the buffer if it was created
+#+begin_src elisp :direction final :exports none :results none
+  (when-let ((birthday (get-buffer "*A-Present-for-Dslide*")))
+    (kill-buffer birthday))
+#+end_src
 * Enjoy!
 - This package use used to create videos on Positron's own 
[[https://www.youtube.com/channel/UCqM0zDcFNdAHj7uQkprLszg/][YouTube ๏…ช]] channel
 - File issues and request features to give us ideas about usage and need



reply via email to

[Prev in Thread] Current Thread [Next in Thread]