Page 1 of 5 12345 LastLast
Results 1 to 10 of 44

Thread: Linux Space Navigator Development Thread

  1. #1
    Raw Wave shotgunefx's Avatar
    Join Date
    Apr 2005
    Location
    Boston, MA
    Posts
    1,800

    Linux Space Navigator Development Thread

    So I'm thinking of avoiding using the Space Navigator through X all together. I'm thinking of running a daemon in the background that will service applications aware of it, sort of like gpsd clients or directly through the app.

    update
    Workaround bug in Linux::Input that ignored EV_SYN (sync events) and added tons of functionality. Still lot's to do.

    update 2007-07-30
    Fixed a bug with button handling, removed kludge for Linux::Input as it's been patched. Added led option, though not very portable.


    update 2007-08-04
    Added an alarm on polling, that will if it times out, reconnect. Otherwise it will hang on resume
    update 2007-08-05
    Rolled back, seems like the hanging is some transient resume issue, it's no longer happening, and when it doesn't the "fix" causes problems *sigh*

    Eventually this will run in the background and send the appropriate events to the currently focused window based on user supplied configuration info.

    Code:
    #!/usr/bin/perl
    # $version 0.3
    
    =head2 Description
    usage: perl ptest.pl -d DEVICENAME [--primary_axis]  [--led]
    
    Needs to be run sudo or as root. 
    -d Device name should be in the form of /dev/input/event[num] or a symlink to the event
    
    --primary_axis will make it act as there is one axis, whichever is dominant
    
    --led will light the leds 
    
    Prerequisites from CPAN:
    Linux::Input
    Linux::Input::Info
    
    Originally based on spacenavi.c by Simon Budig.
    http://www.home.unix-ag.org/simon/files/spacenavi.c
    
    
    This is a skeleton program to interact with a 3DConnexion Space Navigator through evdev
    It supports calibration, scaling of inputs, action debouncing and more. 
    
    =head1 Modes of Operation
    Normal Operation:
    In normal operation, it will simply report back device input based on configuration. 
    If the movement is < mindeflect, the axis will be treated as having no movement.
    Movement is not acknowledged until the Axis has been acted on longer than debounce time.
    
    Primary Axis:
    If "Primary Axis" is enabled, the pointer will act as 8 buttons. Only the axis most acted upon
    will be acknowledged. If the Axis determined to be primary differs from the previous, a release event
    will be generated for the previous primary axis (as if you had let it go). 
    
    LEDs:
    The sub that handles the LEDs (set_spnvled) has the input event format hardcoded. Not portable.
    
    =head1 TODO
    Add a lot more command line options for individual axes and options
    Add support for reading in calibration data.
    Better docs, read the code for now :)
    =cut
    
    use strict;
    use warnings;
    no warnings qw/uninitialized/;
    
    use Time::HiRes qw/time/;    # Need higher res time for debouncing
    use Linux::Input::Info;
    use Linux::Input v1.03;
    use Getopt::Long;
    use Fcntl;
    
    our ( $USE_PRIMARY_AXIS, $DEV, $LED_STATE );
    
    #$SIG{ALRM} = sub { die "timeout" };
    
    # Set defaults
    $USE_PRIMARY_AXIS = 0;       # If true, we pretend that each Axis is separate,
                                 # and only one can be active at a given time.
    
    GetOptions(
        'd=s'         => \$DEV,
        'use_primary' => \$USE_PRIMARY_AXIS,
        'led'         => \$LED_STATE,
    
    );
    
    # Should cajole Linux::Input::Info to export these, constants from input.h
    use constant {
        EV_SYN   => 0x00,
        EV_KEY   => 0x01,
        EV_REL   => 0x02,
        EV_LED   => 0x11,
        REL_X    => 0x00,
        REL_RZ   => 0x05,
        LED_MISC => 0x08,
        BTN_0    => 0x100,
        BTN_1    => 0x101,
    };
    
    use constant {    # Axis/button names for array indexes,
        SPN_LR         => 0,    # Left/Right
          SPN_FB       => 1,    # Fwd/Back
          SPN_UD       => 2,    # Up/Down
          SPN_TILT_FB  => 3,    # Tilt Fwd/Back
          SPN_TILT_LR  => 4,    # Tilt Left/Right (evdev reports this + to -
                                # instead of - to + as we would expect, we flip it
          SPN_ROT_LR   => 5,    # Rotation Left/Right
          SPN_BUTTON_1 => 6,
          SPN_BUTTON_2 => 7,
    
    };
    
    use constant {
        SPN_SCALE => 1000,   # We scale the inputs so they are all ~ +/- SPN_SCALE/2
        SPN_MIN_DEFLECTION =>
          250,    # Default minimum delflection, we ignore movement less than this
        SPN_DEBOUNCE => 0.02,    # Fractional seconds to debounce actions
        SPN_AXIS_MAX => 5,       # How many axes does the device have?
    };
    
    # Where we store device Axis info
    # Eventually, a hash of array refs, keyed by application, all data from config files
    my @ax_info = (
    
        #SPN_LR()
        {
            label       => 'SPN_LR',     # Name for printing for convience
            val         => 0,            # Where we keep the value of the axis
            cmin        => -342,         # Calibration minimum
            cmax        => 382,          # Calibration maximum
            mindeflect  => 200,          # Movement < this ignored
            sensitivity => SPN_SCALE,    # 0 - SPN_SCALE, higher is more sensitive.
                 # let's us favor say, tilt front/back over movement front/back
            active     => 0,   # Internally set, flag if axis is active, leave it ;)
            press_time => 0,   # time we went active, leave it ;)
            event_sent => 0,   # Internally set, if an event was generated yet evebt
            ignore     => 0,   # If true, axis is ignored
        },
    
        #SPN_FB()
        {
            label       => 'SPN_FB',
            val         => 0,
            cmin        => -412,
            cmax        => 371,
            mindeflect  => 200,
            sensitivity => SPN_SCALE,
            active      => 0,
            press_time  => 0,
            event_sent  => 0,
            ignore      => 0,
        },
    
        #SPN_UD()
        {
            label       => 'SPN_UD',
            val         => 0,
            cmin        => -448,
            cmax        => 397,
            mindeflect  => 200,
            sensitivity => SPN_SCALE,
            active      => 0,
            press_time  => 0,
            event_sent  => 0,
            ignore      => 0,
        },
    
        #SPN_TILT_FB()
        {
            label       => 'SPN_TILT_FB',
            val         => 0,
            cmin        => -374,
            cmax        => 393,
            mindeflect  => 200,
            sensitivity => SPN_SCALE,
            active      => 0,
            press_time  => 0,
            event_sent  => 0,
            ignore      => 0,
        },
    
        #SPN_TILT_LR()
        {
            label       => 'SPN_TILT_LR',
            val         => 0,
            cmin        => 369,
            cmax        => -374,
            mindeflect  => 200,
            sensitivity => SPN_SCALE,
            active      => 0,
            press_time  => 0,
            event_sent  => 0,
            ignore      => 0,
        },
    
        #SPN_ROT_LR()
        {
            label       => 'SPN_ROT_LR',
            val         => 0,
            cmin        => -439,
            cmax        => 381,
            mindeflect  => 200,
            sensitivity => SPN_SCALE,
            active      => 0,
            press_time  => 0,
            event_sent  => 0,
            ignore      => 0,
        },
        {
            label => 'SPN_BUTTON_1',
            val   => 0,
        },
        {
            label => 'SPN_BUTTON_2',
            val   => 0,
        },
    
    );
    
    #############################################
    #  Generate scaling data from calibration   #
    #############################################
    for my $c ( 0 .. SPN_AXIS_MAX ) {
        my $mul =
          $ax_info[$c]->{sensitivity} /
          ( $ax_info[$c]->{cmin} - $ax_info[$c]->{cmax} );
    
        # Range check here
        if ( $ax_info[$c]{mindeflect} >= SPN_SCALE / 2 ) {
            warn "Axis:$c minimum deflection too high!, ignoring\n";
            $ax_info[$c]{mindeflect} = SPN_MIN_DEFLECTION;
        }
    
        $ax_info[$c]{mindeflect} = SPN_MIN_DEFLECTION
          unless exists $ax_info[$c]{mindeflect};
    
        printf( "% 4d : % 4d\n",
            $ax_info[$c]{cmin} * $mul,
            $ax_info[$c]{cmax} * $mul );
        $ax_info[$c]{scale} = $mul;
    }
    
    die "No device specified!" unless $DEV;    # Whoops, no input device
    
    my $en = $DEV;
    
    if ( -l $en ) {
    
        # If symlink, get the actual name.  I use udev to symlink it
        $en = readlink($DEV) or die $!;
    }
    
    die "$en doesn't look like a valid input!"
      unless ( $en =~ s#^.*event(\d+)$#$1# );    # Should look like eventN
    
    # Get dev info for debugging
    my $i = Linux::Input::Info->new($en);
    printf "$DEV\n";
    printf "\tbustype  : %s\n",   $i->bustype;
    printf "\tvendor   : 0x%x\n", $i->vendor;
    printf "\tproduct  : 0x%x\n", $i->product;
    printf "\tversion  : %d\n",   $i->version;
    printf "\tname     : %s\n",   $i->name;
    printf "\tuniq     : %s\n",   $i->uniq;
    printf "\tphys     : %s\n",   $i->phys;
    printf "\tbits ev  :";
    printf " %s", $i->ev_name($_) for $i->bits;
    printf "\n";
    
    set_spnvled( $DEV, $LED_STATE );
    
    # Get a handle for the input device
    my $spnv = Linux::Input->new($DEV);    # Open the input device
    
    print "Press Ctrl-C to quit\n";
    
    while (1) {                            # Event loop
        my ( $max_axis, @min, @max );      # Used to figure which axis is primary
        my @generated_events;
    
        while ( my @events = $spnv->poll(0.001) ) {    # Check for an event
    
            foreach my $e (@events) {
    
                # Process 'em
    
                my $max_val = 0;
    
                if ( $e->{type} == EV_REL ) {          # Relative moment event
    
                    if ( $e->{code} <= REL_RZ ) {
    
                        # Scale and save the data unless we are set to ignore
                        my $axis = $e->{code} - REL_X;
                        $ax_info[$axis]{val} =
                          $ax_info[$axis]{ignore}
                          ? 0
                          : $e->{value} * $ax_info[$axis]{scale};
    
                    }
                    else {
                        warn "Unknown code: $e->{code}!\n";
                    }
    
                }
                elsif ( $e->{type} == EV_KEY ) {    # Button press or release
                    if ( $e->{code} >= BTN_0 && $e->{code} <= BTN_1 ) {
                        my $button =
                          $e->{code} - BTN_0 ? SPN_BUTTON_2: SPN_BUTTON_1;
                        push @generated_events,
                          [
                            $button, $e->{'value'} ? 1 : -1,
                            $ax_info[$button]{val}
                          ];
                        print $ax_info[ $button]{label},
                          $e->{'value'} ? " pressed!\n" : " released!\n";
                    }
                    else {
                        warn "Unknown button: $e->{code}!\n";
                    }
                }
                elsif ( $e->{type} == EV_SYN ) {
    
                  # EV_SYN = sync, the kernel has passed as all the axis/button info
                    for my $m ( 0 .. SPN_AXIS_MAX ) {
                        if ( abs( $ax_info[$m]{val} ) < $ax_info[$m]{mindeflect} ) {
                            $ax_info[$m]{val} =
                              0;    # Set value to 0 if < Axis mindeflect
                        }
    
                        if ( $ax_info[$m]{val} ) {
    
                            # We have an event we care about
                            if ( $ax_info[$m]{press_time}
                                and !$ax_info[$m]{active} )
                            {       # Debounce
                                if (
                                    time() - $ax_info[$m]{press_time} >
                                    SPN_DEBOUNCE )
                                {
    
                                   # It's been active long enough, generate an event
                                    $ax_info[$m]{active} =
                                      $ax_info[$m]{val} > 0 ? -1 : 1;
                                    push @generated_events,
                                      [ $m, 1, $ax_info[$m]{val} ];
                                }
                            }
                            else {    # Wasn't active, is now, set time
                                $ax_info[$m]{press_time} = time();   # Change to app
                            }
    
                        }
                        else {
    
                            # Clean up as needed
                            if ( $ax_info[$m]{active} ) {
    
                                # Generate release
                                $ax_info[$m]{active} = 0;
                                push @generated_events,
                                  [ $m, -1, $ax_info[$m]{val} ]
                                  if $ax_info[$m]{event_sent};
                            }
                            $ax_info[$m]{press_time} = undef;    # Reset press time
                        }
    
                        if ( $max_val < abs( $ax_info[$m]{val} ) ) {
    
                            # Save the primary axis
                            $max_axis = $m;
                            $max_val  = abs $ax_info[$m]{val};
                        }
                    }
    
                }
    
            }
    
        }
    
        # Now we have that events that need to be processed
        if (@generated_events) {
    
            # If we want only a primary axis, we need to make sure to send
            # a release event for any Axis previously considered active
            if ($USE_PRIMARY_AXIS) {
                @generated_events =
                  grep {
                         $_->[0] == $max_axis
                      or $_->[1] < 0    # Allow  primary and release events
                  } @generated_events;    # Filter pending events
    
                # Fake release events for any other axis still active
                for ( 0 .. SPN_AXIS_MAX ) {
                    next if $_ == $max_axis;    # Skip the primary
                         # Generate a release even if a "press" has been sent
                    push @generated_events, [ $_, -1, $ax_info[$_]{value} ]
                      if $ax_info[$_]{active}
                      and $ax_info[$_]{event_sent};
    
                }
    
            }
    
            while (@generated_events) {
                my $e = shift @generated_events;
    
                # Track that the event has been sent so we can surpress
                # false releases due to USE_PRIMARY_AXIS  as needed
                if ( $e->[1] == 1 ) {
                    $ax_info[ $e->[0] ]{event_sent} = 1;
    
                    #! Here is where you would actually do something on press
                    print $ax_info[ $e->[0] ]{label}, " pressed!\n";
    
                }
                else {
                    $ax_info[ $e->[0] ]{event_sent} = 0;
    
                    #! Here is where you would actually do something on press
                    print $ax_info[ $e->[0] ]{label}, " released!\n";
                }
    
            }
    
        }
    
    }
    
    set_spnvled( $en, 0 );
    
    sub set_spnvled {
        my ( $dev, $state ) = @_;
        sysopen( DEV, $dev, O_WRONLY )
          or die "Couldn't open $dev for writing: $!\n";
    
        # Highly unportable, need to match  struct input_event event in input.h
        my $ev = pack( 'L!L!S!S!i!', time(), '0', EV_LED, LED_MISC, $state );
        my $written = syswrite( DEV, $ev );
        die "Write failed:[$written] != [" . length($ev) . "]  $!"
          unless $written == length $ev;
    }
    Comments welcomed!

  2. #2
    Raw Wave shotgunefx's Avatar
    Join Date
    Apr 2005
    Location
    Boston, MA
    Posts
    1,800
    Just found X11::WMCtrl and wmctrl. It's a snap to control the window manager, though sending events is proving to be a little more difficult.

  3. #3
    Raw Wave shotgunefx's Avatar
    Join Date
    Apr 2005
    Location
    Boston, MA
    Posts
    1,800
    Well, found a bug (and a workaround) in X11::WMCtrl but still no good for sending Clicks, Keypress.

    Enter X11::GUITest

    A bit of a PITA to install as it had trouble finding libXtst, but now it's working and I can send keyboard data and mouse clicks to apps. Even search through child windows (but it's all write-only, can't read field names, just set 'em, etc).

    Between the two, that should cover most of my input needs.

  4. #4
    Maximum Bitrate
    Join Date
    Aug 2004
    Location
    at home
    Posts
    591
    Maybe this can release you from pita

    http://hoopajoo.net/projects/xautomation.html

  5. #5
    Raw Wave shotgunefx's Avatar
    Join Date
    Apr 2005
    Location
    Boston, MA
    Posts
    1,800
    Quote Originally Posted by kraft View Post
    Maybe this can release you from pita

    http://hoopajoo.net/projects/xautomation.html
    Thanks for the link. I found xdotool last night which also seems promising.

  6. #6
    Raw Wave shotgunefx's Avatar
    Join Date
    Apr 2005
    Location
    Boston, MA
    Posts
    1,800
    Quote Originally Posted by kraft View Post
    Maybe this can release you from pita

    http://hoopajoo.net/projects/xautomation.html
    Got to thank you again, for the pointer. xdotool is nice, and has some things xte doesn't, but has problems sending text and mice events. Always sends them to the current app instead of the one I moved focus to.

    xte, won't change focus or handle windows but does everything else great, so putting them together does just about everything I want.

    One thing that has bothered me for awhile (and maybe there is a way to do it I can't find), but I can't figure out how to launch an app in KDE with no borders. A few minutes of bash scripting, and I have a util to do so. Now I should be able to embed GE and XawTV pretty easily in my front end in an more robust way.

  7. #7
    Maximum Bitrate
    Join Date
    Aug 2004
    Location
    at home
    Posts
    591
    mmm if you allow suggestion, i could say that tvtime is working nicely too
    DCOP should do the job, if you are a bit curious, you will find that right click, if not found yet will let you chose to not show borders at all, using DCOP can contribute with your apps

  8. #8
    Raw Wave shotgunefx's Avatar
    Join Date
    Apr 2005
    Location
    Boston, MA
    Posts
    1,800
    Quote Originally Posted by kraft View Post
    mmm if you allow suggestion, i could say that tvtime is working nicely too
    DCOP should do the job, if you are a bit curious, you will find that right click, if not found yet will let you chose to not show borders at all, using DCOP can contribute with your apps
    I've used both Xaw and Tvtime, both work well. I'm not married to either as I'm pretty much just using it to view my camera.

    I got that you can right click to get rid of borders, but can you do it when launching commands? That's what my script does, basically right click it and navigate to the border option. Otherwise, I'd have to do it manual each time I restarted.

    I looked at DCOP (and googled like hell) but couldn't figure out how to do it.

  9. #9
    Maximum Bitrate
    Join Date
    Aug 2004
    Location
    at home
    Posts
    591
    It must work even when launching commands, i used it but due to aRTSi found annoying and some other details i left kde and switch to a lightweight windowmaker.
    For DCOP, didn't found documentation either but with tweaking and looking some apps sources with the features i wanted, you can find, it's only a question patience

  10. #10
    Raw Wave shotgunefx's Avatar
    Join Date
    Apr 2005
    Location
    Boston, MA
    Posts
    1,800
    Bump, lot's of stuff added, first post updated.

Page 1 of 5 12345 LastLast

Similar Threads

  1. Replies: 44
    Last Post: 02-15-2008, 12:53 AM
  2. Help in Linux
    By Jagaer in forum Linux
    Replies: 3
    Last Post: 04-21-2006, 10:50 PM
  3. Possible EPIA distro for Linux, Can we make one?
    By adamis in forum Software & Software Development
    Replies: 15
    Last Post: 02-23-2005, 05:50 PM
  4. PS2 Linux ?'s
    By jws95hardbody in forum Newbie
    Replies: 5
    Last Post: 01-11-2004, 10:18 PM
  5. Help with linux boot...
    By cyrocr in forum General MP3Car Discussion
    Replies: 1
    Last Post: 11-12-2003, 08:08 AM

Bookmarks

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •