Hammerspoon

The following is a script to enable drag scroll for a trackball:

-- HANDLE SCROLLING WITH MOUSE BUTTON PRESSED
-- Use button right click
-- Use button 3 for kensington back button
-- Use button 6 for Elecom Deft Pro
local config = {
    scrollButtons = { 3, 6 },
    rightButtonScrollEnabled = true,
    scrollSpeedMultiplier = -4, -- Negative for traditional scrolling behavior

}

local oldmousepos = {}
local deferred = false
local rightHeld = false
local leftHeld = false
local sentForwardClick = false
local sentMiddleClick = false
local sentBackClick = false
local shiftHeld = false

overrideOtherMouseDown = hs.eventtap.new({ hs.eventtap.event.types.otherMouseDown }, function(e)
    --print("down")
    local pressedMouseButton = e:getProperty(hs.eventtap.event.properties['mouseEventButtonNumber'])
    -- print ("pressed mouse down:" .. pressedMouseButton)
    if hs.fnutils.contains(config.scrollButtons, pressedMouseButton) then 
        -- print ("pressed mouse down:" .. pressedMouseButton .. " deferred")
        deferred = true
        return true
    end

    return false
end)

overrideOtherMouseUp = hs.eventtap.new({ hs.eventtap.event.types.otherMouseUp }, function(e)
    --print("up")
    local pressedMouseButton = e:getProperty(hs.eventtap.event.properties['mouseEventButtonNumber'])
    -- print ("pressed mouse up:" .. pressedMouseButton)
    if hs.fnutils.contains(config.scrollButtons, pressedMouseButton)
        then 
            if (deferred) then
                overrideOtherMouseDown:stop()
                overrideOtherMouseUp:stop()
                hs.eventtap.otherClick(e:location(), 0, pressedMouseButton)
                overrideOtherMouseDown:start()
                overrideOtherMouseUp:start()
                return true
            end
            return false
        end
        return false
end)

overrideRightMouseDown = hs.eventtap.new({ hs.eventtap.event.types.rightMouseDown }, function(e)
    rightHeld = true
    -- print(modifiers)
    if shiftHeld then
        print "shift is held down"
        print("sending button 4 (forward)")
        hs.eventtap.otherClick(e:location(), 0, 4)
        sentForwardClick = true
        deferred = false
        return true
    end
    -- print("Result:", rightHeld, leftHeld)
    if (rightHeld and leftHeld) then
        print("Sending middle click")
        sentMiddleClick = true
        hs.eventtap.middleClick(e:location(), 0)
        deferred = false
        return true
    end
    if (config.rightButtonScrollEnabled) then 
        -- print("right down")
        deferred = true
        -- print("Result:", rightHeld, leftHeld)
        return true
    end
    return false
end)

overrideLeftMouseDown = hs.eventtap.new({ hs.eventtap.event.types.leftMouseDown }, function(e)
    leftHeld = true
    if shiftHeld then
        -- print "shift is held down"
        print("sending button 3 (back)")
        hs.eventtap.otherClick(e:location(), 0, 3)
        sentBackClick = true
        deferred = false
        return true
    end
    --print("Left Down Result:", rightHeld, leftHeld)
    if (rightHeld and leftHeld) then
        hs.eventtap.middleClick(e:location(), 0)
        sentMiddleClick = true
        deferred = false
        return true
    end
    return false
end)

overrideRightMouseUp = hs.eventtap.new({ hs.eventtap.event.types.rightMouseUp }, function(e)
    -- print("right up")
    rightHeld = false
    sentMiddleClick = false
    if sentForwardClick then
        -- print("doing nothing sent forward click")
        sentForwardClick = false
        deferred = false
        return true
    end
    if (config.rightButtonScrollEnabled and deferred) then
        deferred = false
        -- print("Was deferred restarting handlers")
        overrideRightMouseDown:stop()
        overrideRightMouseUp:stop()
        print("sending rightClick")
        hs.eventtap.rightClick(e:location(), 0)
        overrideRightMouseDown:start()
        overrideRightMouseUp:start()
        return true
    end
    return false
end)

overrideLeftMouseUp = hs.eventtap.new({ hs.eventtap.event.types.leftMouseUp }, function(e)
    --print("left up")
    leftHeld = false
    sentBackClick = false
    sentMiddleClick = false
    return false
end)


dragOtherToScroll = hs.eventtap.new({ hs.eventtap.event.types.otherMouseDragged }, function(e)
    local pressedMouseButton = e:getProperty(hs.eventtap.event.properties['mouseEventButtonNumber'])
    -- print ("pressed mouse " .. pressedMouseButton)
    if hs.fnutils.contains(config.scrollButtons, pressedMouseButton) then 
            -- print("scroll");
            deferred = false
            oldmousepos = hs.mouse.absolutePosition()    
            local dx = e:getProperty(hs.eventtap.event.properties['mouseEventDeltaX'])
            local dy = e:getProperty(hs.eventtap.event.properties['mouseEventDeltaY'])
            local scroll = hs.eventtap.event.newScrollEvent({-dx * config.scrollSpeedMultiplier, -dy * config.scrollSpeedMultiplier},{},'pixel')
            --put the mouse back
            hs.mouse.absolutePosition(oldmousepos)
            return true, {scroll}
        else 
            return false, {}
        end 
end)

dragRightToScroll = hs.eventtap.new({ hs.eventtap.event.types.rightMouseDragged }, function(e)
    -- print("scroll");
    deferred = false
    oldmousepos = hs.mouse.absolutePosition()    
    local dx = e:getProperty(hs.eventtap.event.properties['mouseEventDeltaX'])
    local dy = e:getProperty(hs.eventtap.event.properties['mouseEventDeltaY'])
    local scroll = hs.eventtap.event.newScrollEvent({-dx * config.scrollSpeedMultiplier, -dy * config.scrollSpeedMultiplier},{},'pixel')
    -- put the mouse back
    hs.mouse.absolutePosition(oldmousepos)
    return true, {scroll}
end)

local function shiftDownCatcher(event)
    local flags = event:getFlags()
    local rawFlags = event:getRawEventData().CGEventData.flags & 0xdffffeff
    -- print("------------------------")
    -- print("keyDump","rawFlags",rawFlags)
    -- print("keyDump","regularFlags:",hs.inspect(event:getFlags()))
    -- print("keyDump","keyCodzse",event:getKeyCode())
    -- print("keyDump","getCharacters",event:getCharacters())
    -- print("keyDump","getRawEventData",hs.inspect(event:getRawEventData()))

    -- print("keyDump","systemKey",hs.inspect(event:systemKey()))
    -- print("keyDump","getType",event:getType())

    if flags.shift and not shiftHeld then
        print("Detected shift down")
        shiftHeld = true
    end
    return false -- Passes the event on
end

local function shiftUpCatcher(event)
    local flags = event:getFlags()

    if not flags.shift and shiftHeld then
        print("Detected shift up")
        shiftHeld = false
    end
    return false -- Passes the event on
end

shiftDown = hs.eventtap.new({hs.eventtap.event.types.flagsChanged}, shiftDownCatcher)
shiftUp   = hs.eventtap.new({hs.eventtap.event.types.flagsChanged}, shiftUpCatcher)

overrideRightMouseDown:start()
overrideRightMouseUp:start()
overrideLeftMouseDown:start()
overrideLeftMouseUp:start()
overrideOtherMouseDown:start()
overrideOtherMouseUp:start()
dragOtherToScroll:start()
dragRightToScroll:start()
shiftDown:start()
shiftUp:start()