Add an API for configurable eraser button behavior

This adds the public API to configure an eraser button on a tablet tool
to emulate a normal button. In DEFAULT mode the eraser button will
simply do whatever it does by default (i.e. toggle to eraser).
In BUTTON mode the eraser button will be converted to a regular tool
button event, with libinput handling the underlying proximity event
madness.

Part-of: <https://gitlab.freedesktop.org/libinput/libinput/-/merge_requests/1218>
This commit is contained in:
Peter Hutterer 2025-03-25 15:20:14 +10:00
parent 2d23d7f4aa
commit 4ef14e14e4
11 changed files with 764 additions and 1 deletions

View file

@ -123,6 +123,8 @@ src_rst = files(
'svg/tablet-area.svg',
'svg/tablet-axes.svg',
'svg/tablet-cintiq24hd-modes.svg',
'svg/tablet-eraser-invert.svg',
'svg/tablet-eraser-button.svg',
'svg/tablet-interfaces.svg',
'svg/tablet-intuos-modes.svg',
'svg/tablet-left-handed.svg',

View file

@ -0,0 +1,198 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="104.05109mm"
height="64.88131mm"
viewBox="0 0 368.68495 229.89441"
id="svg4321"
version="1.1"
inkscape:version="1.4 (e7c3feb100, 2024-10-09)"
sodipodi:docname="tablet-eraser-button.svg"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:dc="http://purl.org/dc/elements/1.1/">
<defs
id="defs4323">
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient4294"
id="linearGradient4300"
x1="465.81339"
y1="666.13727"
x2="454.82117"
y2="658.65521"
gradientUnits="userSpaceOnUse" />
<linearGradient
inkscape:collect="always"
id="linearGradient4294">
<stop
style="stop-color:#1a1a1a;stop-opacity:1;"
offset="0"
id="stop4296" />
<stop
style="stop-color:#808080;stop-opacity:1"
offset="1"
id="stop4298" />
</linearGradient>
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient4294"
id="linearGradient3"
gradientUnits="userSpaceOnUse"
x1="465.81339"
y1="666.13727"
x2="454.82117"
y2="658.65521" />
</defs>
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="2.8"
inkscape:cx="330.71429"
inkscape:cy="144.28571"
inkscape:document-units="px"
inkscape:current-layer="layer1"
showgrid="false"
inkscape:window-width="3072"
inkscape:window-height="1659"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
fit-margin-top="0"
fit-margin-left="0"
fit-margin-right="0"
fit-margin-bottom="0"
inkscape:showpageshadow="2"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1" />
<metadata
id="metadata4326">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(-67.741956,-463.35919)">
<g
transform="matrix(0.35596319,-0.1450925,0.14027908,0.34415419,-98.897559,452.82709)"
id="g4304"
style="display:inline">
<path
style="display:inline;fill:#cccccc;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 387.83544,799.76093 c -1.1128,3.61694 -3.2211,13.05163 -1.08543,14.07769 2.13567,1.02606 7.81039,-3.72162 10.99756,-6.69095 z"
id="path4286"
inkscape:connector-curvature="0"
sodipodi:nodetypes="czcc" />
<path
style="display:inline;fill:#000000"
d="m 392.64431,804.79039 c -8.52094,-5.90399 -8.49394,-11.01546 0.22879,-43.30647 1.03999,-3.85 2.46829,-9.67602 3.17399,-12.9467 0.99731,-4.62219 2.39455,-7.29497 6.27321,-12 2.74456,-3.32932 5.25157,-6.2783 5.57113,-6.5533 40.78433,-60.97488 80.48307,-125.1652 118.27253,-184 9.86283,-15.675 26.59424,-42.225 37.18089,-59 10.58666,-16.775 34.01422,-53.9 52.06125,-82.5 18.04703,-28.6 35.04505,-55.31677 37.77338,-59.37059 l 4.9606,-7.3706 4.1828,0.57332 c 4.16371,0.5707 4.19706,0.54958 7.30887,-4.62941 3.75631,-6.2516 8.82067,-11.57582 12.2516,-12.88026 5.99391,-2.27888 14.03303,2.9506 14.03303,9.12854 0,3.90203 -2.51704,10.62127 -6.02878,16.09385 -1.63417,2.54664 -2.97122,4.85949 -2.97122,5.13969 0,0.28019 0.9,1.54715 2,2.81546 2.28453,2.63408 2.47267,4.21918 0.86833,7.31574 -1.28218,2.47476 -26.61383,45.18798 -55.85724,94.18426 -10.83283,18.15 -25.72943,43.1137 -33.10357,55.47489 -7.37413,12.3612 -13.69273,23.17153 -14.04131,24.02297 -0.34859,0.85144 -7.50972,12.78774 -15.91363,26.52511 -15.54138,25.40455 -32.24417,52.9052 -70.74345,116.47703 -40.26028,66.47968 -43.66308,72.46026 -49.21634,86.5 -1.74036,4.4 -3.92035,8.675 -4.8444,9.5 -0.92405,0.825 -4.36246,3.75 -7.6409,6.5 -3.27845,2.75 -9.57132,8.3067 -13.98415,12.34823 -10.62726,9.73304 -16.99729,13.87361 -22.52334,14.64034 -3.99187,0.55386 -5.03885,0.251 -9.27207,-2.6821 z"
id="path4283"
inkscape:connector-curvature="0"
sodipodi:nodetypes="ssssccssscsssssssssssssssssss" />
<path
style="fill:url(#linearGradient4300);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 450.89044,688.88586 c 8.71518,5.62513 45.74035,-59.18436 43.57923,-75.43494 l -7.07107,-6.56599 c -29.93081,25.86352 -47.78438,74.72281 -47.78438,74.72281 z"
id="path4292"
inkscape:connector-curvature="0"
sodipodi:nodetypes="scccs" />
</g>
<rect
transform="scale(1,-1)"
y="-693.18524"
x="67.81031"
height="16.036251"
width="168.54825"
id="rect4136"
style="fill:#4d4d4d;fill-opacity:1;stroke:#4d4d4d;stroke-width:0.136709;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:0.546834, 0.136709;stroke-dashoffset:0;stroke-opacity:1" />
<rect
transform="scale(1,-1)"
y="-693.18524"
x="267.8103"
height="16.036251"
width="168.54825"
id="rect5709"
style="opacity:1;fill:#4d4d4d;fill-opacity:1;stroke:#4d4d4d;stroke-width:0.136709;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:0.546834, 0.136709;stroke-dashoffset:0;stroke-opacity:1" />
<text
id="text5713"
y="559.10828"
x="296.20877"
style="font-style:normal;font-weight:normal;line-height:0%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
xml:space="preserve"><tspan
y="559.10828"
x="296.20877"
id="tspan5715"
sodipodi:role="line"
style="font-size:15px;line-height:1.25;font-family:sans-serif">Eraser</tspan></text>
<text
id="text1"
y="559.10828"
x="109.0436"
style="font-style:normal;font-weight:normal;line-height:0%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
xml:space="preserve"><tspan
y="559.10828"
x="109.0436"
id="tspan1"
sodipodi:role="line"
style="font-size:15px;line-height:1.25;font-family:sans-serif">Pen</tspan></text>
<g
transform="matrix(0.35596319,-0.1450925,0.14027908,0.34415419,117.39708,452.82709)"
id="g3"
style="display:inline">
<path
style="display:inline;fill:#cccccc;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 387.83544,799.76093 c -1.1128,3.61694 -3.2211,13.05163 -1.08543,14.07769 2.13567,1.02606 7.81039,-3.72162 10.99756,-6.69095 z"
id="path1"
inkscape:connector-curvature="0"
sodipodi:nodetypes="czcc" />
<path
style="display:inline;fill:#000000"
d="m 392.64431,804.79039 c -8.52094,-5.90399 -8.49394,-11.01546 0.22879,-43.30647 1.03999,-3.85 2.46829,-9.67602 3.17399,-12.9467 0.99731,-4.62219 2.39455,-7.29497 6.27321,-12 2.74456,-3.32932 5.25157,-6.2783 5.57113,-6.5533 40.78433,-60.97488 80.48307,-125.1652 118.27253,-184 9.86283,-15.675 26.59424,-42.225 37.18089,-59 10.58666,-16.775 34.01422,-53.9 52.06125,-82.5 18.04703,-28.6 35.04505,-55.31677 37.77338,-59.37059 l 4.9606,-7.3706 4.1828,0.57332 c 4.16371,0.5707 4.19706,0.54958 7.30887,-4.62941 3.75631,-6.2516 8.82067,-11.57582 12.2516,-12.88026 5.99391,-2.27888 14.03303,2.9506 14.03303,9.12854 0,3.90203 -2.51704,10.62127 -6.02878,16.09385 -1.63417,2.54664 -2.97122,4.85949 -2.97122,5.13969 0,0.28019 0.9,1.54715 2,2.81546 2.28453,2.63408 2.47267,4.21918 0.86833,7.31574 -1.28218,2.47476 -26.61383,45.18798 -55.85724,94.18426 -10.83283,18.15 -25.72943,43.1137 -33.10357,55.47489 -7.37413,12.3612 -13.69273,23.17153 -14.04131,24.02297 -0.34859,0.85144 -7.50972,12.78774 -15.91363,26.52511 -15.54138,25.40455 -32.24417,52.9052 -70.74345,116.47703 -40.26028,66.47968 -43.66308,72.46026 -49.21634,86.5 -1.74036,4.4 -3.92035,8.675 -4.8444,9.5 -0.92405,0.825 -4.36246,3.75 -7.6409,6.5 -3.27845,2.75 -9.57132,8.3067 -13.98415,12.34823 -10.62726,9.73304 -16.99729,13.87361 -22.52334,14.64034 -3.99187,0.55386 -5.03885,0.251 -9.27207,-2.6821 z"
id="path2"
inkscape:connector-curvature="0"
sodipodi:nodetypes="ssssccssscsssssssssssssssssss" />
<path
style="fill:url(#linearGradient3);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 450.89044,688.88586 c 8.71518,5.62513 45.74035,-59.18436 43.57923,-75.43494 l -7.07107,-6.56599 c -29.93081,25.86352 -47.78438,74.72281 -47.78438,74.72281 z"
id="path3"
inkscape:connector-curvature="0"
sodipodi:nodetypes="scccs" />
</g>
<g
transform="matrix(-0.21797128,-0.07136846,0.05329769,-0.25512499,403.9107,823.07273)"
id="g3663-9-8">
<path
d="m 388.57143,893.79076 -57.14285,-130 c 0,0 -30.0247,-58.84827 4.28571,-70.00001 27.07438,-8.79984 37.32196,9.59496 40,14.64286 27.54455,51.91936 84.64285,173.21429 84.64285,173.21429 h -0.71428 z"
id="path2820-6-7"
style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
inkscape:connector-curvature="0" />
<path
d="m 360.32021,827.78041 c -15.74169,-35.7991 -29.44655,-66.92657 -30.45523,-69.17214 -7.08929,-15.78239 -10.8761,-32.88254 -9.6176,-43.43026 1.39575,-11.69796 7.19746,-18.50389 18.22574,-21.38044 5.18218,-1.35169 8.54724,-1.76827 12.41155,-1.53649 4.43642,0.26609 6.95929,0.93715 11.03011,2.93391 3.93491,1.9301 8.0085,5.56248 10.68932,9.53159 3.68818,5.46055 26.56068,50.9623 49.57778,98.62829 16.60192,34.38082 37.06388,77.41994 36.89013,77.59369 -0.13286,0.13286 -69.01932,11.92114 -69.66286,11.92114 -0.27909,0 -12.00972,-26.24842 -29.08894,-65.08929 z"
id="path2824-1-6"
style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:#ffccaa;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:0.002;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;marker:none;enable-background:accumulate"
inkscape:connector-curvature="0" />
<path
d="m 334.75785,756.75053 c -7.08929,-15.78239 -10.28437,-26.89033 -9.02587,-37.43805 1.39575,-11.69796 5.8085,-16.73613 16.83678,-19.61268 12.44766,-3.59459 20.03902,-1.91353 27.39013,8.75815 11.42622,25.66382 13.40166,29.05484 15.06365,35.48866 -0.13286,0.13286 -42.89663,15.49027 -44.57776,16.18518 -1.72922,0.71479 -4.94789,-2.09377 -5.68693,-3.38126 z"
id="path2824-7-1-7"
style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:0.92;fill:#ffe6d5;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.2;marker:none;enable-background:accumulate"
inkscape:connector-curvature="0" />
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 12 KiB

View file

@ -0,0 +1,179 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="104.05109mm"
height="64.88131mm"
viewBox="0 0 368.68495 229.89441"
id="svg4321"
version="1.1"
inkscape:version="1.4 (e7c3feb100, 2024-10-09)"
sodipodi:docname="tablet-eraser-invert.svg"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:dc="http://purl.org/dc/elements/1.1/">
<defs
id="defs4323">
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient4294"
id="linearGradient4300"
x1="465.81339"
y1="666.13727"
x2="454.82117"
y2="658.65521"
gradientUnits="userSpaceOnUse" />
<linearGradient
inkscape:collect="always"
id="linearGradient4294">
<stop
style="stop-color:#1a1a1a;stop-opacity:1;"
offset="0"
id="stop4296" />
<stop
style="stop-color:#808080;stop-opacity:1"
offset="1"
id="stop4298" />
</linearGradient>
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient4294"
id="linearGradient5721"
gradientUnits="userSpaceOnUse"
x1="465.81339"
y1="666.13727"
x2="454.82117"
y2="658.65521" />
</defs>
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="2.8"
inkscape:cx="330.71429"
inkscape:cy="144.28571"
inkscape:document-units="px"
inkscape:current-layer="layer1"
showgrid="false"
inkscape:window-width="3072"
inkscape:window-height="1659"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
fit-margin-top="0"
fit-margin-left="0"
fit-margin-right="0"
fit-margin-bottom="0"
inkscape:showpageshadow="2"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1" />
<metadata
id="metadata4326">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(-67.741956,-463.35919)">
<g
transform="matrix(0.35596319,-0.1450925,0.14027908,0.34415419,-98.897559,452.82709)"
id="g4304"
style="display:inline">
<path
style="display:inline;fill:#cccccc;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 387.83544,799.76093 c -1.1128,3.61694 -3.2211,13.05163 -1.08543,14.07769 2.13567,1.02606 7.81039,-3.72162 10.99756,-6.69095 z"
id="path4286"
inkscape:connector-curvature="0"
sodipodi:nodetypes="czcc" />
<path
style="display:inline;fill:#000000"
d="m 392.64431,804.79039 c -8.52094,-5.90399 -8.49394,-11.01546 0.22879,-43.30647 1.03999,-3.85 2.46829,-9.67602 3.17399,-12.9467 0.99731,-4.62219 2.39455,-7.29497 6.27321,-12 2.74456,-3.32932 5.25157,-6.2783 5.57113,-6.5533 40.78433,-60.97488 80.48307,-125.1652 118.27253,-184 9.86283,-15.675 26.59424,-42.225 37.18089,-59 10.58666,-16.775 34.01422,-53.9 52.06125,-82.5 18.04703,-28.6 35.04505,-55.31677 37.77338,-59.37059 l 4.9606,-7.3706 4.1828,0.57332 c 4.16371,0.5707 4.19706,0.54958 7.30887,-4.62941 3.75631,-6.2516 8.82067,-11.57582 12.2516,-12.88026 5.99391,-2.27888 14.03303,2.9506 14.03303,9.12854 0,3.90203 -2.51704,10.62127 -6.02878,16.09385 -1.63417,2.54664 -2.97122,4.85949 -2.97122,5.13969 0,0.28019 0.9,1.54715 2,2.81546 2.28453,2.63408 2.47267,4.21918 0.86833,7.31574 -1.28218,2.47476 -26.61383,45.18798 -55.85724,94.18426 -10.83283,18.15 -25.72943,43.1137 -33.10357,55.47489 -7.37413,12.3612 -13.69273,23.17153 -14.04131,24.02297 -0.34859,0.85144 -7.50972,12.78774 -15.91363,26.52511 -15.54138,25.40455 -32.24417,52.9052 -70.74345,116.47703 -40.26028,66.47968 -43.66308,72.46026 -49.21634,86.5 -1.74036,4.4 -3.92035,8.675 -4.8444,9.5 -0.92405,0.825 -4.36246,3.75 -7.6409,6.5 -3.27845,2.75 -9.57132,8.3067 -13.98415,12.34823 -10.62726,9.73304 -16.99729,13.87361 -22.52334,14.64034 -3.99187,0.55386 -5.03885,0.251 -9.27207,-2.6821 z"
id="path4283"
inkscape:connector-curvature="0"
sodipodi:nodetypes="ssssccssscsssssssssssssssssss" />
<path
style="fill:url(#linearGradient4300);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 450.89044,688.88586 c 8.71518,5.62513 45.74035,-59.18436 43.57923,-75.43494 l -7.07107,-6.56599 c -29.93081,25.86352 -47.78438,74.72281 -47.78438,74.72281 z"
id="path4292"
inkscape:connector-curvature="0"
sodipodi:nodetypes="scccs" />
</g>
<rect
transform="scale(1,-1)"
y="-693.18524"
x="67.81031"
height="16.036251"
width="168.54825"
id="rect4136"
style="fill:#4d4d4d;fill-opacity:1;stroke:#4d4d4d;stroke-width:0.136709;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:0.546834, 0.136709;stroke-dashoffset:0;stroke-opacity:1" />
<g
transform="matrix(-0.36104723,0.13193379,-0.12755691,-0.34906957,644.73068,698.04111)"
id="g5701"
style="display:inline">
<path
style="display:inline;fill:#cccccc;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 387.83544,799.76093 c -1.1128,3.61694 -3.2211,13.05163 -1.08543,14.07769 2.13567,1.02606 7.81039,-3.72162 10.99756,-6.69095 z"
id="path5703"
inkscape:connector-curvature="0"
sodipodi:nodetypes="czcc" />
<path
style="display:inline;fill:#000000"
d="m 392.64431,804.79039 c -8.52094,-5.90399 -8.49394,-11.01546 0.22879,-43.30647 1.03999,-3.85 2.46829,-9.67602 3.17399,-12.9467 0.99731,-4.62219 2.39455,-7.29497 6.27321,-12 2.74456,-3.32932 5.25157,-6.2783 5.57113,-6.5533 40.78433,-60.97488 80.48307,-125.1652 118.27253,-184 9.86283,-15.675 26.59424,-42.225 37.18089,-59 10.58666,-16.775 34.01422,-53.9 52.06125,-82.5 18.04703,-28.6 35.04505,-55.31677 37.77338,-59.37059 l 4.9606,-7.3706 4.1828,0.57332 c 4.16371,0.5707 4.19706,0.54958 7.30887,-4.62941 3.75631,-6.2516 8.82067,-11.57582 12.2516,-12.88026 5.99391,-2.27888 14.03303,2.9506 14.03303,9.12854 0,3.90203 -2.51704,10.62127 -6.02878,16.09385 -1.63417,2.54664 -2.97122,4.85949 -2.97122,5.13969 0,0.28019 0.9,1.54715 2,2.81546 2.28453,2.63408 2.47267,4.21918 0.86833,7.31574 -1.28218,2.47476 -26.61383,45.18798 -55.85724,94.18426 -10.83283,18.15 -25.72943,43.1137 -33.10357,55.47489 -7.37413,12.3612 -13.69273,23.17153 -14.04131,24.02297 -0.34859,0.85144 -7.50972,12.78774 -15.91363,26.52511 -15.54138,25.40455 -32.24417,52.9052 -70.74345,116.47703 -40.26028,66.47968 -43.66308,72.46026 -49.21634,86.5 -1.74036,4.4 -3.92035,8.675 -4.8444,9.5 -0.92405,0.825 -4.36246,3.75 -7.6409,6.5 -3.27845,2.75 -9.57132,8.3067 -13.98415,12.34823 -10.62726,9.73304 -16.99729,13.87361 -22.52334,14.64034 -3.99187,0.55386 -5.03885,0.251 -9.27207,-2.6821 z"
id="path5705"
inkscape:connector-curvature="0"
sodipodi:nodetypes="ssssccssscsssssssssssssssssss" />
<path
style="fill:url(#linearGradient5721);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 450.89044,688.88586 c 8.71518,5.62513 45.74035,-59.18436 43.57923,-75.43494 l -7.07107,-6.56599 c -29.93081,25.86352 -47.78438,74.72281 -47.78438,74.72281 z"
id="path5707"
inkscape:connector-curvature="0"
sodipodi:nodetypes="scccs" />
</g>
<rect
transform="scale(1,-1)"
y="-693.18524"
x="267.8103"
height="16.036251"
width="168.54825"
id="rect5709"
style="opacity:1;fill:#4d4d4d;fill-opacity:1;stroke:#4d4d4d;stroke-width:0.136709;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:0.546834, 0.136709;stroke-dashoffset:0;stroke-opacity:1" />
<text
id="text5713"
y="559.10828"
x="296.20877"
style="font-style:normal;font-weight:normal;line-height:0%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
xml:space="preserve"><tspan
y="559.10828"
x="296.20877"
id="tspan5715"
sodipodi:role="line"
style="font-size:15px;line-height:1.25;font-family:sans-serif">Eraser</tspan></text>
<text
id="text1"
y="559.10828"
x="109.0436"
style="font-style:normal;font-weight:normal;line-height:0%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
xml:space="preserve"><tspan
y="559.10828"
x="109.0436"
id="tspan1"
sodipodi:role="line"
style="font-size:15px;line-height:1.25;font-family:sans-serif">Pen</tspan></text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 9.4 KiB

View file

@ -495,3 +495,50 @@ maximum provided in this call.
The size of the tablet reported by **libinput_device_get_size()** always reflects
the physical area, not the logical area.
.. _tablet-eraser-button:
------------------------------------------------------------------------------
Tablet eraser buttons
------------------------------------------------------------------------------
Tablet tools come in a variety of forms but the most common one is a
pen-like tool. Some of these pen-like tools have a virtual eraser at the
tip of the tool - inverting the tool brings the eraser into proximity.
.. figure:: tablet-eraser-invert.svg
:align: center
An pen-like tool used as pen and as eraser by inverting it
Having an eraser as a separate tool is beneficial in many applications as the
eraser tool can be assigned different functionality (colors, paint tools, etc.)
that is easily available.
However, a large proportion of tablet pens have an "eraser button". By
pressing the button the pen switches to be an eraser tool.
On the data level this is not done via a button event, instead the firmware
will pretend the pen tool going out of proximity and the eraser coming
into proximity immediately after - as if the tool was physically inverted.
.. figure:: tablet-eraser-button.svg
:align: center
An pen-like tool used as pen and as eraser by pressing the eraser button
Microsoft mandates this behavior (see
`Windows Pen States <https://learn.microsoft.com/en-us/windows-hardware/design/component-guidelines/windows-pen-states>`_
for details) and thus the overwhelming majority of devices will have
an eraser button that virtually inverts the pen.
Enforcing an eraser button means that users have one button less on the
stylus that they would have otherwise. For some users the eraser button
is in an inconvenient location, others don't want an eraser button at all.
libinput provides an eraser button configuration that allows disabling the
eraser button and turning it into a normal button event. If the eraser button
is disabled, pressing that button will generate a normal tablet tool button
event.
This configuration is only available on pens with an eraser button, not on
with an invert-type eraser.

View file

@ -542,6 +542,17 @@ struct libinput_tablet_tool_config_pressure_range {
void (*get_default)(struct libinput_tablet_tool *tool, double *min, double *max);
};
struct libinput_tablet_tool_config_eraser_button {
bitmask_t (*get_modes)(struct libinput_tablet_tool *tool);
enum libinput_config_status (*set_mode)(struct libinput_tablet_tool *tool, enum libinput_config_eraser_button_mode mode);
enum libinput_config_eraser_button_mode (*get_mode)(struct libinput_tablet_tool *tool);
enum libinput_config_eraser_button_mode (*get_default_mode)(struct libinput_tablet_tool *tool);
enum libinput_config_status (*set_button)(struct libinput_tablet_tool *tool, unsigned int button);
unsigned int (*get_button)(struct libinput_tablet_tool *tool);
unsigned int (*get_default_button)(struct libinput_tablet_tool *tool);
};
struct libinput_tablet_tool_pressure_threshold {
unsigned int tablet_id;
@ -581,6 +592,7 @@ struct libinput_tablet_tool {
struct {
struct libinput_tablet_tool_config_pressure_range pressure_range;
struct libinput_tablet_tool_config_eraser_button eraser_button;
} config;
};

View file

@ -5056,6 +5056,83 @@ libinput_tablet_tool_config_pressure_range_get_default_maximum(struct libinput_t
return max;
}
LIBINPUT_EXPORT uint32_t
libinput_tablet_tool_config_eraser_button_get_modes(struct libinput_tablet_tool *tool)
{
if (!tool->config.eraser_button.get_modes)
return 0;
return bitmask_as_u32(tool->config.eraser_button.get_modes(tool));
}
LIBINPUT_EXPORT enum libinput_config_status
libinput_tablet_tool_config_eraser_button_set_mode(struct libinput_tablet_tool *tool,
enum libinput_config_eraser_button_mode mode)
{
uint32_t modes = libinput_tablet_tool_config_eraser_button_get_modes(tool);
if (mode && (modes & mode) == 0)
return LIBINPUT_CONFIG_STATUS_UNSUPPORTED;
switch (mode) {
case LIBINPUT_CONFIG_ERASER_BUTTON_DEFAULT:
case LIBINPUT_CONFIG_ERASER_BUTTON_BUTTON:
break;
default:
return LIBINPUT_CONFIG_STATUS_INVALID;
}
return tool->config.eraser_button.set_mode(tool, mode);
}
LIBINPUT_EXPORT enum libinput_config_eraser_button_mode
libinput_tablet_tool_config_eraser_button_get_mode(struct libinput_tablet_tool *tool)
{
if (!libinput_tablet_tool_config_eraser_button_get_modes(tool))
return LIBINPUT_CONFIG_ERASER_BUTTON_DEFAULT;
return tool->config.eraser_button.get_mode(tool);
}
LIBINPUT_EXPORT enum libinput_config_eraser_button_mode
libinput_tablet_tool_config_eraser_button_get_default_mode(struct libinput_tablet_tool *tool)
{
if (!libinput_tablet_tool_config_eraser_button_get_modes(tool))
return LIBINPUT_CONFIG_ERASER_BUTTON_DEFAULT;
return tool->config.eraser_button.get_mode(tool);
}
LIBINPUT_EXPORT enum libinput_config_status
libinput_tablet_tool_config_eraser_button_set_button(struct libinput_tablet_tool *tool,
unsigned int button)
{
if (!libinput_tablet_tool_config_eraser_button_get_modes(tool))
return LIBINPUT_CONFIG_STATUS_UNSUPPORTED;
if (!libinput_tablet_tool_has_button(tool, button))
return LIBINPUT_CONFIG_STATUS_INVALID;
return tool->config.eraser_button.set_button(tool, button);
}
LIBINPUT_EXPORT unsigned int
libinput_tablet_tool_config_eraser_button_get_button(struct libinput_tablet_tool *tool)
{
if (!libinput_tablet_tool_config_eraser_button_get_modes(tool))
return 0;
return tool->config.eraser_button.get_button(tool);
}
LIBINPUT_EXPORT unsigned int
libinput_tablet_tool_config_eraser_button_get_default_button(struct libinput_tablet_tool *tool)
{
if (!libinput_tablet_tool_config_eraser_button_get_modes(tool))
return 0;
return tool->config.eraser_button.get_button(tool);
}
#if HAVE_LIBWACOM
WacomDeviceDatabase *
libinput_libwacom_ref(struct libinput *li)

View file

@ -6973,6 +6973,203 @@ libinput_tablet_tool_config_pressure_range_get_default_minimum(struct libinput_t
double
libinput_tablet_tool_config_pressure_range_get_default_maximum(struct libinput_tablet_tool *tool);
/**
* @ingroup config
*/
enum libinput_config_eraser_button_mode {
/**
* Use the default hardware behavior of the tool. libinput
* does not modify the behavior of the eraser button (if any).
*/
LIBINPUT_CONFIG_ERASER_BUTTON_DEFAULT = 0,
/**
* The eraser button on the tool sends a button event
* instead. If this tool comes into proximity as an eraser,
* a button event on the pen is emulated instead.
*
* See libinput_tablet_tool_config_eraser_button_set_mode() for details.
*/
LIBINPUT_CONFIG_ERASER_BUTTON_BUTTON = (1 << 0),
};
/**
* @ingroup config
*
* Check if a tool can change the behavior of or to a firmware eraser button.
*
* A firmware eraser button is a button on the tool that, when pressed,
* virtually toggles the pen going out of proximity followed by the
* eraser tool coming in proximity. When released, the eraser goes
* out of proximity followed by the pen coming back into proximity.
*
* This is the default behavior for many contemporary pens who implement
* this in firmware. See also the [Windows Pen
* States](https://learn.microsoft.com/en-us/windows-hardware/design/component-guidelines/windows-pen-states).
*
* See the libinput documentation for more details.
*
* @param tool The libinput tool
* @return Non-zero if the device can be set to change to an eraser on button
* press.
*
* @see libinput_tablet_tool_config_eraser_button_get_modes
* @see libinput_tablet_tool_config_eraser_button_set_mode
* @see libinput_tablet_tool_config_eraser_button_get_mode
* @see libinput_tablet_tool_config_eraser_button_get_default_mode
*
* @since 1.29
*/
uint32_t
libinput_tablet_tool_config_eraser_button_get_modes(struct libinput_tablet_tool *tool);
/**
* @ingroup config
*
* Change the eraser button behavior on a tool.
*
* If set to @ref LIBINPUT_CONFIG_ERASER_BUTTON_BUTTON, pressing the
* firmware eraser button on the tool instead triggers an event
* of type @ref LIBINPUT_EVENT_TABLET_TOOL_BUTTON.
* This event's libinput_event_tablet_tool_get_button() returns the
* button set with
* libinput_tablet_tool_config_eraser_button_set_button()
* Releasing the firmware eraser button releases that button again.
*
* @param tool The libinput tool
* @param mode The eraser button mode to switch to
*
* @return A config status code
*
* @see libinput_tablet_tool_config_eraser_button_get_modes
* @see libinput_tablet_tool_config_eraser_button_set_mode
* @see libinput_tablet_tool_config_eraser_button_get_mode
* @see libinput_tablet_tool_config_eraser_button_get_default_mode
* @see libinput_tablet_tool_config_eraser_button_set_button
* @see libinput_tablet_tool_config_eraser_button_get_button
* @see libinput_tablet_tool_config_eraser_button_get_default_button
*
* @since 1.29
*/
enum libinput_config_status
libinput_tablet_tool_config_eraser_button_set_mode(struct libinput_tablet_tool *tool,
enum libinput_config_eraser_button_mode mode);
/**
* @ingroup config
*
* Get the mode for the eraser button.
*
* @param tool The libinput tool
*
* @return The eraser mode
*
* @see libinput_tablet_tool_config_eraser_button_get_modes
* @see libinput_tablet_tool_config_eraser_button_set_mode
* @see libinput_tablet_tool_config_eraser_button_get_mode
* @see libinput_tablet_tool_config_eraser_button_get_default_mode
* @see libinput_tablet_tool_config_eraser_button_set_button
* @see libinput_tablet_tool_config_eraser_button_get_button
* @see libinput_tablet_tool_config_eraser_button_get_default_button
*
* @since 1.29
*/
enum libinput_config_eraser_button_mode
libinput_tablet_tool_config_eraser_button_get_mode(struct libinput_tablet_tool *tool);
/**
* @ingroup config
*
* Get the default mode for the eraser button.
*
* @param tool The libinput tool
*
* @return The eraser button, if any, or zero otherwise
*
* @see libinput_tablet_tool_config_eraser_button_get_modes
* @see libinput_tablet_tool_config_eraser_button_set_mode
* @see libinput_tablet_tool_config_eraser_button_get_mode
* @see libinput_tablet_tool_config_eraser_button_get_default_mode
* @see libinput_tablet_tool_config_eraser_button_set_button
* @see libinput_tablet_tool_config_eraser_button_get_button
* @see libinput_tablet_tool_config_eraser_button_get_default_button
*
* @since 1.29
*/
enum libinput_config_eraser_button_mode
libinput_tablet_tool_config_eraser_button_get_default_mode(struct libinput_tablet_tool *tool);
/**
* @ingroup config
*
* Set a button to be the eraser button for this tool.
* This configuration has no effect unless the caller also sets
* the eraser mode to @ref LIBINPUT_CONFIG_ERASER_BUTTON_BUTTON via
* libinput_tablet_tool_config_eraser_button_set_mode().
*
* @param tool The libinput tool
* @param button The button, usually one of BTN_STYLUS, BTN_STYLUS2 or
* BTN_STYLUS3
*
* @return A config status code
*
* @see libinput_tablet_tool_config_eraser_button_get_modes
* @see libinput_tablet_tool_config_eraser_button_set_mode
* @see libinput_tablet_tool_config_eraser_button_get_mode
* @see libinput_tablet_tool_config_eraser_button_get_default_mode
* @see libinput_tablet_tool_config_eraser_button_set_button
* @see libinput_tablet_tool_config_eraser_button_get_button
* @see libinput_tablet_tool_config_eraser_button_get_default_button
*
* @since 1.29
*/
enum libinput_config_status
libinput_tablet_tool_config_eraser_button_set_button(struct libinput_tablet_tool *tool,
unsigned int button);
/**
* @ingroup config
*
* Get the button configured to emulate an eraser for this tool.
*
* @param tool The libinput tool
*
* @return The eraser button, if any, or zero otherwise
*
* @see libinput_tablet_tool_config_eraser_button_get_modes
* @see libinput_tablet_tool_config_eraser_button_set_mode
* @see libinput_tablet_tool_config_eraser_button_get_mode
* @see libinput_tablet_tool_config_eraser_button_get_default_mode
* @see libinput_tablet_tool_config_eraser_button_set_button
* @see libinput_tablet_tool_config_eraser_button_get_button
* @see libinput_tablet_tool_config_eraser_button_get_default_button
*
* @since 1.29
*/
unsigned int
libinput_tablet_tool_config_eraser_button_get_button(struct libinput_tablet_tool *tool);
/**
* @ingroup config
*
* Get the default button configured to emulate an eraser for this tool.
*
* @param tool The libinput tool
*
* @return The eraser button, if any, or zero otherwise
*
* @see libinput_tablet_tool_config_eraser_button_get_modes
* @see libinput_tablet_tool_config_eraser_button_set_mode
* @see libinput_tablet_tool_config_eraser_button_get_mode
* @see libinput_tablet_tool_config_eraser_button_get_default_mode
* @see libinput_tablet_tool_config_eraser_button_set_button
* @see libinput_tablet_tool_config_eraser_button_get_button
* @see libinput_tablet_tool_config_eraser_button_get_default_button
*
* @since 1.29
*/
unsigned int
libinput_tablet_tool_config_eraser_button_get_default_button(struct libinput_tablet_tool *tool);
#ifdef __cplusplus
}
#endif

View file

@ -364,3 +364,13 @@ LIBINPUT_1.28 {
libinput_device_config_3fg_drag_get_enabled;
libinput_device_config_3fg_drag_get_default_enabled;
} LIBINPUT_1.27;
LIBINPUT_1.29 {
libinput_tablet_tool_config_eraser_button_get_button;
libinput_tablet_tool_config_eraser_button_get_default_button;
libinput_tablet_tool_config_eraser_button_get_default_mode;
libinput_tablet_tool_config_eraser_button_get_mode;
libinput_tablet_tool_config_eraser_button_get_modes;
libinput_tablet_tool_config_eraser_button_set_button;
libinput_tablet_tool_config_eraser_button_set_mode;
} LIBINPUT_1.28;

View file

@ -117,6 +117,14 @@ Sets the type of the custom acceleration function.
Defaults to fallback.
This only applies to the custom profile.
.TP 8
.B \-\-set\-eraser\-button\-button=[BTN_STYLUS|BTN_STYLUS2|BTN_STYLUS3]
Sets the eraser button button to the given tablet tool button. Only
takes effect if combined with
.B \-\-set\-eraser\-button\-mode=on\-button\-down.
.TP 8
.B \-\-set\-eraser\-button\-mode=[default|on-button-down]
Sets the eraser button mode to the given mode.
.TP 8
.B \-\-set\-pressure\-range=<min>:<max>
Set the tablet tool pressure range to min:max. min and max must be in range [0.0, 1.0].
.TP 8

View file

@ -436,6 +436,33 @@ tools_parse_option(int option,
return 1;
}
break;
case OPT_ERASER_BUTTON_MODE:
if (!optarg)
return 1;
if (streq(optarg, "default"))
options->eraser_button_mode = LIBINPUT_CONFIG_ERASER_BUTTON_DEFAULT;
else if (streq(optarg, "button"))
options->eraser_button_mode = LIBINPUT_CONFIG_ERASER_BUTTON_BUTTON;
else {
fprintf(stderr, "Invalid --set-eraser-button-mode\n"
"Valid options: default|button\n");
return 1;
}
break;
case OPT_ERASER_BUTTON_BUTTON:
if (!optarg)
return 1;
if (streq(optarg, "BTN_STYLUS"))
options->eraser_button_button = BTN_STYLUS;
else if (streq(optarg, "BTN_STYLUS2"))
options->eraser_button_button = BTN_STYLUS2;
else if (streq(optarg, "BTN_STYLUS3"))
options->eraser_button_button = BTN_STYLUS3;
else {
fprintf(stderr, "Unsupported eraser button %s\n", optarg);
return 1;
}
break;
}
return 0;
}

View file

@ -76,6 +76,8 @@ enum configuration_options {
OPT_AREA,
OPT_3FG_DRAG,
OPT_SENDEVENTS,
OPT_ERASER_BUTTON_MODE,
OPT_ERASER_BUTTON_BUTTON,
};
#define CONFIGURATION_OPTIONS \
@ -114,7 +116,9 @@ enum configuration_options {
{ "set-rotation-angle", required_argument, 0, OPT_ROTATION_ANGLE }, \
{ "set-pressure-range", required_argument, 0, OPT_PRESSURE_RANGE }, \
{ "set-calibration", required_argument, 0, OPT_CALIBRATION }, \
{ "set-area", required_argument, 0, OPT_AREA }
{ "set-area", required_argument, 0, OPT_AREA }, \
{ "set-eraser-button-mode", required_argument, 0, OPT_ERASER_BUTTON_MODE }, \
{ "set-eraser-button-button", required_argument, 0, OPT_ERASER_BUTTON_BUTTON }
static inline void
tools_print_usage_option_list(struct option *opts)
@ -170,6 +174,8 @@ struct tools_options {
struct libinput_config_area_rectangle area;
enum libinput_config_3fg_drag_state drag_3fg;
enum libinput_config_send_events_mode sendevents;
enum libinput_config_eraser_button_mode eraser_button_mode;
unsigned int eraser_button_button;
};
void tools_init_options(struct tools_options *options);