Centre Button BullSomething on LINUX

It has been a minor annoyance for as long as I can remember. When using LINUX, or more specifically X-Windows on LINUX, clicking the mouse centre button would perform a paste wherever the cursor is right now. When you are using the scroll-wheel to scroll through a document, often the centre button has a hair-trigger and will perform a click (and therefore a paste) without even realizing it – and, boom! You have random (well, arbitrary) text in the middle of your document. Fortunately, if it’s source code, the compile or execution will almost always fail… so you can fix it. But, when it’s a word processing document… ugh!

X-What????
X-Windows is the display server predominantly used on LINUX. You might ask, “Display server, what’s that?“.

OK, so a bit of clarification. LINUX is actually just the program that loads into the computer and performs the core functions, otherwise known as the kernel. On top of the LINUX kernel, bare bones LINUX has only the command line. That command line, and the other programs that run from the command line, are most generally provided (without attribution!) by the GNU Project. GNU is a recursive acronym for “GNU’s not UNIX”, a reference to the Bell Labs operating system UNIX developed in the 1970s, but which was expensive to licence in the 1990s. GNU was established to bring UNIX-like functions free to computers.

In fact, Richard Stallman, a vocal open-source advocate and thorn in the side for computer monopolists everywhere, has termed what we colloquially call LINUX as GNU/LINUX, because there’s much more there than the specific LINUX kernel.

OK, so GNU + LINUX gives a command line. Most people want a graphic interface though. The graphic interface uses the desktop metaphor, made popular by… well, not what you think – made popular by the Xerox Palo Alto Research Centre.

Anyway, enough of that. The desktop is implemented by a program called the Desktop Manager. Popular desktop managers on LINUX (oops GNU/LINUX) are Gnome, KDE, Enlightenment, IceWM, and dozens more.

But, to get their image onto the screen the Desktop Manager relies on the windowing system – which manages the screen, taking commands to draw things.

X-Windows is, by far, the most popular windowing system on UNIX, LINUX and other UNIX-like systems. There are others, check out the windowing system Wikimedia page. Donate while you are there, will you?

OK, so now that we have established (in my mind anyways), that X-Windows is a thing, and it has a place… back to the original rant.

Paste This, X-Windows!
So the centre click paste bit me while composing my CV. Yes, I’m searching for work right now, ugh. I almost didn’t catch it! Only after I PDFed my CV and was giving it my final check through, did I catch it – it would have been ugly 🙁

So I started looking into this in earnest. After seeing a lot of not-so-helpful advice (which is often the case on the Internet, where the signal-to-noise ratio is abominally low), I found one site that helped on the Ubuntu wiki.

The offending feature is apparently implemented in X-Windows, all right. The button functions can be manipulated using the program xinput. You may have to go out to your repository to get it. Every time you log in to your desktop, you pop up a command prompt:
# xinput list | grep -i 'mouse'
This gives you all input devices that are referred to as “mouse”. So, let’s assume for a moment that your mouse shows with “id=10”, say. Get the button map using:
# xinput get-button-map 10
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 10

Wow, lots of buttons, apparently. Lots of bogus buttons! Anyway, you can kill the centre mouse button by setting the second entry to zero:
# xinput set-button-map 10 1 0 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 10
You could restore the centre mouse button mapping by restoring the number:
# xinput set-button-map 10 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 10
You can perform other tricks, like swapping left and right buttons (for left handers).

The sad thing is that you often have to do this for several devices in the list. Sometimes, their ID numbers change. They get added and removed as you plug in different mice. What’s a po boy supposed to do? You have to write a script!

Scripting the Night Away
For many years, my scripting language of choice was PERL. Not that I’m an expert, or that I love it so much, but it was useful, and had access to CPAN, where you could readily get, for free, almost any library function. You want to do Fourier transforms? Easy, just load the CPAN module. Statistics? CPAN module. Downloading and manipulating web content? Yep, CPAN.

More recently, I moved over to Python. It is far more popular than Perl, and has a more formal, if less colourful, background… although I have to say, it’s almost as quirky in spots 🙂 Especially when you have to pick between Python 2 and Python 3, ugh. Oh well. Not having to deal with a lot of legacy code, I prefer to use the latest, so I mostly use Python 3. However, it seems that Python 2 is the default on my system (Ubuntu 18.04), so I can work with that too.

The best development environment that I’ve found is Spyder, which can readily be downloaded as part of the Anaconda, a Python distribution package available for LINUX and other platforms.

Anyway, so I wrote a script called KillMouseCentreButtonPaste that calls xinput to get each mouse entry, then disables the second button. It’s not beautiful Python code, but it works.

#!/usr/bin/env python2
# -*- coding: utf-8 -*-
"""
Created on Tue Mar 26 09:31:29 2019

@author: dmw
"""
#=====================================================================
#
# Get rid of troublesome paste centre mouse button click...
# (c) Dean Weiten, Winnipeg
# All rights reserved.
#
# Initial Author: Dean Weiten
#
# Create Date: 2019-03-26
# Design Name: KillMouseCentreButtonPaste.py
# Tool versions:
#
#
# Description:
# .
#
# Dependencies:
#
# Revision:
#
# Log: KillMouseCentreButtonPaste.py,v
# Revision 1.1 2019/03/27 01:48:37 dmw
# Initial revision
#
#
#
# Additional Comments:
#
# "Nothing focusses the mind so well as an execution in the morning"
#
# Reminder: to add a module using PIP, do something like this:
# pip install pywt
# OR
# pip3 install pywt
#
#=====================================================================

import sys
import getopt
import subprocess

#***************
# Constants.
#---------------
Version = 0
Issue = 1
IssueDate = "2019-03-26"

#***************
# Function to provide usage help.
#---------------
def usage():
print ("\nGet rid of troublesome paste centre mouse button click, v"+"%1d" % (Version)+"."+"%1d" % (Issue)+" "+IssueDate+" dmw")
print ("\nTypical usage: "+sys.argv[0]+" [option]* ")
print ("\n Options: ")
print (" -v More verbose")

# print ("\n Notes: 1. This program creates non-header binary RAF files for Rigol signal")
# print (" 6. .")

print ("\n RCS Info:")
print (" Header: /home/dmw/src/util/RCS/KillMouseCentreButtonPaste.py,v 1.1 2019/03/27 01:48:37 dmw Exp")

print ("\n")

#***************
# Main function. Called from the bottom of this file!
#---------------
def main():
#***************
# Get command line options. Error results in help text dump and exit.
#---------------
try:
opts, args = getopt.getopt(sys.argv[1:], "dhv", ["help"])
except getopt.GetoptError as Error:
# print help information and exit:
print ("\n")
print (Error) # will print something like "option -a not recognized"
print ("\n------------------------------")
usage()
sys.exit(2)

#***************
# Set defaults.
#---------------
Debug = False
Verbose = False
mousestring = "mouse".lower()
idstring = "id=".lower()

#***************
# Parse values from command line options. Error results in message and exit.
#---------------
for Option, Argument in opts:
if Option in ("-d"): # Debug output (intentionally undocumented in help message).
Debug = True
elif Option in ("-h", "--help"): # Usage help output.
usage()
sys.exit(0)
elif Option in ("-v"): # Turn on verbosity.
Verbose = True
else:
print ("\nUnknown option \"" + Option + " " + Argument + "\", aborting...")
print ("\n------------------------------")
usage()
sys.exit(2)

if (Debug):
Verbose = True # Debug implies verbose.

if (Verbose):
print ("\nGet rid of troublesome paste centre mouse button click, v"+"%1d" % (Version)+"."+"%1d" % (Issue)+" "+IssueDate+" dmw")

#***************
# Dump important values.
#---------------
if (Debug):
print
# " 123456789012345678901234567890= "
# print (" 123456789012345678901234567890= ", )
print ("Important values:");
print (" Debug = " + str( Debug))
print (" Verbose = " + str(Verbose))

NullFile = open ("/dev/null", "w")

XinputList = subprocess.check_output(["xinput", "list"], stderr=NullFile).splitlines()

for XinputLine in XinputList:
LowerXinputLine = XinputLine.lower()
if (LowerXinputLine.find(mousestring)>0):
# print ("found mouse in " + XinputLine)
if (LowerXinputLine.find(idstring)>0):
XinputLineAfterID = LowerXinputLine[LowerXinputLine.index(idstring) + len(idstring):]
SplitUpXinputLineAfterID = XinputLineAfterID.split()
MouseIdentifier = int(SplitUpXinputLineAfterID[0])
MouseIdentifierString = str(SplitUpXinputLineAfterID[0])

try:
XinputButtonmapString = subprocess.check_output(["xinput", "get-button-map", str(MouseIdentifier)], stderr=NullFile).rstrip()
if (Verbose):
print
if (Debug):
print ("Mouse ID " + "{0:2,d}".format(MouseIdentifier) + " has buttonmap: " + XinputButtonmapString)
XinputButtonmapList = XinputButtonmapString.split()
# print XinputButtonmapList
NumberOfButtonmaps = len(XinputButtonmapList)
# print (NumberOfButtonmaps)
if (NumberOfButtonmaps >= 3):
if (Debug):
print (" " + "{0:,d}".format(NumberOfButtonmaps) + " buttons on Mouse ID " + "{0:2,d}".format(MouseIdentifier))

MiddleButtonFunction = int(XinputButtonmapList[1], 10)
# print MiddleButtonFunction
if (MiddleButtonFunction == 0):
if (Verbose):
print (" Second button for Mouse ID " + "{0:2,d}".format(MouseIdentifier) + " has no function")
else:
if (Debug):
print (" Second button has function" + "{0:2,d}".format(MiddleButtonFunction))
XinputButtonmapList[1] = "0"
if (Debug):
print (" Changed buttonmap to: ")
print (XinputButtonmapList)
RunList = ["xinput", "set-button-map", MouseIdentifierString] + XinputButtonmapList
if (Debug):
print (" Run command list:")
print (RunList)

try:
XsetButtonmapString = subprocess.check_output(RunList, stderr=NullFile)
if (Verbose):
print ("-> Changed buttonmap for Mouse ID " + "{0:2,d}".format(MouseIdentifier))
except:
print ("-> Unable to change buttonmap for Mouse ID " + "{0:2,d}".format(MouseIdentifier))
# else:
# print (" only " + "{0:2,d}".format(NumberOfButtonmaps) + " buttons on Mouse ID " + "{0:2,d}".format(MouseIdentifier))
except:
if (Verbose):
print
print ("Mouse ID " + "{0:2,d}".format(MouseIdentifier) + " has no buttonmap")
pass

if (Verbose):
print

# else:
# print ("mouse not found in " + XinputLine)

# sys.exit(0)

#***************
# Calls the main function.
#---------------
main()

Leave a Reply