New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Help Enabling virtual scroll wheel on Sierra. #814

Open
jeffmikels opened this Issue Feb 10, 2017 · 26 comments

Comments

Projects
None yet
@jeffmikels

jeffmikels commented Feb 10, 2017

I have a Logitech Marble Mouse, and I love it too much to switch to anything else, but having a scroll wheel is incredibly useful on a mac. Currently, I'm on El Capitan because Karabiner has a virtual scroll wheel feature where I can hold down CTL + CMD and then my trackball turns into a scroll wheel. It's the only thing preventing me from upgrading to Sierra.

I am a capable programmer, so I'd like to help development by enabling this one feature on Sierra, but I'd need some guidance. Can anyone help me enable virtual scroll wheel on Sierra?

@dunkarooftop

This comment has been minimized.

dunkarooftop commented Feb 12, 2017

I believe "Hammerspoon" can do it, just google "hammerspoon scroll wheel". Many people switch to hammerspoon after Sierra due to Karabiner is not supported in Sierra.

@jeffmikels

This comment has been minimized.

jeffmikels commented Feb 12, 2017

Thanks!

@jeffmikels

This comment has been minimized.

jeffmikels commented Feb 21, 2017

Following up on this:
I have been able to successfully reproduce the Karabiner Virtual Scroll using Hammerspoon. For those who are interested, here is my hammerspoon code:

-- HANDLE SCROLLING
local oldmousepos = {}
local scrollmult = -4	-- negative multiplier makes mouse work like traditional scrollwheel

mousetap = hs.eventtap.new({5}, function(e)
	oldmousepos = hs.mouse.getAbsolutePosition()
	local mods = hs.eventtap.checkKeyboardModifiers()
	if mods['ctrl'] and mods['cmd'] then
		-- print ("will scroll")
		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 * scrollmult, dy * scrollmult},{},'pixel')
		scroll:post()
		
		-- put the mouse back
		hs.mouse.setAbsolutePosition(oldmousepos)
		
		-- return true, {scroll}
		return true
	else
		return false, {}
	end
	-- print ("Mouse moved!")
	-- print (dx)
	-- print (dy)
end)
mousetap:start()
@weitzj

This comment has been minimized.

weitzj commented Mar 29, 2017

If anyone is interested:

I have adopted the above answer, which allows me to scroll vertically/horizontally using my Logitech M570 (Trackball), while holding a mouse button (instead of ctrl + cmd)

-- HANDLE SCROLLING
local oldmousepos = {}
-- positive multiplier (== natural scrolling) makes mouse work like traditional scrollwheel
local scrollmult = 4 

-- The were all events logged, when using `{"all"}`
mousetap = hs.eventtap.new({0,3,5,14,25,26,27}, function(e)
	oldmousepos = hs.mouse.getAbsolutePosition()
	local mods = hs.eventtap.checkKeyboardModifiers()
    local pressedMouseButton = e:getProperty(hs.eventtap.event.properties['mouseEventButtonNumber'])

    -- If OSX button 4 is pressed, allow scrolling
    local shouldScroll = 3 == pressedMouseButton
    if shouldScroll then
		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 * scrollmult, dy * scrollmult},{},'pixel')
		scroll:post()
		
		-- put the mouse back
		hs.mouse.setAbsolutePosition(oldmousepos)
		
		return true, {scroll}
	else
		return false, {}
	end
	-- print ("Mouse moved!")
	-- print (dx)
	-- print (dy)
end)
mousetap:start()
@vclav

This comment has been minimized.

vclav commented Oct 18, 2017

I had the same problem, but I prefer to use right mouse button to init scrolling, so I used your code and modified it to perform scroll during right mouse drag. There was some problems with deferring menuOpen on rightMouseDown, but I ended with this script, which works great.

Note: this will make right mouse dragging impossible, but who needs it anyway?

-- HANDLE SCROLLING

local deferred = false

overrideRightMouseDown = hs.eventtap.new({ hs.eventtap.event.types.rightMouseDown }, function(e)
    --print("down"))
    deferred = true
    return true
end)

overrideRightMouseUp = hs.eventtap.new({ hs.eventtap.event.types.rightMouseUp }, function(e)
    -- print("up"))
    if (deferred) then
        overrideRightMouseDown:stop()
        overrideRightMouseUp:stop()
        hs.eventtap.rightClick(e:location())
        overrideRightMouseDown:start()
        overrideRightMouseUp:start()
        return true
    end

    return false
end)


local oldmousepos = {}
local scrollmult = -4	-- negative multiplier makes mouse work like traditional scrollwheel
dragRightToScroll = hs.eventtap.new({ hs.eventtap.event.types.rightMouseDragged }, function(e)
    -- print("scroll");

    deferred = false

    oldmousepos = hs.mouse.getAbsolutePosition()    

    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 * scrollmult, dy * scrollmult},{},'pixel')
    
    -- put the mouse back
    hs.mouse.setAbsolutePosition(oldmousepos)

    return true, {scroll}
end)

overrideRightMouseDown:start()
overrideRightMouseUp:start()
dragRightToScroll:start()
@3rd3

This comment has been minimized.

3rd3 commented Jan 2, 2018

Can this be restricted to one particular device?

@vclav

This comment has been minimized.

vclav commented Jan 2, 2018

@3rd3 you can enable/disable Hammerspoon functions when device is un-/plugged from computer with hs.usb.watcher or as a result of launch/exit of some application with hs.application.watcher, but I could not find a way, how to limit functionality only to certain device, not the hs.eventtap nor the hs.eventtap.event seem to provide a information about device which triggered the mouse event.

@randydod-teamunify

This comment has been minimized.

randydod-teamunify commented Jan 11, 2018

Yet another modification for using a Logitech Trackman Marble Trackball. weitzj's code above works as-is if you use the small left button to scroll. I use the trackball with my left hand, so I like using the right small button with my thumb to enable scrolling. I also like rolling down to scroll down, so I use the negative multiplier. For convenience, here's the code.

`

-- Enable virtual mouse scroll wheel with trackball while pressing small mouse button
local oldmousepos = {}
-- positive multiplier = natural scrolling, negative makes mouse work like traditional scroll wheel
local scrollmult = -4 

-- There were all events logged, when using `{"all"}`
mousetap = hs.eventtap.new({0,3,5,14,25,26,27}, function(e)
	oldmousepos = hs.mouse.getAbsolutePosition()
	local mods = hs.eventtap.checkKeyboardModifiers()
	local pressedMouseButton = e:getProperty(hs.eventtap.event.properties['mouseEventButtonNumber'])

	-- If small mouse button is pressed, allow scrolling with trackball
	-- 3 = left small button, 4 = right small button
	local shouldScroll = 4 == pressedMouseButton
	if shouldScroll then
		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 * scrollmult, dy * scrollmult},{},'pixel')
		scroll:post()
	
		-- put the mouse back
		hs.mouse.setAbsolutePosition(oldmousepos)
	
		return true, {scroll}
	else
		return false, {}
	end
end)
mousetap:start()

`

@greazyfingaz

This comment has been minimized.

greazyfingaz commented Jan 31, 2018

Hey randydod, any way you could modify the above code for me for karabiner elements? I want to hold the left shift modifier and have my kensington expert trackball scroll; up, down, left, right etc. Rolling down scrolls down. Also, how do I even load this code?

@greazyfingaz

This comment has been minimized.

greazyfingaz commented Jan 31, 2018

Needless to say I have to experience with karabiner

@randydod-teamunify

This comment has been minimized.

randydod-teamunify commented Jan 31, 2018

greazyfingaz, the last time I checked (Jan 11, 2018), Karabiner Elements cannot do this. My code is for Hammerspoon. Use jeffmikels' code above (his 2/21/17 post) and replace his "if mods['ctrl'] and mods['cmd'] then" line with this line instead:
if mods['shift'] then

As far as I can tell, Hammerspoon does not have the ability to distinguish between the left and right shift keys, so the code will make scrolling work with either shift key.

http://www.hammerspoon.org/go can help you get started using it.

@greazyfingaz

This comment has been minimized.

greazyfingaz commented Jan 31, 2018

@greazyfingaz

This comment has been minimized.

greazyfingaz commented Feb 1, 2018

@greazyfingaz

This comment has been minimized.

greazyfingaz commented Feb 1, 2018

@randydod-teamunify

This comment has been minimized.

randydod-teamunify commented Feb 6, 2018

I encourage you to dig through Hammerspoon's documentation, as that's what I would have to do to figure this out. In other words, I don't know how. A brief reading of their examples reveals that apparently it cannot discern between the left and right shift keys or other dual keys. It can interface w/ Karabiner (see http://www.hammerspoon.org/go/#karabinerurl), which does not work in Sierra, but it does not mention Karabiner Elements.

Looking through their docs, http://www.hammerspoon.org/docs/, these functions look the most promising. hs.eventtap, hs.eventtap.event, hs.expose, hs.keycodes

You might post your question by itself to see if someone else here would know how to do this.

@jeffmikels

This comment has been minimized.

jeffmikels commented Feb 6, 2018

@greazyfingaz Yes. Look through the code above. You want to add an "if" statement that checks if specific modifier keys are down, and if they are down, sets "shouldScroll" to false.

Are you familiar with how to write code?

@Marviel

This comment has been minimized.

Marviel commented Feb 10, 2018

@randydod-teamunify thanks for the hammerspoon modification!

For future users, I found out, through experimentation, that the left small mouse button is actually index "2" on my trackman marble.

@JKillian

This comment has been minimized.

JKillian commented Apr 3, 2018

Thanks for the scripts everyone, worked well for me! To get @randydod-teamunify's script working, I had to go into the logitech settings and set the button number for the small buttons manually:

image

@mansoff

This comment has been minimized.

mansoff commented Jul 10, 2018

getting error

2018-07-10 10:16:44: 10:16:44 ERROR: LuaSkin: hs.shutdownCallback: attempt to call a nil value stack traceback:

with this code #814 (comment)

hammerspoon - Version 0.9.66 (0.9.66)

@Burdu

This comment has been minimized.

Burdu commented Aug 23, 2018

Leaving my script for scrolling with mouse wheel pressed. Mouse wheel button in my case is 2. The button is configurable on the second line of code.
Natural scrolling on both axes:

-- HANDLE SCROLLING WITH MOUSE BUTTON PRESSED
local scrollMouseButton = 2
local deferred = false

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

overrideOtherMouseUp = hs.eventtap.new({ hs.eventtap.event.types.otherMouseUp }, function(e)
     -- print("up")
    local pressedMouseButton = e:getProperty(hs.eventtap.event.properties['mouseEventButtonNumber'])
    if scrollMouseButton == 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)

local oldmousepos = {}
local scrollmult = -4	-- negative multiplier makes mouse work like traditional scrollwheel

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 scrollMouseButton == pressedMouseButton 
        then 
            -- print("scroll");
            deferred = false
            oldmousepos = hs.mouse.getAbsolutePosition()    
            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 * scrollmult, dy * scrollmult},{},'pixel')
            -- put the mouse back
            hs.mouse.setAbsolutePosition(oldmousepos)
            return true, {scroll}
        else 
            return false, {}
        end 
end)

overrideOtherMouseDown:start()
overrideOtherMouseUp:start()
dragOtherToScroll:start()
@CaioCosta

This comment has been minimized.

CaioCosta commented Oct 31, 2018

@Burdu Thanks for that script! Your version won't completely disable the chosen mouse button, so I can still use my extra buttons for back and forward actions.

Just a note: on line number 23 (hs.eventtap.otherClick(e:location(), pressedMouseButton)) I had to move the second parameter to the third position (by adding 0 as the second parameter) because the second parameter is the click delay and not the mouse button number. Other than that minor issue, your code is definitely the best on this thread.

@Burdu

This comment has been minimized.

Burdu commented Oct 31, 2018

Thanks, I updated the script to fix that

@Madd0g

This comment has been minimized.

Madd0g commented Nov 8, 2018

I see a lot of hammerspoon masters here. Does anyone know how to replicate the firefox auto-scroll (mousewheel click) feature with HS? I want to press a button and have the screen scroll down until I press it again, without holding anything, is it possible?

@jeffmikels

This comment has been minimized.

jeffmikels commented Nov 8, 2018

@Madd0g

This comment has been minimized.

Madd0g commented Nov 8, 2018

@jeffmikels - thanks, I'll try, I was a bit worried about jerkiness from using a timer, but I guess I'll tweak the timer/scroll values until it looks ok. Thanks

was soooo easy:

local timer = nil
hs.hotkey.bind({"cmd", "alt", "ctrl"}, "s", function()
    if timer == nil or not timer then
        timer = hs.timer.doEvery(0.07, performScroll) 
    else
        timer.stop(timer)
        timer = nil
    end
end)

function performScroll()
	hs.eventtap.event.newScrollEvent({ 0, -2 }, {}, 'pixel'):post()
end

keywords: google chrome autoscroll page hammerspoon

@ianjennings

This comment has been minimized.

ianjennings commented Nov 15, 2018

For anyone who finds this thread via Google and is just looking for some damn mouse drivers:

  1. Click "open config" in hammerspoon menu
  2. Paste one of the above scripts into init.lua and save
  3. In hammerspoon, click "show console"
  4. Click "reload config"
  5. If you get hs.eventtap:start() Unable to create eventtap. Is Accessibility enabled? follow the accessibility directions in the faq http://www.hammerspoon.org/faq/

Edit local scrollmult = -4 to change direction and speed.

Thanks for sharing your code everybody!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment