ButtonDialog: add key mapping stack page

ratbag's key mapping signature is forcing us to treat modifiers
different from regular keys. To detect them, we need several workarounds
over Gdk.EventKey which makes the code uglier than it could be. Perhaps
we should just skip this altogether and implement macros only; since
they don't differentiate between modifiers and regular keys all these
workaround can then be removed while providing the same functionality.
This commit is contained in:
Jente Hidskes
2017-07-19 12:09:06 +02:00
committed by Peter Hutterer
parent 0773bbcbca
commit 847d8fcf3a
6 changed files with 734 additions and 50 deletions

View File

@@ -0,0 +1,245 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="256"
height="72"
viewBox="0 0 256 72.000001"
id="svg3611"
version="1.1"
inkscape:version="0.91 r13725"
sodipodi:docname="enter-keyboard-shortcut.svg">
<defs
id="defs3613" />
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="2.8"
inkscape:cx="137.98997"
inkscape:cy="34.663602"
inkscape:document-units="px"
inkscape:current-layer="layer1"
showgrid="false"
units="px"
inkscape:window-width="1366"
inkscape:window-height="704"
inkscape:window-x="0"
inkscape:window-y="27"
inkscape:window-maximized="1" />
<metadata
id="metadata3616">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Camada 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(0,-980.36216)">
<g
id="g3715"
transform="translate(-503.23415,689.94658)">
<path
d="m 509.66363,325.47627 c 5.53002,1.4185 18.51389,1.4185 24.29359,0 0.80721,-0.19813 1.43306,0.67185 1.50029,1.50028 l 1.99751,13.05863 -1.50028,0 -28.28862,0 -1.50029,0 c 0.66583,-4.35287 1.33167,-8.70575 1.99752,-13.05863 0.12564,-0.82161 0.69518,-1.70681 1.50028,-1.50028 z"
id="path27275"
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:1.59999979;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
sodipodi:nodetypes="sssccccss"
inkscape:connector-curvature="0" />
<path
d="m 543.62146,325.47627 c 5.53,1.4185 18.51387,1.4185 24.29357,0 0.80721,-0.19813 1.43308,0.67185 1.50029,1.50028 l 1.99753,13.05863 -1.5003,0 -28.28862,0 -1.50029,0 c 0.66583,-4.35287 1.33168,-8.70575 1.99752,-13.05863 0.12564,-0.82161 0.69518,-1.70681 1.5003,-1.50028 z"
id="path27277"
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:1.59999979;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
sodipodi:nodetypes="sssccccss"
inkscape:connector-curvature="0" />
<path
d="m 577.57927,325.47627 c 5.53,1.4185 18.51387,1.4185 24.29357,0 0.80721,-0.19813 1.43308,0.67185 1.50029,1.50028 l 1.99753,13.05863 -1.5003,0 -28.28862,0 -1.50029,0 c 0.66583,-4.35287 1.33168,-8.70575 1.99752,-13.05863 0.12564,-0.82161 0.6952,-1.70681 1.5003,-1.50028 z"
id="path27279"
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:1.59999979;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
sodipodi:nodetypes="sssccccss"
inkscape:connector-curvature="0" />
<path
d="m 611.12326,325.47627 c 5.53002,1.4185 18.51389,1.4185 24.29359,0 0.80721,-0.19813 1.43306,0.67185 1.50029,1.50028 l 1.99751,13.05863 -1.50028,0 -28.28862,0 -1.50029,0 c 0.66583,-4.35287 1.33167,-8.70575 1.99752,-13.05863 0.12564,-0.82161 0.69518,-1.70681 1.50028,-1.50028 z"
id="path5218"
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:1.59999979;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
sodipodi:nodetypes="sssccccss"
inkscape:connector-curvature="0" />
<path
d="m 645.08109,325.47627 c 5.53,1.4185 18.51387,1.4185 24.29357,0 0.80721,-0.19813 1.43308,0.67185 1.50029,1.50028 l 1.99753,13.05863 -1.5003,0 -28.28862,0 -1.50029,0 c 0.66583,-4.35287 1.33168,-8.70575 1.99751,-13.05863 0.12565,-0.82161 0.69519,-1.70681 1.50031,-1.50028 z"
id="path5220"
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:1.59999979;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
sodipodi:nodetypes="sssccccss"
inkscape:connector-curvature="0" />
<path
d="m 679.0389,325.47627 c 5.53,1.4185 18.51387,1.4185 24.29357,0 0.80721,-0.19813 1.43308,0.67185 1.50029,1.50028 l 1.99753,13.05863 -1.5003,0 -28.28862,0 -1.50029,0 c 0.66583,-4.35287 1.33168,-8.70575 1.99751,-13.05863 0.12565,-0.82161 0.69521,-1.70681 1.50031,-1.50028 z"
id="path5222"
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:1.59999979;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
sodipodi:nodetypes="sssccccss"
inkscape:connector-curvature="0" />
<path
d="m 712.58289,325.47627 c 5.53002,1.4185 18.51389,1.4185 24.29359,0 0.80721,-0.19813 1.43306,0.67185 1.50029,1.50028 l 1.99751,13.05863 -1.50028,0 -28.28862,0 -1.50029,0 c 0.66583,-4.35287 1.33167,-8.70575 1.99751,-13.05863 0.12565,-0.82161 0.69519,-1.70681 1.50029,-1.50028 z"
id="path4829"
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:1.60000002;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
sodipodi:nodetypes="sssccccss"
inkscape:connector-curvature="0" />
<path
d="m 516.19044,335.26648 c 5.53002,1.41851 18.51389,1.41851 24.2936,0 0.8072,-0.19812 1.43306,0.67186 1.50028,1.50029 l 1.99752,13.05863 -1.50029,0 -28.28862,0 -1.50029,0 c 0.66584,-4.35288 1.33167,-8.70575 1.99752,-13.05863 0.12564,-0.82161 0.69518,-1.70681 1.50028,-1.50029 z"
id="path3662"
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:1.59999979;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
sodipodi:nodetypes="sssccccss"
inkscape:connector-curvature="0" />
<path
d="m 550.14827,335.26648 c 5.53,1.41851 18.51387,1.41851 24.29358,0 0.8072,-0.19812 1.43307,0.67186 1.50028,1.50029 l 1.99753,13.05863 -1.5003,0 -28.28862,0 -1.50029,0 c 0.66584,-4.35288 1.33168,-8.70575 1.99752,-13.05863 0.12564,-0.82161 0.69518,-1.70681 1.5003,-1.50029 z"
id="path3664"
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:1.59999979;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
sodipodi:nodetypes="sssccccss"
inkscape:connector-curvature="0" />
<path
d="m 584.10608,335.26648 c 5.53,1.41851 18.51387,1.41851 24.29357,0 0.80721,-0.19812 1.43308,0.67186 1.50029,1.50029 l 1.99753,13.05863 -1.5003,0 -28.28862,0 -1.50029,0 c 0.66584,-4.35288 1.33168,-8.70575 1.99752,-13.05863 0.12564,-0.82161 0.6952,-1.70681 1.5003,-1.50029 z"
id="path3666"
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:1.59999979;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
sodipodi:nodetypes="sssccccss"
inkscape:connector-curvature="0" />
<path
d="m 617.65007,335.26648 c 5.53002,1.41851 18.51389,1.41851 24.29359,0 0.80721,-0.19812 1.43306,0.67186 1.50029,1.50029 l 1.99751,13.05863 -1.50028,0 -28.28862,0 -1.50029,0 c 0.66584,-4.35288 1.33167,-8.70575 1.99752,-13.05863 0.12564,-0.82161 0.69518,-1.70681 1.50028,-1.50029 z"
id="path3668"
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:1.59999979;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
sodipodi:nodetypes="sssccccss"
inkscape:connector-curvature="0" />
<path
d="m 651.6079,335.26648 c 5.53,1.41851 18.51387,1.41851 24.29357,0 0.80721,-0.19812 1.43308,0.67186 1.50029,1.50029 l 1.99753,13.05863 -1.5003,0 -28.28862,0 -1.50029,0 c 0.66583,-4.35288 1.33168,-8.70575 1.99752,-13.05863 0.12564,-0.82161 0.69518,-1.70681 1.5003,-1.50029 z"
id="path3670"
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:1.59999979;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
sodipodi:nodetypes="sssccccss"
inkscape:connector-curvature="0" />
<path
d="m 685.56571,335.26648 c 5.53,1.41851 18.51387,1.41851 24.29357,0 0.80721,-0.19812 1.43308,0.67186 1.50029,1.50029 l 1.99753,13.05863 -1.5003,0 -28.28862,0 -1.50029,0 c 0.66583,-4.35288 1.33168,-8.70575 1.99752,-13.05863 0.12564,-0.82161 0.6952,-1.70681 1.5003,-1.50029 z"
id="path3672"
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:1.59999979;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
sodipodi:nodetypes="sssccccss"
inkscape:connector-curvature="0" />
<path
d="m 719.1097,335.26648 c 5.53002,1.41851 18.51389,1.41851 24.29359,0 0.80721,-0.19812 1.43306,0.67186 1.50029,1.50029 l 1.99751,13.05863 -1.50028,0 -28.28862,0 -1.50029,0 c 0.66583,-4.35288 1.33167,-8.70575 1.99752,-13.05863 0.12564,-0.82161 0.69518,-1.70681 1.50028,-1.50029 z"
id="path3674"
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:1.60000002;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
sodipodi:nodetypes="sssccccss"
inkscape:connector-curvature="0" />
<path
d="m 525.98066,345.0567 c 5.53002,1.4185 18.51389,1.4185 24.29359,0 0.80721,-0.19812 1.43306,0.67185 1.50029,1.50029 l 1.99751,13.05863 -1.50028,0 -28.28862,0 -1.50029,0 c 0.66583,-4.35288 1.33167,-8.70576 1.99752,-13.05863 0.12564,-0.82162 0.69518,-1.70681 1.50028,-1.50029 z"
id="path3676"
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:1.59999979;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
sodipodi:nodetypes="sssccccss"
inkscape:connector-curvature="0" />
<path
d="m 559.93849,345.0567 c 5.53,1.4185 18.51387,1.4185 24.29357,0 0.80721,-0.19812 1.43308,0.67185 1.50029,1.50029 l 1.99753,13.05863 -1.5003,0 -28.28862,0 -1.50029,0 c 0.66583,-4.35288 1.33168,-8.70576 1.99751,-13.05863 0.12565,-0.82162 0.69519,-1.70681 1.50031,-1.50029 z"
id="path3678"
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:1.59999979;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
sodipodi:nodetypes="sssccccss"
inkscape:connector-curvature="0" />
<path
d="m 593.8963,345.0567 c 5.53,1.4185 18.51387,1.4185 24.29357,0 0.80721,-0.19812 1.43308,0.67185 1.50029,1.50029 l 1.99753,13.05863 -1.5003,0 -28.28862,0 -1.50029,0 c 0.66583,-4.35288 1.33168,-8.70576 1.99751,-13.05863 0.12565,-0.82162 0.69521,-1.70681 1.50031,-1.50029 z"
id="path3680"
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:1.59999979;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
sodipodi:nodetypes="sssccccss"
inkscape:connector-curvature="0" />
<path
d="m 627.44029,345.0567 c 5.53002,1.4185 18.51389,1.4185 24.29359,0 0.80721,-0.19812 1.43306,0.67185 1.50029,1.50029 l 1.99751,13.05863 -1.50028,0 -28.28862,0 -1.50029,0 c 0.66583,-4.35288 1.33167,-8.70576 1.99751,-13.05863 0.12565,-0.82162 0.69519,-1.70681 1.50029,-1.50029 z"
id="path3682"
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:1.59999979;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
sodipodi:nodetypes="sssccccss"
inkscape:connector-curvature="0" />
<path
d="m 661.39812,345.0567 c 5.53,1.4185 18.51387,1.4185 24.29357,0 0.80721,-0.19812 1.43308,0.67185 1.50029,1.50029 l 1.99753,13.05863 -1.5003,0 -28.28863,0 -1.50028,0 c 0.66583,-4.35288 1.33168,-8.70576 1.99751,-13.05863 0.12564,-0.82162 0.69519,-1.70681 1.50031,-1.50029 z"
id="path3684"
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:1.59999979;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
sodipodi:nodetypes="sssccccss"
inkscape:connector-curvature="0" />
<path
d="m 695.35593,345.0567 c 5.53,1.4185 18.51387,1.4185 24.29357,0 0.8072,-0.19812 1.43308,0.67185 1.50029,1.50029 l 1.99753,13.05863 -1.5003,0 -28.28863,0 -1.50028,0 c 0.66583,-4.35288 1.33168,-8.70576 1.99751,-13.05863 0.12564,-0.82162 0.69521,-1.70681 1.50031,-1.50029 z"
id="path3686"
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:1.59999979;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
sodipodi:nodetypes="sssccccss"
inkscape:connector-curvature="0" />
<path
d="m 728.89992,345.0567 c 5.53002,1.4185 18.51389,1.4185 24.29359,0 0.8072,-0.19812 1.43306,0.67185 1.50029,1.50029 l 1.99751,13.05863 -1.50028,0 -28.28863,0 -1.50028,0 c 0.66583,-4.35288 1.33166,-8.70576 1.99751,-13.05863 0.12564,-0.82162 0.69519,-1.70681 1.50029,-1.50029 z"
id="path3688"
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:1.60000002;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
sodipodi:nodetypes="sssccccss"
inkscape:connector-curvature="0" />
<g
id="g3713"
transform="matrix(1.359752,0,0,1.359752,418.09336,-671.08525)">
<path
d="m 95.250257,715.10933 0,1.09089 c -1.31e-4,0.0113 -5.02e-4,0.0227 0,0.0341 0.01222,0.27812 0.140266,0.55621 0.340902,0.74999 l 5.693061,5.76124 5.65897,-5.76124 c 0.20529,-0.20532 0.30681,-0.49473 0.30681,-0.78413 l 0,-1.09089 -1.09088,0 c -0.28941,0 -0.57881,0.10156 -0.78408,0.30681 l -4.09082,4.15901 -4.124913,-4.15901 c -0.212319,-0.22989 -0.511898,-0.33071 -0.818164,-0.30681 l -1.090886,0 z"
id="path3715"
style="color:#bebebe;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:'Andale Mono';-inkscape-font-specification:'Andale Mono';text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;text-anchor:start;display:inline;overflow:visible;visibility:visible;fill:#000100;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.78124988;marker:none;enable-background:new"
inkscape:connector-curvature="0" />
<rect
height="11.999745"
id="rect3717"
rx="0"
ry="0"
style="color:#bebebe;display:inline;overflow:visible;visibility:visible;fill:#000100;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:2;marker:none;enable-background:accumulate"
transform="scale(-1,1)"
width="2.1817718"
x="-102.34102"
y="708.92743" />
</g>
<g
id="g3740"
transform="matrix(1.359752,0,0,1.359752,492.12198,-661.29504)">
<path
d="m 95.250257,715.10933 0,1.09089 c -1.31e-4,0.0113 -5.02e-4,0.0227 0,0.0341 0.01222,0.27812 0.140266,0.55621 0.340902,0.74999 l 5.693061,5.76124 5.65897,-5.76124 c 0.20529,-0.20532 0.30681,-0.49473 0.30681,-0.78413 l 0,-1.09089 -1.09088,0 c -0.28941,0 -0.57881,0.10156 -0.78408,0.30681 l -4.09082,4.15901 -4.124913,-4.15901 c -0.212319,-0.22989 -0.511898,-0.33071 -0.818164,-0.30681 l -1.090886,0 z"
id="path3742"
style="color:#bebebe;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:'Andale Mono';-inkscape-font-specification:'Andale Mono';text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;text-anchor:start;display:inline;overflow:visible;visibility:visible;fill:#000100;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.78124988;marker:none;enable-background:new"
inkscape:connector-curvature="0" />
<rect
height="11.999745"
id="rect3744"
rx="0"
ry="0"
style="color:#bebebe;display:inline;overflow:visible;visibility:visible;fill:#000100;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:2;marker:none;enable-background:accumulate"
transform="scale(-1,1)"
width="2.1817718"
x="-102.34102"
y="708.92743" />
</g>
<g
id="g3746"
transform="matrix(1.359752,0,0,1.359752,593.58161,-661.29504)">
<path
d="m 95.250257,715.10933 0,1.09089 c -1.31e-4,0.0113 -5.02e-4,0.0227 0,0.0341 0.01222,0.27812 0.140266,0.55621 0.340902,0.74999 l 5.693061,5.76124 5.65897,-5.76124 c 0.20529,-0.20532 0.30681,-0.49473 0.30681,-0.78413 l 0,-1.09089 -1.09088,0 c -0.28941,0 -0.57881,0.10156 -0.78408,0.30681 l -4.09082,4.15901 -4.124913,-4.15901 c -0.212319,-0.22989 -0.511898,-0.33071 -0.818164,-0.30681 l -1.090886,0 z"
id="path3748"
style="color:#bebebe;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:'Andale Mono';-inkscape-font-specification:'Andale Mono';text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;text-anchor:start;display:inline;overflow:visible;visibility:visible;fill:#000100;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.78124988;marker:none;enable-background:new"
inkscape:connector-curvature="0" />
<rect
height="11.999745"
id="rect3750"
rx="0"
ry="0"
style="color:#bebebe;display:inline;overflow:visible;visibility:visible;fill:#000100;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:2;marker:none;enable-background:accumulate"
transform="scale(-1,1)"
width="2.1817718"
x="-102.34102"
y="708.92743" />
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 18 KiB

View File

@@ -2,6 +2,7 @@
<gresources>
<gresource prefix="/org/freedesktop/Piper">
<file>404.svg</file>
<file>enter-keyboard-shortcut.svg</file>
<file preprocess="xml-stripblanks">AboutDialog.ui</file>
<file preprocess="xml-stripblanks">ui/ButtonDialog.ui</file>

View File

@@ -41,77 +41,205 @@
<property name="can_focus">False</property>
<property name="transition_type">slide-left-right</property>
<child>
<object class="GtkBox">
<object class="GtkStack" id="stack_mapping">
<property name="can_focus">False</property>
<property name="vexpand">True</property>
<property name="orientation">vertical</property>
<property name="spacing">6</property>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Button- and key mappings allow you to assign to a physical mouse button another
logical mouse button or a sequence of keypresses. This way you may for example
make the right mouse button perform a left click, or execute the keystroke Ctrl+R with
a click on the right mouse button.</property>
<property name="justify">center</property>
<property name="wrap">True</property>
<property name="track_visited_links">False</property>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkBox">
<property name="visible">True</property>
<property name="can_focus">False</property>
<child>
<object class="GtkComboBoxText" id="combo_mapping">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="tooltip_text" translatable="yes">Assign another logical mouse button to this button</property>
<property name="halign">end</property>
<property name="has_entry">True</property>
<signal name="changed" handler="_on_mapping_changed" swapped="no"/>
<child internal-child="entry">
<object class="GtkEntry">
<property name="can_focus">False</property>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="pack_type">end</property>
<property name="position">0</property>
</packing>
</child>
<property name="vexpand">True</property>
<property name="orientation">vertical</property>
<property name="spacing">6</property>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">start</property>
<property name="label" translatable="yes">Assign a button mapping:</property>
<property name="label" translatable="yes">Button- and key mappings allow you to assign to a physical mouse button another
logical mouse button or a sequence of keypresses. This way you may for example
make the right mouse button perform a left click, or execute the keystroke Ctrl+R with
a click on the right mouse button.</property>
<property name="justify">center</property>
<property name="wrap">True</property>
<property name="track_visited_links">False</property>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkBox">
<property name="visible">True</property>
<property name="can_focus">False</property>
<child>
<object class="GtkComboBoxText" id="combo_mapping">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="tooltip_text" translatable="yes">Assign another logical mouse button to this button</property>
<property name="halign">end</property>
<property name="has_entry">True</property>
<signal name="changed" handler="_on_mapping_changed" swapped="no"/>
<child internal-child="entry">
<object class="GtkEntry">
<property name="can_focus">False</property>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="pack_type">end</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">start</property>
<property name="label" translatable="yes">Assign a button mapping:</property>
<property name="track_visited_links">False</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkBox">
<property name="visible">True</property>
<property name="can_focus">False</property>
<child>
<object class="GtkButton">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="tooltip_text" translatable="yes">Assign a keystroke to this button</property>
<property name="halign">end</property>
<property name="relief">none</property>
<signal name="clicked" handler="_on_capture_keystroke_clicked" swapped="no"/>
<child>
<object class="GtkShortcutLabel" id="label_keystroke">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">end</property>
<property name="hexpand">True</property>
<property name="disabled-text" translatable="yes">None</property>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="pack_type">end</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">start</property>
<property name="label" translatable="yes">Assign a key stroke:</property>
<property name="track_visited_links">False</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
<property name="name">overview</property>
</packing>
</child>
<child>
<placeholder/>
<object class="GtkBox">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="orientation">vertical</property>
<property name="spacing">18</property>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Enter a new key sequence for the mapping, starting with zero or more modifiers and finishing with one regular key.</property>
<property name="justify">center</property>
<property name="wrap">True</property>
<property name="wrap_mode">word-char</property>
<property name="width_chars">15</property>
<property name="max_width_chars">20</property>
<property name="track_visited_links">False</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkShortcutLabel" id="label_preview">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">center</property>
<property name="hexpand">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkImage">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="resource">/org/freedesktop/Piper/enter-keyboard-shortcut.svg</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Press Backspace to reset the key mapping.</property>
<property name="wrap">True</property>
<style>
<class name="dim-label"/>
</style>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">3</property>
</packing>
</child>
</object>
<packing>
<property name="name">capture</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>

View File

@@ -14,14 +14,17 @@
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
import sys
from gettext import gettext as _
from .gi_composites import GtkTemplate
from .ratbagd import RatbagdButton
from .keystroke import KeyStroke
import gi
gi.require_version("Gtk", "3.0")
from gi.repository import GObject, Gtk
from gi.repository import Gdk, GObject, Gtk
@GtkTemplate(ui="/org/freedesktop/Piper/ui/ButtonDialog.ui")
@@ -38,8 +41,27 @@ class ButtonDialog(Gtk.Dialog):
RatbagdButton.ACTION_TYPE_MACRO: "macro",
}
_MODIFIERS = [
Gdk.KEY_Shift_L,
Gdk.KEY_Shift_R,
Gdk.KEY_Shift_Lock,
Gdk.KEY_Hyper_L,
Gdk.KEY_Hyper_R,
Gdk.KEY_Meta_L,
Gdk.KEY_Meta_R,
Gdk.KEY_Control_L,
Gdk.KEY_Control_R,
Gdk.KEY_Super_L,
Gdk.KEY_Super_R,
Gdk.KEY_Alt_L,
Gdk.KEY_Alt_R,
]
stack = GtkTemplate.Child()
combo_mapping = GtkTemplate.Child()
stack_mapping = GtkTemplate.Child()
label_keystroke = GtkTemplate.Child()
label_preview = GtkTemplate.Child()
def __init__(self, ratbagd_button, buttons, *args, **kwargs):
"""Instantiates a new ButtonDialog.
@@ -49,9 +71,12 @@ class ButtonDialog(Gtk.Dialog):
"""
Gtk.Dialog.__init__(self, *args, **kwargs)
self.init_template()
self._grab_pointer = None
self._keystroke = KeyStroke()
self._button = ratbagd_button
self._action_type = self._button.action_type
self._button_mapping = ratbagd_button.mapping
self._key_mapping = ratbagd_button.key
self._init_mapping_page(buttons)
self._activate_current_page()
@@ -75,6 +100,14 @@ class ButtonDialog(Gtk.Dialog):
if self._button_mapping > 0 and button == self._button:
self.combo_mapping.set_active_id(key)
self._keystroke.connect("keystroke-set", self._on_keystroke_set)
self._keystroke.connect("keystroke-cleared", self._on_keystroke_set)
self._keystroke.bind_property("accelerator", self.label_keystroke, "accelerator")
self._keystroke.bind_property("accelerator", self.label_preview, "accelerator")
if self._button.type == RatbagdButton.ACTION_TYPE_KEY:
keys = self._button.key
self._keystroke.set_from_evdev(keys[0], keys[1:])
def _get_button_key_and_name(self, button):
if button.index in RatbagdButton.BUTTON_DESCRIPTION:
name = RatbagdButton.BUTTON_DESCRIPTION[button.index]
@@ -82,6 +115,95 @@ class ButtonDialog(Gtk.Dialog):
name = _("Button {} click").format(button.index)
return str(button.index + 1), name # Logical buttons are 1-indexed.
def _grab_seat(self):
# Grabs the keyboard seat. Returns True on success, False on failure.
# Gratefully copied from GNOME Control Center's keyboard panel.
window = self.get_window()
if window is None:
return False
display = window.get_display()
seats = display.list_seats()
if len(seats) == 0:
return False
device = seats[0].get_keyboard()
if device is None:
return False
if device.get_source == Gdk.InputSource.KEYBOARD:
pointer = device.get_associated_device()
if pointer is None:
return False
else:
pointer = device
status = pointer.get_seat().grab(window, Gdk.SeatCapabilities.KEYBOARD,
False, None, None, None, None)
if status != Gdk.GrabStatus.SUCCESS:
return False
self._grab_pointer = pointer
self.grab_add()
return True
def _release_grab(self):
# Releases a previously grabbed keyboard seat, if any.
if self._grab_pointer is None:
return
self._grab_pointer.get_seat().ungrab()
self._grab_pointer = None
self.grab_remove()
def do_key_press_event(self, event):
# Overrides Gtk.Widget's standard key press event callback, so we can
# capture the pressed buttons in capture mode. Gratefully copied from
# GNOME Control Center's keyboard panel.
# Don't process key events when we're not in capture mode.
if self.stack_mapping.get_visible_child_name() == "overview":
return Gtk.Widget.do_key_press_event(self, event)
# TODO: remove this workaround when libratbag removes its keycode
# contraints. When that happens, we just cache all keypresses in the
# order they arrive and set the keystroke upon Return.
# GdkEventKey.is_modified isn't exposed through PyGObject (see
# https://bugzilla.gnome.org/show_bug.cgi?id=752784), so we have to
# approximate its behaviour ourselves. This selection is from Gtk's
# default mod mask and should be fine for now for most use cases.
event.is_modifier = event.keyval in self._MODIFIERS
# We only want to bind keystrokes using the default modifiers, so that
# our workaround above and the one in KeyStroke._update_accelerator()
# work.
event.state &= Gtk.accelerator_get_default_mod_mask()
# Put shift back if it changed the case of the key, not otherwise.
keyval_lower = Gdk.keyval_to_lower(event.keyval)
if keyval_lower != event.keyval:
event.state |= Gdk.ModifierType.SHIFT_MASK
event.keyval = keyval_lower
# Normalize tab.
if event.keyval == Gdk.KEY_ISO_Left_Tab:
event.keyval = Gdk.KEY_Tab
# HACK: we don't want to use SysRq as a keybinding, but we do want
# Al+Print, so we avoid translating Alt+Print to SysRq.
if event.keyval == Gdk.KEY_Sys_Req and (event.state & Gdk.ModifierType.MOD1_MASK):
event.keyval = Gdk.KEY_Print
# Backspace clears the current keystroke.
if not event.is_modifier and event.state == 0 and event.keyval == Gdk.KEY_BackSpace:
self._keystroke.clear()
return Gdk.EVENT_STOP
# Anything else we process as a regular key event.
self._keystroke.process_event(event)
return Gdk.EVENT_STOP
def _on_keystroke_set(self, keystroke):
# A keystroke has been set or cleared; update accordingly.
self._action_type = RatbagdButton.ACTION_TYPE_KEY
self._key_mapping = self._keystroke.get_keys()
self.stack_mapping.set_visible_child_name("overview")
self._release_grab()
@GtkTemplate.Callback
def _on_mapping_changed(self, combo):
tree_iter = combo.get_active_iter()
@@ -91,6 +213,16 @@ class ButtonDialog(Gtk.Dialog):
mapping = int(model[tree_iter][1])
if mapping != self._button_mapping:
self._button_mapping = mapping
self._action_type = RatbagdButton.ACTION_TYPE_BUTTON
@GtkTemplate.Callback
def _on_capture_keystroke_clicked(self, button):
# Switches to the capture stack page and grabs the keyboard seat to
# capture all key presses.
self.stack_mapping.set_visible_child_name("capture")
if self._grab_seat() is not True:
print("Unable to grab keyboard, can't set keystroke", file=sys.stderr)
self.stack_mapping.set_visible_child_name("overview")
@GObject.Property
def action_type(self):
@@ -99,3 +231,7 @@ class ButtonDialog(Gtk.Dialog):
@GObject.Property
def button_mapping(self):
return self._button_mapping
@GObject.Property
def key_mapping(self):
return self._key_mapping

View File

@@ -70,6 +70,8 @@ class ButtonsPage(Gtk.Box):
if response == Gtk.ResponseType.APPLY:
if dialog.action_type == RatbagdButton.ACTION_TYPE_BUTTON:
ratbagd_button.mapping = dialog.button_mapping
elif dialog.action_type == RatbagdButton.ACTION_TYPE_KEY:
ratbagd_button.key = dialog.key_mapping
dialog.destroy()
def _find_active_profile(self):

172
piper/keystroke.py Normal file
View File

@@ -0,0 +1,172 @@
# Copyright (C) 2017 Jente Hidskes <hjdskes@gmail.com>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
import sys
import gi
gi.require_version("Gtk", "3.0")
from gi.repository import Gdk, GObject, Gtk
# TODO: this file needs to be checked for its Wayland support.
class KeyStroke(GObject.Object):
"""The KeyStroke object represents a keyboard shortcut as pressed by the
user in the key mapping's capture mode. Note that internally it uses
keycodes (and keyvalues) as defined by Gdk, not evdev. It does have the
ability to import from and export as evdev keycodes, as required by
libratbagd. See the get_keys() and set_from_evdev() methods."""
# Gdk uses an offset of 8 from the keycodes defined in linux/input.h as
# used by evdev.
_XORG_KEYCODE_OFFSET = 8
__gsignals__ = {
'keystroke-set': (GObject.SIGNAL_RUN_FIRST, None, ()),
'keystroke-cleared': (GObject.SIGNAL_RUN_FIRST, None, ()),
}
def __init__(self, **kwargs):
"""Intantiates a new KeyStroke."""
GObject.Object.__init__(self, **kwargs)
self._accelerator = ""
self._keycode = 0
self._keyval = 0
self._modifiers = []
@GObject.Property(type=str)
def accelerator(self):
"""The accelerator of the current keystroke, as generated by
Gtk.accelerator_name(keyval, mask)."""
return self._accelerator
def process_event(self, event):
"""Processes the given key event to update the current keystroke.
@param event The event to process, as Gdk.EventKey.
"""
if not self._check_key(event.hardware_keycode, event.keyval):
return
if event.is_modifier:
if (event.hardware_keycode, event.keyval) not in self._modifiers:
self._modifiers.append((event.hardware_keycode, event.keyval))
else:
self._keycode = event.hardware_keycode
self._keyval = event.keyval
# Since keystrokes may only contain a single regular keycode, we
# accept as soon as the user enters one. TODO: we should assign the
# modifiers and keycode+keyvalue to self._cur_* so that we can add
# cancel functionality to the button dialog on Escape.
self.emit("keystroke-set")
self._update_accelerator()
def clear(self):
"""Clears the current keystroke."""
self._keycode = 0
self._keyval = 0
self._modifiers = []
self._update_accelerator()
self.emit("keystroke-cleared")
def set_from_evdev(self, keycode, modifiers):
"""Converts the given evdev keycodes to their Gdk counterparts and sets
them as the current keystroke.
@param keycode The evdev keycode, as int.
@param modifiers A list of modifier keycodes, as [int].
"""
keycode += self._XORG_KEYCODE_OFFSET
keyval = self._keycode_to_keyval(keycode)
if not self._check_key(keycode, keyval):
return
self._keycode = keycode
self._keyval = keyval
for modifier in modifiers:
keycode = modifier + self._XORG_KEYCODE_OFFSET
keyval = self._keycode_to_keyval(keycode)
if not self._check_key(keycode, keyval):
continue
if (keycode, keyval) not in self._modifiers:
self._modifiers.append((keycode, keyval))
self._update_accelerator()
def _keycode_to_keyval(self, keycode):
# Attempts to retrieve a Gdk keyval belonging to a Gdk keycode. In case
# none can be found, 0 is returned. Note that there is some inaccuracy
# here in that the keycode can correspond to several keyvals (due to
# levels and groups) and that we return the keyval belonging to level
# and group 0, if any.
keymap = Gdk.Keymap.get_default()
ok, keys, keyvals = keymap.get_entries_for_keycode(keycode)
if ok:
for key, keyval in zip(keys, keyvals):
if key.level == 0 and key.group == 0:
return keyval
print("Could not translate keycode to keyval, using 0", file=sys.stderr)
# This default will be skipped when printing the accelerator, and won't
# pass self._check_key in which case it will be skipped altogether.
return 0
def get_keys(self):
"""Returns a list of keycodes with the first being the pressed key and
the rest modifiers, if any. These keycodes map to those as defined by
evdev as opposed to those defined by Gdk, thus the returned list is
directly usable by libratbag."""
if self._keycode > self._XORG_KEYCODE_OFFSET:
ret = [self._keycode - self._XORG_KEYCODE_OFFSET]
else:
# TODO: make ratbag reset the key mapping on an empty list or
# something like that and return that value here instead. Currently
# this returns an unmappable key.
ret = [self._keycode]
return ret + [keycode - self._XORG_KEYCODE_OFFSET for keycode, _ in self._modifiers]
def _check_key(self, keycode, keyval):
# Checks if the given keycode and keyval are valid through a simply
# range check.
if not self._XORG_KEYCODE_OFFSET <= keycode <= 255:
print("Keycode is not within the valid range.", file=sys.stderr)
return False
elif not Gdk.KEY_space <= keyval <= Gdk.KEY_AudioMicMute:
print("Keyval is not within the valid range.", file=sys.stderr)
return False
return True
def _update_accelerator(self):
# Updates the accelerator property, so that any GObject bindings to
# Gtk.ShortcutLabels' accelerator property are updated automatically.
mask = Gdk.ModifierType(0)
for (_, keyval) in self._modifiers:
# The following are GTK+'s default modifiers that are allowed
# through in ButtonDialog.on_key_press_event, hence we check for
# these modifiers only -- it is unlikely we will find others. If
# this needs to be made more accurate, we can iterate over the mask
# returned by Gtk.accelerator_get_default_mod_mask().
# The default mod mask depends on the Gdk backend in use, but will
# typically include Control, Shift, Alt, Super, Hyper and Meta.
if keyval == Gdk.KEY_Shift_L or keyval == Gdk.KEY_Shift_R or keyval == Gdk.KEY_Shift_Lock:
mask |= Gdk.ModifierType.SHIFT_MASK
elif keyval == Gdk.KEY_Hyper_L or keyval == Gdk.KEY_Hyper_R:
mask |= Gdk.ModifierType.HYPER_MASK
elif keyval == Gdk.KEY_Meta_L or keyval == Gdk.KEY_Meta_R:
mask |= Gdk.ModifierType.META_MASK
elif keyval == Gdk.KEY_Control_L or keyval == Gdk.KEY_Control_R:
mask |= Gdk.ModifierType.CONTROL_MASK
elif keyval == Gdk.KEY_Super_L or keyval == Gdk.KEY_Super_R:
mask |= Gdk.ModifierType.SUPER_MASK
elif keyval == Gdk.KEY_Alt_L or keyval == Gdk.KEY_Alt_R:
mask |= Gdk.ModifierType.MOD1_MASK
self._accelerator = Gtk.accelerator_name(self._keyval, mask)
self.notify("accelerator")