Version 6 of VirtualMouse Cross Platform Mouse Handling

Updated 2008-10-06 02:20:52 by Duoas

A package to handle the mouse in a cross-platform way.

 What: VirtualMouse 1.0
 Where: here, alas
 Description: A simple package to interface with the mouse on the user platform's terms.
 Released: 02 May 2007
 License: MIT-style

If you find this useful, have questions or other commentary, bug fixes, etc. please post here. 02-05-2007 Duoas

05-10-2008 Alas, I no longer have online storage at the moment, so I'll post the entire package here. (It is pretty small.)

  # virtualmouse.tcl
  #
  # Provide cross-platform virtual mouse event handling.
  # Copyright 2006 Michael Thomas Greer
  #
  #  Permission is hereby granted, free of charge, to any person obtaining a
  #  copy of this software and associated documentation files (the "Software"),
  #  to deal in the Software without restriction, including without limitation
  #  the rights to use, copy, modify, merge, publish, distribute, sublicense,
  #  and/or sell copies of the Software, and to permit persons to whom the
  #  Software is furnished to do so, subject to the following conditions:
  #
  #  The above copyright notice and this permission notice shall be included in
  #  all copies or substantial portions of the Software.
  #
  #  Except as contained in this notice, the name of the above copyright
  #  holder shall not be used in advertising or otherwise to promote the sale,
  #  use or other dealings in this Software without prior written authorization.
  #
  #  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  #  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  #  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
  #  THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  #  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
  #  FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
  #  DEALINGS IN THE SOFTWARE.
  #

  package require Tk 8.0

  #-----------------------------------------------------------------------------
  namespace eval ::virtualmouse:: {
  #-----------------------------------------------------------------------------
    namespace export setVirtualMouseEvents

    variable  MouseButton 0
    variable  MouseLocked false  ;# Prevent middle press after left or right has already triggered
    variable  AfterInfoID 0

    variable  Timeout
    array set Timeout {macintosh 375 windows 150}

    package provide virtualmouse 1.0
    }

  #-----------------------------------------------------------------------------
  # setVirtualMouseEvents
  #-----------------------------------------------------------------------------
  proc ::virtualmouse::setVirtualMouseEvents {
    {mode     abstract}
    {platform default}
    } {
    # Remove all old event bindings ............................................
    foreach vevent {
      <<MousePressLeft>>    <<MouseMotionLeft>>    <<MouseReleaseLeft>>
      <<MousePressMiddle>>  <<MouseMotionMiddle>>  <<MouseReleaseMiddle>>
      <<MousePressRight>>   <<MouseMotionRight>>   <<MouseReleaseRight>>
      } {
      event delete $vevent
      }
    foreach event {
      <ButtonPress-1>      <ButtonPress-2>          <ButtonPress-3>
      <Alt-ButtonPress-1>  <Control-ButtonPress-1>
      <B1-ButtonPress-3>   <B3-ButtonPress-1>
      <B1-Motion>          <B2-Motion>              <B3-Motion>
      <ButtonRelease-1>    <ButtonRelease-2>        <ButtonRelease-3>
      } {
      bind all $event {}
      }

    # Argument defaults ........................................................
    if {$platform eq {default}} {set platform $::tcl_platform(platform)}

    if {[lsearch {direct abstract} $mode] < 0} {
      return -code error {Invalid mode. Must be one of: direct abstract}
      }
    if {[lsearch {macintosh unix windows} $platform] < 0} {
      return -code error {Invalid platform. Must be one of: macintosh unix windows}
      }

    # Bindings .................................................................
    eval Set[string totitle $mode][string totitle $platform]MouseEvents
    }

  #-----------------------------------------------------------------------------
  proc ::virtualmouse::SetDirectMacintoshMouseEvents {} {
  #-----------------------------------------------------------------------------
    bind all <ButtonPress-1>         {::virtualmouse::ButtonPress 1 %W %X %Y %x %y %s %E %t}
    bind all <Option-ButtonPress-1>  {::virtualmouse::ButtonPress 2 %W %X %Y %x %y %s %E %t}
    bind all <Control-ButtonPress-1> {::virtualmouse::ButtonPress 3 %W %X %Y %x %y %s %E %t}

    bind all <B1-Motion>             {::virtualmouse::Motion        %W %X %Y %x %y %s %E %t}

    bind all <ButtonRelease-1>       {::virtualmouse::ButtonRelease %W %X %Y %x %y %s %E %t}

    event add <<MousePressMiddle>>   <ButtonPress-2>
    event add <<MouseMotionMiddle>>  <B2-Motion>
    event add <<MouseReleaseMiddle>> <ButtonRelease-2>

    event add <<MousePressRight>>    <ButtonPress-3>
    event add <<MouseMotionRight>>   <B3-Motion>
    event add <<MouseReleaseRight>>  <ButtonRelease-3>
    }

  #-----------------------------------------------------------------------------
  proc ::virtualmouse::SetDirectUnixMouseEvents {} {
  #-----------------------------------------------------------------------------
    event add <<MousePressLeft>>     <ButtonPress-1>
    event add <<MousePressMiddle>>   <ButtonPress-2>
    event add <<MousePressRight>>    <ButtonPress-3>

    event add <<MouseMotionLeft>>    <B1-Motion>
    event add <<MouseMotionMiddle>>  <B2-Motion>
    event add <<MouseMotionRight>>   <B3-Motion>

    event add <<MouseReleaseLeft>>   <ButtonRelease-1>
    event add <<MouseReleaseMiddle>> <ButtonRelease-2>
    event add <<MouseReleaseRight>>  <ButtonRelease-3>
    }

  #-----------------------------------------------------------------------------
  proc ::virtualmouse::SetDirectWindowsMouseEvents {} {
  #-----------------------------------------------------------------------------
    bind all <ButtonPress-1>         {::virtualmouse::ButtonPressTimer 1 %W %X %Y %x %y %s %E %t windows}
    bind all <ButtonPress-3>         {::virtualmouse::ButtonPressTimer 3 %W %X %Y %x %y %s %E %t windows}

    bind all <B1-ButtonPress-3>      {::virtualmouse::ButtonPress      2 %W %X %Y %x %y %s %E %t}
    bind all <B3-ButtonPress-1>      {::virtualmouse::ButtonPress      2 %W %X %Y %x %y %s %E %t}

    bind all <B1-Motion> {
      ::virtualmouse::ButtonPressTimeout %W %X %Y %x %y %s %E %t
      ::virtualmouse::Motion             %W %X %Y %x %y %s %E %t
      }
    bind all <B3-Motion> {
      ::virtualmouse::ButtonPressTimeout %W %X %Y %x %y %s %E %t
      ::virtualmouse::Motion             %W %X %Y %x %y %s %E %t
      }

    bind all <ButtonRelease-1> {
      ::virtualmouse::ButtonPressTimeout %W %X %Y %x %y %s %E %t
      ::virtualmouse::ButtonRelease      %W %X %Y %x %y %s %E %t
      }
    bind all <ButtonRelease-3> {
      ::virtualmouse::ButtonPressTimeout %W %X %Y %x %y %s %E %t
      ::virtualmouse::ButtonRelease      %W %X %Y %x %y %s %E %t
      }

    bind all <ButtonPress-2>         {::virtualmouse::ButtonPress 2 %W %X %Y %x %y %s %E %t}
    bind all <B2-Motion>             {::virtualmouse::Motion        %W %X %Y %x %y %s %E %t}
    bind all <ButtonRelease-2>       {::virtualmouse::ButtonRelease %W %X %Y %x %y %s %E %t
      ::virtualmouse::setVirtualMouseEvents direct unix
      }
    }

  #-----------------------------------------------------------------------------
  proc ::virtualmouse::SetAbstractMacintoshMouseEvents {} {
  #-----------------------------------------------------------------------------
    bind all <ButtonPress-1>         {::virtualmouse::ButtonPressTimer 3 %W %X %Y %x %y %s %E %t macintosh}
    bind all <Option-ButtonPress-1>  {::virtualmouse::ButtonPress      2 %W %X %Y %x %y %s %E %t}
    bind all <Control-ButtonPress-1> {::virtualmouse::ButtonPress      3 %W %X %Y %x %y %s %E %t}

    bind all <B1-Motion> {
      ::virtualmouse::ButtonPressChoose 1 3 %W %X %Y %x %y %s %E %t
      ::virtualmouse::Motion                %W %X %Y %x %y %s %E %t
      }

    bind all <ButtonRelease-1> {
      ::virtualmouse::ButtonPressChoose 1 3 %W %X %Y %x %y %s %E %t
      ::virtualmouse::ButtonRelease         %W %X %Y %x %y %s %E %t
      }

    bind all <ButtonPress-2>         {::virtualmouse::ButtonPress 2 %W %X %Y %x %y %s %E %t}
    bind all <ButtonPress-3>         {::virtualmouse::ButtonPress 3 %W %X %Y %x %y %s %E %t}
    bind all <B2-Motion>             {::virtualmouse::Motion        %W %X %Y %x %y %s %E %t}
    bind all <B3-Motion>             {::virtualmouse::Motion        %W %X %Y %x %y %s %E %t}
    bind all <ButtonRelease-2>       {::virtualmouse::ButtonRelease %W %X %Y %x %y %s %E %t
                                      ::virtualmouse::setVirtualMouseEvents direct macintosh}
    bind all <ButtonRelease-3>       [bind all <ButtonRelease-2>]
    }

  #-----------------------------------------------------------------------------
  proc ::virtualmouse::SetAbstractUnixMouseEvents {} {
  #-----------------------------------------------------------------------------
    SetDirectUnixMouseEvents
    }

  #-----------------------------------------------------------------------------
  proc ::virtualmouse::SetAbstractWindowsMouseEvents {} {
  #-----------------------------------------------------------------------------
    SetDirectWindowsMouseEvents
    }

  #-----------------------------------------------------------------------------
  proc ::virtualmouse::ButtonPress {btn win rootx rooty x y state event time} {
  #-----------------------------------------------------------------------------
    if {$::virtualmouse::MouseLocked} return

    set ::virtualmouse::MouseButton $btn
    set ::virtualmouse::MouseLocked true

    if {$::virtualmouse::AfterInfoID != 0} {
      after cancel $::virtualmouse::AfterInfoID
      set ::virtualmouse::AfterInfoID 0
      }

    event generate $win [lindex {err
      <<MousePressLeft>> <<MousePressMiddle>> <<MousePressRight>>
      } $btn] \
      -rootx $rootx -rooty $rooty -x $x -y $y -state $state -sendevent $event -time $time

    return
    }

  #-----------------------------------------------------------------------------
  proc ::virtualmouse::ButtonPressTimer {btn win rootx rooty x y state event time platform} {
  #-----------------------------------------------------------------------------
    set ::virtualmouse::MouseButton $btn
    set ::virtualmouse::AfterInfoID \
      [after $::virtualmouse::Timeout($platform) "
        ::virtualmouse::ButtonPress $btn $win $rootx $rooty $x $y $state $event $time
        set ::virtualmouse::AfterInfoID 0"]
    return
    }

  #-----------------------------------------------------------------------------
  proc ::virtualmouse::ButtonPressTimeout {win rootx rooty x y state event time} {
  #-----------------------------------------------------------------------------
    if {$::virtualmouse::MouseButton == 0} return
    ButtonPress $::virtualmouse::MouseButton $win $rootx $rooty $x $y $state $event $time
    }

  #-----------------------------------------------------------------------------
  proc ::virtualmouse::ButtonPressChoose {btn0 btn1 win rootx rooty x y state event time} {
  #-----------------------------------------------------------------------------
    if {!$::virtualmouse::MouseLocked} {
      if {$::virtualmouse::MouseButton == $btn1} {
        set ::virtualmouse::MouseButton $btn0
      } }
    ButtonPressTimeout $win $rootx $rooty $x $y $state $event $time
    }

  #-----------------------------------------------------------------------------
  proc ::virtualmouse::Motion {win rootx rooty x y state event time} {
  #-----------------------------------------------------------------------------
    if {$::virtualmouse::MouseButton == 0} return

    event generate $win [lindex {err
      <<MouseMotionLeft>> <<MouseMotionMiddle>> <<MouseMotionRight>>
      } $::virtualmouse::MouseButton] \
      -rootx $rootx -rooty $rooty -x $x -y $y -state $state -sendevent $event -time $time

    return
    }

  #-----------------------------------------------------------------------------
  proc ::virtualmouse::ButtonRelease {win rootx rooty x y state event time} {
  #-----------------------------------------------------------------------------
    if {$::virtualmouse::MouseButton == 0} return

    event generate $win [lindex {err
      <<MouseReleaseLeft>> <<MouseReleaseMiddle>> <<MouseReleaseRight>>
      } $::virtualmouse::MouseButton] \
      -rootx $rootx -rooty $rooty -x $x -y $y -state $state -sendevent $event -time $time

    set ::virtualmouse::MouseButton 0
    set ::virtualmouse::MouseLocked false

    return
    }

  ::virtualmouse::setVirtualMouseEvents

  # end virtualmouse.tcl

The pkgIndex.tcl file:

  package ifneeded virtualmouse 1.0 [list source [file join $dir virtualmouse.tcl]]

A simple test program

  console show

  source virtualmouse.tcl

  proc mac  {{e abstract}} {::virtualmouse::setVirtualMouseEvents $e macintosh}
  proc unix {{e abstract}} {::virtualmouse::setVirtualMouseEvents $e unix}
  proc win  {{e abstract}} {::virtualmouse::setVirtualMouseEvents $e windows}

  bind . <<MousePressLeft>> {puts {press left}}
  bind . <<MousePressMiddle>> {puts {press middle}}
  bind . <<MousePressRight>> {puts {press right}}
  bind . <<MouseMotionLeft>> {puts -nonewline l}
  bind . <<MouseMotionMiddle>> {puts -nonewline m}
  bind . <<MouseMotionRight>> {puts -nonewline r}
  bind . <<MouseReleaseLeft>> {puts {release left}}
  bind . <<MouseReleaseMiddle>> {puts {release middle}}
  bind . <<MouseReleaseRight>> {puts {release right}}

  puts {Type one of}
  puts {  unix   win    mac}
  puts {optionally followed by 'direct' or 'abstract'.}
  puts {Then play with the mouse in the window to see the events working.}

And finally, the HTML documentation. It isn't much, but due to size I have zipped it then MIME-encodeded using the usual base64 algorithm [L1 ]. Decode it with your favorite base64 decoder (or use one online [L2 ]), then unzip it. (Sorry for this. Until I get back some online storage it will have to do.)

  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

Duoas