diff --git a/externals/hcs/Makefile b/externals/hcs/Makefile
index 61315d4391aa0b2a31af3860095f7cfdb6a3074e..075b7a541d9151719b33ff34c05be303366dac9f 100644
--- a/externals/hcs/Makefile
+++ b/externals/hcs/Makefile
@@ -6,7 +6,7 @@ LIBRARY_NAME = hcs
 # add your .c source files, one object per file, to the SOURCES
 # variable, help files will be included automatically, and for GUI
 # objects, the matching .tcl file too
-SOURCES = canvas_name.c ce_path.c classpath.c colorpanel.c cursor.c folder_list.c group.c helppath.c passwd.c screensize.c setenv.c split_path.c sql_query.c stat.c sys_gui.c uname.c unsetenv.c version.c window_name.c
+SOURCES = canvas_name.c ce_path.c classpath.c colorpanel.c folder_list.c group.c helppath.c passwd.c screensize.c setenv.c split_path.c sql_query.c stat.c sys_gui.c uname.c unsetenv.c version.c window_name.c
 
 # For objects that only build on certain platforms, add those to the SOURCES
 # line for the right platforms.
@@ -14,7 +14,7 @@ SOURCES_linux = ifeel.c
 
 # list all pd objects (i.e. myobject.pd) files here, and their helpfiles will
 # be included automatically
-PDOBJECTS = ISOdate.pd ISOtime.pd ascii2int.pd autoscale.pd blocksize_in_ms.pd debounce.pd debug.pd embed.pd file_type.pd float2ascii.pd get-audio-dialog.pd get-midi-dialog.pd gid2group_name.pd group_name2gid.pd hostname.pd make-audio-dialog.pd mouse_region.pd noquit.pd pi.pd pointer_position.pd pwm.pd pwm~.pd split_my_msgs.pd square~.pd tcl_version.pd tkconsole.pd tremolo~.pd uid2username.pd username2uid.pd
+PDOBJECTS = ISOdate.pd ISOtime.pd ascii2int.pd autoscale.pd blocksize_in_ms.pd debounce.pd debug.pd embed.pd file_type.pd float2ascii.pd get-audio-dialog.pd get-midi-dialog.pd gid2group_name.pd group_name2gid.pd hostname.pd make-audio-dialog.pd mouse_region.pd noquit.pd pi.pd pointer_position.pd pwm.pd pwm~.pd split_my_msgs.pd square~.pd tcl_version.pd tkconsole.pd tremolo~.pd uid2username.pd username2uid.pd cursor.pd
 
 # example patches and related files, in the 'examples' subfolder
 EXAMPLES = changing_the_colors.pd move_pd_window.pd cursor.gif
diff --git a/externals/hcs/cursor-help.pd b/externals/hcs/cursor-help.pd
index 92b4d1098c1c2c7655a7d4d0d8428119c07e5da4..f37d63114284b5e1bf65b23c3082eeadc0f3bce8 100644
--- a/externals/hcs/cursor-help.pd
+++ b/externals/hcs/cursor-help.pd
@@ -1,13 +1,13 @@
-#N canvas 1 88 659 523 10;
-#X obj 388 293 pddp/pddplink http://tcl.tk/man/tcl8.5/TkCmd/cursors.htm
+#N canvas 226 35 659 617 10;
+#X obj 388 333 pddp/pddplink http://tcl.tk/man/tcl8.5/TkCmd/cursors.htm
 ;
-#X text 273 276 Here's a complete list of the available cursors:;
-#X msg 441 124 dot;
-#X msg 518 124 gumby;
-#X msg 561 124 gobbler;
-#X msg 370 124 coffee_mug;
-#X msg 471 124 pirate;
-#X msg 42 44 runmode_nothing;
+#X text 273 316 Here's a complete list of the available cursors:;
+#X msg 441 164 dot;
+#X msg 518 164 gumby;
+#X msg 561 164 gobbler;
+#X msg 370 164 coffee_mug;
+#X msg 471 164 pirate;
+#X msg 42 84 runmode_nothing;
 #N canvas 0 22 462 312 make 0;
 #X obj 95 9 inlet;
 #X obj 114 258 outlet;
@@ -25,46 +25,46 @@
 #X connect 6 0 7 0;
 #X connect 7 0 1 0;
 #X connect 7 1 1 0;
-#X restore 169 205 pd make message;
-#X msg 59 63 runmode_clickme;
-#X msg 75 82 runmode_thicken;
-#X msg 92 101 runmode_addpoint;
-#X msg 110 120 editmode_nothing;
-#X obj 169 266 cursor;
-#X text 342 238 <-- this is the actual message to send;
-#X text 15 18 set your mouse cursors for this patch:;
-#X text 279 309 Here are some pictures of the cursors:;
-#X obj 387 325 pddp/pddplink http://www.lehigh.edu/~sol0/ptk/cursors.gif
+#X restore 169 245 pd make message;
+#X msg 59 103 runmode_clickme;
+#X msg 75 122 runmode_thicken;
+#X msg 92 141 runmode_addpoint;
+#X msg 110 160 editmode_nothing;
+#X obj 169 306 cursor;
+#X text 342 278 <-- this is the actual message to send;
+#X text 15 58 set your mouse cursors for this patch:;
+#X text 279 349 Here are some pictures of the cursors:;
+#X obj 387 365 pddp/pddplink http://www.lehigh.edu/~sol0/ptk/cursors.gif
 ;
-#X msg 126 139 editmode_connect;
-#X msg 143 158 editmode_disconnect;
-#X msg 169 239 editmode_disconnect arrow;
-#X text 290 55 first pick a cursor:;
-#X text 189 80 <-- then choose your cursor mode;
-#X obj 46 390 cursor;
-#X msg 55 367 bang;
-#X obj 46 448 route x y;
-#X floatatom 33 472 5 0 0 0 x - -;
-#X floatatom 71 472 5 0 0 1 y - -;
-#X obj 46 342 tgl 15 0 empty empty empty 17 7 0 10 -262144 -1 -1 0
+#X msg 126 179 editmode_connect;
+#X msg 143 198 editmode_disconnect;
+#X msg 169 279 editmode_disconnect arrow;
+#X text 290 95 first pick a cursor:;
+#X text 189 120 <-- then choose your cursor mode;
+#X obj 46 480 cursor;
+#X msg 55 447 bang;
+#X obj 46 538 route x y;
+#X floatatom 33 562 5 0 0 0 x - -, f 5;
+#X floatatom 71 562 5 0 0 1 y - -, f 5;
+#X obj 46 382 tgl 15 0 empty empty empty 17 7 0 10 -262144 -1 -1 0
 1;
-#X floatatom 135 488 5 0 0 1 mousewheel - -;
-#X obj 46 414 route motion mousewheel button;
-#X text 65 339 turn on to get mouse motion and buttons;
-#X obj 164 440 route 1 2 3;
-#X obj 164 460 tgl 15 0 empty empty empty 17 7 0 10 -262144 -1 -1 0
+#X floatatom 135 578 5 0 0 1 mousewheel - -, f 5;
+#X obj 46 504 route motion mousewheel button;
+#X text 65 379 turn on to get mouse motion and buttons;
+#X obj 164 530 route 1 2 3;
+#X obj 164 550 tgl 15 0 empty empty empty 17 7 0 10 -262144 -1 -1 0
 1;
-#X obj 185 460 tgl 15 0 empty empty empty 17 7 0 10 -262144 -1 -1 0
+#X obj 185 550 tgl 15 0 empty empty empty 17 7 0 10 -262144 -1 -1 0
 1;
-#X obj 206 460 tgl 15 0 empty empty empty 17 7 0 10 -262144 -1 -1 0
+#X obj 206 550 tgl 15 0 empty empty empty 17 7 0 10 -262144 -1 -1 0
 1;
-#X text 227 459 <-- mouse buttons;
-#X text 86 366 or bang to get the current mouse position;
+#X text 227 549 <-- mouse buttons;
+#X text 86 446 or bang to get the current mouse position;
 #N canvas 583 22 481 747 image 0;
 #X obj 240 373 moonlib/image examples/cursor.gif 0;
-#X restore 509 309 pd image of all cursors;
-#X msg 328 124 clock;
-#X msg 288 124 arrow;
+#X restore 509 349 pd image of all cursors;
+#X msg 328 164 clock;
+#X msg 288 164 arrow;
 #N canvas 413 246 494 344 META 0;
 #X text 12 155 HELP_PATCH_AUTHORS "pd meta" information added by Jonathan
 Wilkes for Pd version 0.42.;
@@ -76,7 +76,17 @@ runmode_addpoint editmode_nothing editmode_connect editmode_disconnect
 ;
 #X text 12 115 OUTLET_0;
 #X text 12 5 KEYWORDS control canvas-op user_input;
-#X restore 600 498 pd META;
+#X restore 600 588 pd META;
+#X text 18 8 Note: this is a Purr Data convenience abstraction to support
+the mouse events [cursor] provides. Currently custom cursors don't
+work in Purr Data.;
+#X text 77 398 Purr Data Note: the [cursor] convenience abstraction
+still receives mouse motion data even when the toggle is off. It just
+doesn't forward it to the outlet.;
+#X text 250 492 Purr Data note: motion x/y coordinates are not screen
+coordinates but the coordinates of the mouse on the current canvas.
+You can get something close to screen coordinates by displaying the
+current canvas in full screen mode.;
 #X connect 2 0 8 1;
 #X connect 3 0 8 1;
 #X connect 4 0 8 1;
diff --git a/externals/hcs/cursor.pd b/externals/hcs/cursor.pd
new file mode 100644
index 0000000000000000000000000000000000000000..9276647639579df0c210729827c048e2af48757f
--- /dev/null
+++ b/externals/hcs/cursor.pd
@@ -0,0 +1,87 @@
+#N canvas 221 76 450 464 10;
+#X obj 68 279 list prepend motion;
+#X obj 37 5 inlet;
+#X obj 68 400 outlet;
+#X obj 68 368 spigot;
+#X msg 201 255 mousewheel \$1;
+#X obj 68 301 list trim;
+#X obj 304 246 swap;
+#X obj 304 268 pack;
+#X obj 304 290 list prepend button;
+#X obj 304 312 list trim;
+#X obj 37 125 route bang float;
+#X obj 83 147 s \$0-spigot;
+#X obj 115 368 r \$0-spigot;
+#X obj 68 192 swap;
+#N canvas 24 99 450 323 x 0;
+#X obj 192 38 inlet;
+#X obj 192 105 f;
+#X msg 192 127 x \$1;
+#X obj 192 149 outlet;
+#X connect 0 0 1 0;
+#X connect 1 0 2 0;
+#X connect 2 0 3 0;
+#X restore 114 242 pd x;
+#N canvas 24 99 450 323 y 0;
+#X obj 192 38 inlet;
+#X obj 192 105 f;
+#X obj 192 149 outlet;
+#X msg 192 127 y \$1;
+#X connect 0 0 1 0;
+#X connect 1 0 3 0;
+#X connect 3 0 2 0;
+#X restore 68 242 pd y;
+#X obj 37 192 b;
+#X obj 37 27 route bang float;
+#X obj 37 57 f;
+#X msg 37 89 1 \, bang \, \$1;
+#N canvas 0 0 450 300 error 0;
+#X obj 41 19 inlet;
+#X obj 41 41 route runmode_nothing runmode_clickme runmode_thicken
+runmode_addpoint editmode_nothing editmode_connect editmode_disconnect
+;
+#X msg 41 121 custom cursors not currently supported in Purr Data;
+#X obj 41 149 print cursor;
+#X connect 0 0 1 0;
+#X connect 1 0 2 0;
+#X connect 1 1 2 0;
+#X connect 1 2 2 0;
+#X connect 1 3 2 0;
+#X connect 1 4 2 0;
+#X connect 1 5 2 0;
+#X connect 1 6 2 0;
+#X connect 2 0 3 0;
+#X restore 130 57 pd error;
+#X obj 68 170 legacy_mousemotion;
+#X obj 173 220 legacy_mousewheel;
+#X obj 304 220 legacy_mouseclick;
+#X connect 0 0 5 0;
+#X connect 1 0 17 0;
+#X connect 3 0 2 0;
+#X connect 4 0 3 0;
+#X connect 5 0 3 0;
+#X connect 6 0 7 0;
+#X connect 6 1 7 1;
+#X connect 7 0 8 0;
+#X connect 8 0 9 0;
+#X connect 9 0 3 0;
+#X connect 10 0 16 0;
+#X connect 10 1 11 0;
+#X connect 12 0 3 1;
+#X connect 13 0 15 0;
+#X connect 13 1 14 0;
+#X connect 14 0 0 0;
+#X connect 15 0 0 0;
+#X connect 16 0 14 0;
+#X connect 16 0 15 0;
+#X connect 17 0 18 0;
+#X connect 17 1 18 1;
+#X connect 17 1 10 0;
+#X connect 17 2 20 0;
+#X connect 18 0 19 0;
+#X connect 19 0 10 0;
+#X connect 21 0 13 0;
+#X connect 21 1 13 1;
+#X connect 22 1 4 0;
+#X connect 23 0 6 0;
+#X connect 23 1 6 1;
diff --git a/pd/doc/5.reference/legacy_mouseclick-help.pd b/pd/doc/5.reference/legacy_mouseclick-help.pd
new file mode 100644
index 0000000000000000000000000000000000000000..746b87a17b8b4af6e36a6da945a9c3e0aa773cd4
--- /dev/null
+++ b/pd/doc/5.reference/legacy_mouseclick-help.pd
@@ -0,0 +1,88 @@
+#N canvas 224 25 555 619 10;
+#X obj 0 585 cnv 15 552 21 empty \$0-pddp.cnv.footer empty 20 12 0
+14 -228856 -66577 0;
+#X obj 0 0 cnv 15 552 40 empty \$0-pddp.cnv.header legacy_mouseclick
+3 12 0 18 -204280 -1 0;
+#X obj 0 307 cnv 3 550 3 empty \$0-pddp.cnv.inlets inlets 8 12 0 13
+-228856 -1 0;
+#N canvas 487 278 494 344 META 0;
+#X text 12 145 LIBRARY internal;
+#X text 12 25 LICENSE SIBSD;
+#X text 12 65 OUTLET_0 float;
+#X text 12 5 KEYWORDS control user_input legacy;
+#X text 12 45 DESCRIPTION get window x/y coordinates from the mouse
+;
+#X text 12 85 OUTLET_1 float;
+#X text 12 165 AUTHOR Jonathan Wilkes;
+#X text 12 185 HELP_PATCH_AUTHORS Jonathan Wilkes;
+#X text 12 105 OUTLET_2 float;
+#X text 12 125 OUTLET_3 float;
+#X restore 500 587 pd META;
+#X obj 0 335 cnv 3 550 3 empty \$0-pddp.cnv.outlets outlets 8 12 0
+13 -228856 -1 0;
+#X obj 0 475 cnv 3 550 3 empty \$0-pddp.cnv.argument arguments 8 12
+0 13 -228856 -1 0;
+#X obj 0 513 cnv 3 550 3 empty \$0-pddp.cnv.more_info more_info 8 12
+0 13 -228856 -1 0;
+#N canvas 222 479 428 145 Related_objects 0;
+#X obj 1 1 cnv 15 425 20 empty \$0-pddp.cnv.subheading empty 3 12 0
+14 -204280 -1 0;
+#X text 19 72 Externals and other object libraries;
+#X obj 18 94 pddp/helplink Gem/gemkeyboard;
+#X obj 18 114 pddp/helplink Gem/gemkeyname;
+#X obj 141 42 legacy_mousewheel;
+#X text 7 1 [legacy_mousemotion] Related Objects;
+#X obj 21 42 legacy_mousemotion;
+#X restore 102 588 pd Related_objects;
+#X text 98 343 float;
+#X obj 78 344 cnv 17 3 30 empty \$0-pddp.cnv.let.0 0 5 9 0 16 -228856
+-162280 0;
+#X text 98 311 (none);
+#X floatatom 122 230 5 0 0 0 x - -, f 5;
+#X obj 4 587 pddp/pddplink all_about_help_patches.pd -text Usage Guide
+;
+#X text 63 114 The interface for this object isn't stable. Don't depend
+on it for building stable abstractions. Instead use the mouse-related
+methods to [draw] with data structures for a better mouse event interface.
+;
+#X floatatom 155 210 5 0 0 0 y - -, f 5;
+#X text 195 211 These coordinates are relative to the canvas window
+;
+#X text 195 231 With the current Purr Data GUI there isn't an easy
+;
+#X text 195 247 way to get the screen coordinates. But for legacy;
+#X text 195 262 externals this should be good enough.;
+#X text 168 311 - input comes directly from the mouse events in the
+GUI.;
+#X text 98 374 float;
+#X obj 78 375 cnv 17 3 30 empty \$0-pddp.cnv.let.0 1 5 9 0 16 -228856
+-162280 0;
+#X text 98 479 (none);
+#X text 99 517 Unfortunately this object only returns coordinates when
+the mouse is hovering over a canvas window. To simulate something close
+to screen coordinates \, put the current canvas into full-screen mode
+from the "View" menu.;
+#X obj 56 183 legacy_mouseclick;
+#X floatatom 89 257 5 0 0 1 button_id - -, f 5;
+#X floatatom 56 278 5 0 0 1 0=up/1=down - -, f 5;
+#X text 63 54 This is a convenience class that generates output when
+the mouse is clicked on a Pd canvas. It is used to provide output for
+some old legacy externals from Pd-extended.;
+#X text 98 405 float;
+#X obj 78 406 cnv 17 3 30 empty \$0-pddp.cnv.let.0 2 5 9 0 16 -228856
+-162280 0;
+#X text 168 405 - x coordinate for the canvas window over which the
+mouse is currently hovering.;
+#X text 98 436 float;
+#X obj 78 437 cnv 17 3 30 empty \$0-pddp.cnv.let.0 3 5 9 0 16 -228856
+-162280 0;
+#X text 168 436 - y coordinate for the canvas window over which the
+mouse is currently hovering.;
+#X text 168 343 - 0 = mouseup \, 1 = mousedown;
+#X text 168 374 - id of the button clicked. 1 = left click \, 2 = middle
+button \, 3 = right click. (Note: may be platform specific);
+#X text 11 23 mouse button notification on current canvas;
+#X connect 24 0 26 0;
+#X connect 24 1 25 0;
+#X connect 24 2 11 0;
+#X connect 24 3 14 0;
diff --git a/pd/doc/5.reference/legacy_mousemotion-help.pd b/pd/doc/5.reference/legacy_mousemotion-help.pd
new file mode 100644
index 0000000000000000000000000000000000000000..9302f3901a1d9576ff2b573bf54f64dc0fb12629
--- /dev/null
+++ b/pd/doc/5.reference/legacy_mousemotion-help.pd
@@ -0,0 +1,75 @@
+#N canvas 190 29 555 619 10;
+#X obj 0 585 cnv 15 552 21 empty \$0-pddp.cnv.footer empty 20 12 0
+14 -228856 -66577 0;
+#X obj 0 0 cnv 15 552 40 empty \$0-pddp.cnv.header legacy_mousemotion
+3 12 0 18 -204280 -1 0;
+#X obj 0 317 cnv 3 550 3 empty \$0-pddp.cnv.inlets inlets 8 12 0 13
+-228856 -1 0;
+#N canvas 487 278 494 344 META 0;
+#X text 12 105 LIBRARY internal;
+#X text 12 25 LICENSE SIBSD;
+#X text 12 65 OUTLET_0 float;
+#X text 12 5 KEYWORDS control user_input legacy;
+#X text 12 45 DESCRIPTION get window x/y coordinates from the mouse
+;
+#X text 12 85 OUTLET_1 float;
+#X text 12 125 AUTHOR Jonathan Wilkes;
+#X text 12 145 HELP_PATCH_AUTHORS Jonathan Wilkes;
+#X restore 500 587 pd META;
+#X obj 0 345 cnv 3 550 3 empty \$0-pddp.cnv.outlets outlets 8 12 0
+13 -228856 -1 0;
+#X obj 0 435 cnv 3 550 3 empty \$0-pddp.cnv.argument arguments 8 12
+0 13 -228856 -1 0;
+#X obj 0 473 cnv 3 550 3 empty \$0-pddp.cnv.more_info more_info 8 12
+0 13 -228856 -1 0;
+#N canvas 222 479 428 145 Related_objects 0;
+#X obj 1 1 cnv 15 425 20 empty \$0-pddp.cnv.subheading empty 3 12 0
+14 -204280 -1 0;
+#X text 19 72 Externals and other object libraries;
+#X obj 18 94 pddp/helplink Gem/gemkeyboard;
+#X obj 18 114 pddp/helplink Gem/gemkeyname;
+#X obj 21 42 legacy_mouseclick;
+#X obj 141 42 legacy_mousewheel;
+#X text 7 1 [legacy_mousemotion] Related Objects;
+#X restore 102 588 pd Related_objects;
+#X text 98 353 float;
+#X obj 78 354 cnv 17 3 30 empty \$0-pddp.cnv.let.0 0 5 9 0 16 -228856
+-162280 0;
+#X text 98 321 (none);
+#X floatatom 56 230 5 0 0 0 x_pos - -, f 5;
+#X obj 4 587 pddp/pddplink all_about_help_patches.pd -text Usage Guide
+;
+#X text 11 23 get mouse position on current canvas;
+#X text 63 54 This is a convenience class that outputs the mouse coordinates
+for all "motion" messages sent from the GUI. It is used to provide
+output for some old legacy externals from Pd-extended.;
+#X text 63 114 The interface for this object isn't stable. Don't depend
+on it for building stable abstractions. Instead use the mouse-related
+methods to [draw] with data structures for a better mouse event interface.
+;
+#X obj 56 193 legacy_mousemotion;
+#X floatatom 161 230 5 0 0 0 y_pos - -, f 5;
+#X text 205 193 These coordinates are relative to the canvas window
+;
+#X text 205 213 With the current Purr Data GUI there isn't an easy
+;
+#X text 205 229 way to get the screen coordinates. But for legacy;
+#X text 205 244 externals this should be good enough.;
+#X text 168 321 - input comes directly from the mouse events in the
+GUI.;
+#X text 168 353 - x coordinate for the canvas window over which the
+mouse is currently hovering.;
+#X text 98 393 float;
+#X obj 78 394 cnv 17 3 30 empty \$0-pddp.cnv.let.0 1 5 9 0 16 -228856
+-162280 0;
+#X text 168 393 - y coordinate for the canvas window over which the
+mouse is currently hovering.;
+#X text 98 439 (none);
+#X text 99 477 Unfortunately this object only returns coordinates when
+the mouse is hovering over a canvas window. To simulate something close
+to screen coordinates \, put the current canvas into full-screen mode
+from the "View" menu.;
+#X text 99 537 Also note that this object is expensive-- it executes
+a Pd method for every mousemove event coming from the GUI;
+#X connect 16 0 11 0;
+#X connect 16 1 17 0;
diff --git a/pd/doc/5.reference/legacy_mousewheel.pd b/pd/doc/5.reference/legacy_mousewheel.pd
new file mode 100644
index 0000000000000000000000000000000000000000..f72ce1520738e9cbc32eb7dab50c73636eb7a068
--- /dev/null
+++ b/pd/doc/5.reference/legacy_mousewheel.pd
@@ -0,0 +1,74 @@
+#N canvas 234 36 555 619 10;
+#X obj 0 585 cnv 15 552 21 empty \$0-pddp.cnv.footer empty 20 12 0
+14 -228856 -66577 0;
+#X obj 0 0 cnv 15 552 40 empty \$0-pddp.cnv.header legacy_mousewheel
+3 12 0 18 -204280 -1 0;
+#X obj 0 307 cnv 3 550 3 empty \$0-pddp.cnv.inlets inlets 8 12 0 13
+-228856 -1 0;
+#N canvas 487 278 494 344 META 0;
+#X text 12 124 LIBRARY internal;
+#X text 12 25 LICENSE SIBSD;
+#X text 12 65 OUTLET_0 float;
+#X text 12 5 KEYWORDS control user_input legacy;
+#X text 12 45 DESCRIPTION get window x/y coordinates from the mouse
+;
+#X text 12 85 OUTLET_1 float;
+#X text 12 144 AUTHOR Jonathan Wilkes;
+#X text 12 164 HELP_PATCH_AUTHORS Jonathan Wilkes;
+#X text 12 105 OUTLET_2 float;
+#X restore 500 587 pd META;
+#X obj 0 335 cnv 3 550 3 empty \$0-pddp.cnv.outlets outlets 8 12 0
+13 -228856 -1 0;
+#X obj 0 455 cnv 3 550 3 empty \$0-pddp.cnv.argument arguments 8 12
+0 13 -228856 -1 0;
+#X obj 0 493 cnv 3 550 3 empty \$0-pddp.cnv.more_info more_info 8 12
+0 13 -228856 -1 0;
+#N canvas 222 479 428 145 Related_objects 0;
+#X obj 1 1 cnv 15 425 20 empty \$0-pddp.cnv.subheading empty 3 12 0
+14 -204280 -1 0;
+#X text 19 72 Externals and other object libraries;
+#X obj 18 94 pddp/helplink Gem/gemkeyboard;
+#X obj 18 114 pddp/helplink Gem/gemkeyname;
+#X obj 21 42 legacy_mousemotion;
+#X obj 141 42 legacy_mouseclick;
+#X text 7 1 [legacy_mousewheel] Related Objects;
+#X restore 102 588 pd Related_objects;
+#X text 98 343 float;
+#X obj 78 344 cnv 17 3 30 empty \$0-pddp.cnv.let.0 0 5 9 0 16 -228856
+-162280 0;
+#X text 98 311 (none);
+#X floatatom 155 260 5 0 0 0 delta_z - -, f 5;
+#X obj 4 587 pddp/pddplink all_about_help_patches.pd -text Usage Guide
+;
+#X text 63 114 The interface for this object isn't stable. Don't depend
+on it for building stable abstractions. Instead use the mouse-related
+methods to [draw] with data structures for a better mouse event interface.
+;
+#X text 168 311 - input comes directly from the mouse events in the
+GUI.;
+#X text 98 374 float;
+#X obj 78 375 cnv 17 3 30 empty \$0-pddp.cnv.let.0 1 5 9 0 16 -228856
+-162280 0;
+#X text 98 459 (none);
+#X floatatom 105 237 5 0 0 0 delta_y - -, f 5;
+#X floatatom 56 217 5 0 0 0 delta_x - -, f 5;
+#X text 98 405 float;
+#X obj 78 406 cnv 17 3 30 empty \$0-pddp.cnv.let.0 2 5 9 0 16 -228856
+-162280 0;
+#X obj 56 183 legacy_mousewheel;
+#X text 11 23 mouse wheel notification on current canvas;
+#X text 63 54 This is a convenience class that generates output when
+the mouse wheel is moved on a Pd canvas. It is used to provide output
+for some old legacy externals from Pd-extended.;
+#X text 175 204 Output is only received when the mouse is hovering
+over a Pd window.;
+#X text 168 343 - delta x;
+#X text 168 374 - delta y;
+#X text 168 405 - delta z;
+#X text 99 507 For platform-consistency \, output is normalized so
+that moving the wheel up generates a "-1" \, down generates "1" \,
+and "0" means no change. (Haven't tested this with delta x and delta
+z yet.);
+#X connect 22 0 19 0;
+#X connect 22 1 18 0;
+#X connect 22 2 11 0;
diff --git a/pd/nw/pd_canvas.js b/pd/nw/pd_canvas.js
index 8a02095e61e327e2686011239de46a1f41785115..7753dfbd796c8675b71c017536008ddb1593816e 100644
--- a/pd/nw/pd_canvas.js
+++ b/pd/nw/pd_canvas.js
@@ -751,13 +751,24 @@ var canvas_events = (function() {
 
     // MouseWheel event for zooming
     document.addEventListener("wheel", function(evt) {
-        if (pdgui.cmd_or_ctrl_key(evt)) {
-            if (evt.deltaY < 0) {
-                nw_window_zoom(name, +1);
-            } else if (evt.deltaY > 0) {
-                nw_window_zoom(name, -1);
+        var d = { deltaX: 0, deltaY: 0, deltaZ: 0 };
+        Object.keys(d).forEach(function(key) {
+            if (evt[key] < 0) {
+                d[key] = -1;
+            } else if (evt[key] > 0) {
+                d[key] = 1;
+            } else {
+                d[key] = 0;
             }
+        });
+        if (pdgui.cmd_or_ctrl_key(evt)) {
+            // scroll up for zoom-in, down for zoom-out
+            nw_window_zoom(name, -d.deltaY);
         }
+        // Send a message on to Pd for the [mousewheel] legacy object
+        // (in the future we can refcount if we want to prevent forwarding
+        // these messages when there's no extant receiver)
+        pdgui.pdsend(name, "legacy_mousewheel", d.deltaX, d.deltaY, d.deltaZ);
     });
 
     // The following is commented out because we have to set the
diff --git a/pd/src/g_editor.c b/pd/src/g_editor.c
index 3fdf9473bd2e780e284cd2b546ecf450bcc238a9..6674690471e76b485bf3183acb01b0a696015ee2 100644
--- a/pd/src/g_editor.c
+++ b/pd/src/g_editor.c
@@ -3756,11 +3756,43 @@ void canvas_doclick(t_canvas *x, int xpos, int ypos, int which,
     }
 }
 
+   // Dispatch mouseclick message to receiver (for legacy mouse event externals)
+void canvas_dispatch_mouseclick(t_float down, t_float xpos, t_float ypos,
+    t_float which)
+{
+    t_symbol *mouseclicksym = gensym("#legacy_mouseclick");
+    if (mouseclicksym->s_thing)
+    {
+        t_atom at[4];
+        SETFLOAT(at, down);
+        SETFLOAT(at+1, which);
+        SETFLOAT(at+2, xpos);
+        SETFLOAT(at+3, ypos);
+        pd_list(mouseclicksym->s_thing, &s_list, 4, at);
+    }
+}
+
 void canvas_mousedown(t_canvas *x, t_floatarg xpos, t_floatarg ypos,
     t_floatarg which, t_floatarg mod)
 {
     //fprintf(stderr,"canvas_mousedown %d\n", x->gl_editor->e_onmotion);
     canvas_doclick(x, xpos, ypos, which, mod, 1);
+    // now dispatch to any listeners
+    canvas_dispatch_mouseclick(1., xpos, ypos, which);
+}
+
+void canvas_mousewheel(t_canvas *x, t_floatarg xpos, t_floatarg ypos,
+    t_floatarg zpos)
+{
+    t_symbol *mousewheelsym = gensym("#legacy_mousewheel");
+    if (mousewheelsym->s_thing)
+    {
+        t_atom at[3];
+        SETFLOAT(at, xpos);
+        SETFLOAT(at+1, ypos);
+        SETFLOAT(at+2, zpos);
+        pd_list(mousewheelsym->s_thing, &s_list, 3, at);
+    }
 }
 
 int canvas_isconnected (t_canvas *x, t_text *ob1, int n1,
@@ -4770,6 +4802,8 @@ void canvas_mouseup(t_canvas *x,
     if (canvas_last_glist_mod == -1)
         canvas_doclick(x, xpos, ypos, 0,
             (glob_shift + glob_ctrl*2 + glob_alt*4), 0);
+    // now dispatch to any click listeners
+    canvas_dispatch_mouseclick(0., xpos, ypos, which);
 }
 
 /* Cheap hack to simulate mouseup at the last x/y coord. We use this in
@@ -5150,6 +5184,7 @@ extern void graph_checkgop_rect(t_gobj *z, t_glist *glist,
 void canvas_motion(t_canvas *x, t_floatarg xpos, t_floatarg ypos,
     t_floatarg fmod)
 {
+    static t_symbol *mousemotionsym;
     //fprintf(stderr,"motion %d %d %d %d\n",
     //    (int)xpos, (int)ypos, (int)fmod, canvas_last_glist_mod);
     //fprintf(stderr,"canvas_motion=%d\n",x->gl_editor->e_onmotion);
@@ -5312,6 +5347,16 @@ void canvas_motion(t_canvas *x, t_floatarg xpos, t_floatarg ypos,
     //        x, (int)xpos, (int)ypos);
     //}
     x->gl_editor->e_lastmoved = 1;
+    // Dispatch to any listeners for the motion message
+    if (!mousemotionsym)
+        mousemotionsym = gensym("#legacy_mousemotion");
+    if (mousemotionsym->s_thing)
+    {
+        t_atom at[2];
+        SETFLOAT(at, xpos);
+        SETFLOAT(at+1, ypos);
+        pd_list(mousemotionsym->s_thing, &s_list, 2, at);
+    }
 }
 
 void canvas_startmotion(t_canvas *x)
@@ -7549,8 +7594,10 @@ void g_editor_setup(void)
         A_FLOAT, A_FLOAT, A_FLOAT, A_NULL);
     class_addmethod(canvas_class, (t_method)canvas_mouseup_fake,
         gensym("mouseup_fake"), A_NULL);
-    class_addmethod(canvas_class, (t_method)canvas_mousedown_middle, gensym("mouse-2"),
-        A_FLOAT, A_FLOAT, A_FLOAT, A_NULL);
+    class_addmethod(canvas_class, (t_method)canvas_mousedown_middle,
+        gensym("mouse-2"), A_FLOAT, A_FLOAT, A_FLOAT, A_NULL);
+    class_addmethod(canvas_class, (t_method)canvas_mousewheel,
+        gensym("legacy_mousewheel"), A_FLOAT, A_FLOAT, A_FLOAT, A_NULL);
     class_addmethod(canvas_class, (t_method)canvas_key, gensym("key"),
         A_GIMME, A_NULL);
     class_addmethod(canvas_class, (t_method)canvas_motion, gensym("motion"),
diff --git a/pd/src/x_gui.c b/pd/src/x_gui.c
index f7f1fb70d438303567b06ace41c2eacdc179b309..b5b66ca6b9f285c586925715de4680785e116fb7 100644
--- a/pd/src/x_gui.c
+++ b/pd/src/x_gui.c
@@ -478,6 +478,141 @@ static void key_setup(void)
     //class_sethelpsymbol(keyname_class, gensym("key"));
 }
 
+/* ------------------ mouse classes for legacy externals ------------------ */
+
+/* Every other legacy external library has some ad hoc code for getting
+   mouse state within a Pd patch. All of them have different weird interfaces
+   and some are outright buggy.
+
+   Most of these return screen coordinates. This is unfortunately more of
+   a pain than it should be in nw.js. Instead, we return window coordinates
+   and hope that this is good enough for the uses to which these external
+   classes have been put. At worst the user can make the relevant canvas
+   full screen and get the desired behavior (minus the offset for the menu).
+
+   Most of the uses for mouse coordinates seem to do with tutorials that map
+   x/y positions to amplitude, frequency, etc. So these classes should be
+   good enough to build abstractions to do an end run around the relevant
+   externals.
+*/
+
+static t_symbol *mousemotion_sym, *mouseclick_sym, *mousewheel_sym;
+static t_class *mousemotion_class, *mouseclick_class, *mousewheel_class;
+
+typedef struct _mousemotion
+{
+    t_object x_obj;
+    t_outlet *x_outlet1;
+    t_outlet *x_outlet2;
+} t_mousemotion;
+
+static void *mousemotion_new( void)
+{
+    t_mousemotion *x = (t_mousemotion *)pd_new(mousemotion_class);
+    x->x_outlet1 = outlet_new(&x->x_obj, &s_float);
+    x->x_outlet2 = outlet_new(&x->x_obj, &s_float);
+    pd_bind(&x->x_obj.ob_pd, mousemotion_sym);
+    return (x);
+}
+
+static void mousemotion_list(t_mousemotion *x, t_symbol *s, int argc,
+    t_atom *argv)
+{
+    outlet_float(x->x_outlet2, atom_getfloatarg(1, argc, argv));
+    outlet_float(x->x_outlet1, atom_getfloatarg(0, argc, argv));
+}
+
+static void mousemotion_free(t_mousemotion *x)
+{
+    pd_unbind(&x->x_obj.ob_pd, mousemotion_sym);
+}
+
+typedef struct _mouseclick
+{
+    t_object x_obj;
+    t_outlet *x_outlet1;
+    t_outlet *x_outlet2;
+    t_outlet *x_outlet3;
+    t_outlet *x_outlet4;
+} t_mouseclick;
+
+static void *mouseclick_new( void)
+{
+    t_mouseclick *x = (t_mouseclick *)pd_new(mouseclick_class);
+    x->x_outlet1 = outlet_new(&x->x_obj, &s_float);
+    x->x_outlet2 = outlet_new(&x->x_obj, &s_float);
+    x->x_outlet3 = outlet_new(&x->x_obj, &s_float);
+    x->x_outlet4 = outlet_new(&x->x_obj, &s_float);
+    pd_bind(&x->x_obj.ob_pd, mouseclick_sym);
+    return (x);
+}
+
+static void mouseclick_list(t_mouseclick *x, t_symbol *s, int argc,
+    t_atom *argv)
+{
+    outlet_float(x->x_outlet4, atom_getfloatarg(3, argc, argv));
+    outlet_float(x->x_outlet3, atom_getfloatarg(2, argc, argv));
+    outlet_float(x->x_outlet2, atom_getfloatarg(1, argc, argv));
+    outlet_float(x->x_outlet1, atom_getfloatarg(0, argc, argv));
+}
+
+static void mouseclick_free(t_mouseclick *x)
+{
+    pd_unbind(&x->x_obj.ob_pd, mouseclick_sym);
+}
+
+typedef struct _mousewheel
+{
+    t_object x_obj;
+    t_outlet *x_outlet1;
+    t_outlet *x_outlet2;
+    t_outlet *x_outlet3;
+} t_mousewheel;
+
+static void *mousewheel_new( void)
+{
+    t_mousewheel *x = (t_mousewheel *)pd_new(mousewheel_class);
+    x->x_outlet1 = outlet_new(&x->x_obj, &s_float);
+    x->x_outlet2 = outlet_new(&x->x_obj, &s_float);
+    x->x_outlet3 = outlet_new(&x->x_obj, &s_float);
+    pd_bind(&x->x_obj.ob_pd, mousewheel_sym);
+    return (x);
+}
+
+static void mousewheel_list(t_mousewheel *x, t_symbol *s, int argc,
+    t_atom *argv)
+{
+    outlet_float(x->x_outlet3, atom_getfloatarg(2, argc, argv));
+    outlet_float(x->x_outlet2, atom_getfloatarg(1, argc, argv));
+    outlet_float(x->x_outlet1, atom_getfloatarg(0, argc, argv));
+}
+
+static void mousewheel_free(t_mousewheel *x)
+{
+    pd_unbind(&x->x_obj.ob_pd, mousewheel_sym);
+}
+
+static void mouse_setup(void)
+{
+    mousemotion_class = class_new(gensym("legacy_mousemotion"),
+        (t_newmethod)mousemotion_new, (t_method)mousemotion_free,
+        sizeof(t_mousemotion), CLASS_NOINLET, 0);
+    class_addlist(mousemotion_class, mousemotion_list);
+    mousemotion_sym = gensym("#legacy_mousemotion");
+
+    mouseclick_class = class_new(gensym("legacy_mouseclick"),
+        (t_newmethod)mouseclick_new, (t_method)mouseclick_free,
+        sizeof(t_mouseclick), CLASS_NOINLET, 0);
+    class_addlist(mouseclick_class, mouseclick_list);
+    mouseclick_sym = gensym("#legacy_mouseclick");
+
+    mousewheel_class = class_new(gensym("legacy_mousewheel"),
+        (t_newmethod)mousewheel_new, (t_method)mousewheel_free,
+        sizeof(t_mousewheel), CLASS_NOINLET, 0);
+    class_addfloat(mousewheel_class, mousewheel_list);
+    mousewheel_sym = gensym("#legacy_mousewheel");
+}
+
 /* -------------------------- setup routine ------------------------------ */
 
 void x_gui_setup(void)
@@ -486,6 +621,7 @@ void x_gui_setup(void)
     openpanel_setup();
     savepanel_setup();
     key_setup();
+    mouse_setup();
     // jsarlo
     magicGlass_setup();
     // end jsarlo