[PATCH libinput v3 3/3] touchpad: Add edge-scrolling support

Hans de Goede hdegoede at redhat.com
Mon Nov 24 03:16:06 PST 2014


Add edge-scrolling support for non multi-touch touchpads as well as for
users who prefer edge-scrolling (as long as they don't have a clickpad).

Note the percentage to use of the width / height as scroll-edge differs from
one manufacturer to the next, the various per model percentages were taken
from xf86-input-synaptics.

BugLink: https://bugs.freedesktop.org/show_bug.cgi?id=85635
Signed-off-by: Hans de Goede <hdegoede at redhat.com>
---
 doc/touchpad-edge-scrolling-state-machine.svg | 262 ++++++++++++++++++
 src/Makefile.am                               |   1 +
 src/evdev-mt-touchpad-edge-scroll.c           | 374 ++++++++++++++++++++++++++
 src/evdev-mt-touchpad.c                       |  95 ++++++-
 src/evdev-mt-touchpad.h                       |  46 ++++
 5 files changed, 767 insertions(+), 11 deletions(-)
 create mode 100644 doc/touchpad-edge-scrolling-state-machine.svg
 create mode 100644 src/evdev-mt-touchpad-edge-scroll.c

diff --git a/doc/touchpad-edge-scrolling-state-machine.svg b/doc/touchpad-edge-scrolling-state-machine.svg
new file mode 100644
index 0000000..7a82d88
--- /dev/null
+++ b/doc/touchpad-edge-scrolling-state-machine.svg
@@ -0,0 +1,262 @@
+<?xml version="1.0"?>
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="1490px" height="1399px" version="1.1">
+  <defs/>
+  <g transform="translate(0.5,0.5)">
+    <ellipse cx="261" cy="143" rx="63" ry="45.5" fill="#ccccff" stroke="#000000" stroke-width="2" pointer-events="none"/>
+    <g fill="#000000" font-family="Helvetica" text-anchor="middle" font-size="12px">
+      <text x="261" y="126">NONE</text>
+      <text x="261" y="140">on-entry:</text>
+      <text x="261" y="154">edge = none</text>
+      <text x="261" y="168">threshold = def</text>
+    </g>
+    <rect x="30" y="386" width="150" height="101" rx="6" ry="6" fill="#ccffcc" stroke="#000000" stroke-width="2" pointer-events="none"/>
+    <g fill="#000000" font-family="Helvetica" text-anchor="middle" font-size="12px">
+      <text x="105" y="419">EDGE_NEW</text>
+      <text x="105" y="433">on-entry:</text>
+      <text x="105" y="447">edge = get_edge()</text>
+      <text x="105" y="461">set_timer()</text>
+    </g>
+    <rect x="348" y="386" width="130" height="100" rx="6" ry="6" fill="#ccffcc" stroke="#000000" stroke-width="2" pointer-events="none"/>
+    <g fill="#000000" font-family="Helvetica" text-anchor="middle" font-size="12px">
+      <text x="413" y="419">AREA</text>
+      <text x="413" y="433">on-entry:</text>
+      <text x="413" y="447">edge = none</text>
+      <text x="413" y="461">set_pointer()</text>
+    </g>
+    <path d="M 237 7 C 239 4 243 2 246 2 L 275 2 C 278 2 282 4 284 7 L 301 30 C 301 31 301 33 301 34 L 284 57 C 282 60 278 62 275 62 L 246 62 C 243 62 239 60 237 57 L 220 34 C 220 33 220 31 220 30 L 237 7 Z" fill="#ffd966" stroke="#000000" stroke-width="2" stroke-miterlimit="10" pointer-events="none"/>
+    <g fill="#000000" font-family="Helvetica" text-anchor="middle" font-size="12px">
+      <text x="261" y="36">release</text>
+    </g>
+    <path d="M 237 222 C 239 219 243 217 246 217 L 276 217 C 279 217 283 219 285 222 L 303 245 C 303 246 303 248 303 249 L 285 272 C 283 275 279 277 276 277 L 246 277 C 243 277 239 275 237 272 L 219 249 C 219 248 219 246 219 245 L 237 222 Z" fill="#ffd966" stroke="#000000" stroke-width="2" stroke-miterlimit="10" pointer-events="none"/>
+    <g fill="#000000" font-family="Helvetica" text-anchor="middle" font-size="12px">
+      <text x="261" y="251">touch</text>
+    </g>
+    <path d="M 261 188 L 261 211" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
+    <path d="M 261 216 L 258 209 L 261 211 L 265 209 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
+    <path d="M 218 535 C 223 531 229 528 235 528 L 285 528 C 291 528 297 531 302 535 L 331 570 C 332 571 332 574 331 575 L 302 610 C 297 614 291 617 285 617 L 235 617 C 229 617 223 614 218 610 L 189 575 C 188 574 188 571 189 570 L 218 535 Z" fill="#ffd966" stroke="#000000" stroke-width="2" stroke-miterlimit="10" pointer-events="none"/>
+    <g fill="#000000" font-family="Helvetica" text-anchor="middle" font-size="12px">
+      <text x="260" y="563">touch,</text>
+      <text x="260" y="577">edge &= get_edge()</text>
+    </g>
+    <path d="M 220 526 L 158 487" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
+    <path d="M 224 529 L 217 528 L 220 526 L 220 522 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
+    <path d="M 105 617 L 105 650" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
+    <path d="M 105 656 L 101 649 L 105 650 L 108 649 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
+    <path d="M 478 436 L 498 436 Q 508 436 508 426 L 508 42 Q 508 32 498 32 L 307 32" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
+    <path d="M 302 32 L 309 29 L 307 32 L 309 36 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
+    <path d="M 261 62 L 261 71 Q 261 80 261 85 L 261 91" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
+    <path d="M 261 96 L 257 89 L 261 91 L 264 89 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
+    <ellipse cx="1133" cy="67" rx="11" ry="11" fill="#000000" stroke="#ff0000" pointer-events="none"/>
+    <path d="M 1133 82 L 1133 110 Q 1133 120 1133 130 L 1133 155" fill="none" stroke="#ff0000" stroke-miterlimit="10" pointer-events="none"/>
+    <path d="M 1129 147 L 1133 156 L 1138 147" fill="none" stroke="#ff0000" stroke-miterlimit="10" pointer-events="none"/>
+    <rect x="1083" y="12" width="100" height="40" fill="none" stroke="none" pointer-events="none"/>
+    <g fill="#000000" font-family="Helvetica" text-anchor="middle" font-size="12px">
+      <text x="1133" y="36">tp_edge_scroll_post_events()</text>
+    </g>
+    <path d="M 1133 212 L 1193 242 L 1133 272 L 1073 242 Z" fill="#ffffc0" stroke="#ff0000" stroke-miterlimit="10" pointer-events="none"/>
+    <g fill="#000000" font-family="Helvetica" text-anchor="middle" font-size="12px">
+      <text x="1133" y="246">dirty?</text>
+    </g>
+    <path d="M 1193 242 L 1278 242 Q 1288 242 1298 242 L 1456 242" fill="none" stroke="#ff0000" stroke-miterlimit="10" pointer-events="none"/>
+    <path d="M 1448 247 L 1457 242 L 1448 238" fill="none" stroke="#ff0000" stroke-miterlimit="10" pointer-events="none"/>
+    <g fill="#000000" font-family="Helvetica" font-size="11px">
+      <rect fill="#ffffff" stroke="none" x="1225" y="223" width="18" height="18" stroke-width="0"/>
+      <text x="1226" y="236">no</text>
+    </g>
+    <path d="M 1133 272 L 1133 285 Q 1133 295 1133 305 L 1133 316" fill="none" stroke="#ff0000" stroke-miterlimit="10" pointer-events="none"/>
+    <path d="M 1129 308 L 1133 317 L 1138 308" fill="none" stroke="#ff0000" stroke-miterlimit="10" pointer-events="none"/>
+    <g fill="#000000" font-family="Helvetica" font-size="11px">
+      <rect fill="#ffffff" stroke="none" x="1150" y="292" width="24" height="18" stroke-width="0"/>
+      <text x="1150" y="302">yes</text>
+    </g>
+    <ellipse cx="1473" cy="242" rx="11" ry="11" fill="#000000" stroke="#ff0000" pointer-events="none"/>
+    <ellipse cx="1473" cy="242" rx="15" ry="15" fill="transparent" stroke="#ff0000" pointer-events="none"/>
+    <rect x="1033" y="97" width="200" height="70" rx="28" ry="28" fill="#ffffc0" stroke="#ff0000" pointer-events="none"/>
+    <g fill="#000000" font-family="Helvetica" text-anchor="middle" font-size="12px">
+      <text x="1133" y="115">current = buttons.state & 0x01</text>
+      <text x="1133" y="129">old = buttons.old_state & 0x01</text>
+      <text x="1133" y="143">button = 0</text>
+      <text x="1133" y="157">is_top = 0</text>
+    </g>
+    <path d="M 1133 167 L 1133 180 Q 1133 190 1133 200 L 1133 210" fill="none" stroke="#ff0000" stroke-miterlimit="10" pointer-events="none"/>
+    <path d="M 1129 202 L 1133 211 L 1138 202" fill="none" stroke="#ff0000" stroke-miterlimit="10" pointer-events="none"/>
+    <rect x="1237" y="436" width="188" height="50" rx="20" ry="20" fill="#ffffc0" stroke="#ff0000" pointer-events="none"/>
+    <g fill="#000000" font-family="Helvetica" text-anchor="middle" font-size="12px">
+      <text x="1331" y="458">notify_axis(last_axis, 0.0)</text>
+      <text x="1331" y="472">last_axis = -1</text>
+    </g>
+    <path d="M 1134 516 L 1194 552 L 1134 587 L 1073 552 Z" fill="#ffffc0" stroke="#ff0000" stroke-miterlimit="10" pointer-events="none"/>
+    <g fill="#000000" font-family="Helvetica" text-anchor="middle" font-size="12px">
+      <text x="1134" y="556">edge == right</text>
+    </g>
+    <path d="M 1194 552 L 1268 552 Q 1278 552 1278 553 L 1278 554 Q 1278 554 1268 554 L 1250 554" fill="none" stroke="#ff0000" stroke-miterlimit="10" pointer-events="none"/>
+    <path d="M 1258 550 L 1249 554 L 1258 559" fill="none" stroke="#ff0000" stroke-miterlimit="10" pointer-events="none"/>
+    <g fill="#000000" font-family="Helvetica" font-size="11px">
+      <rect fill="#ffffff" stroke="none" x="1202" y="559" width="24" height="18" stroke-width="0"/>
+      <text x="1203" y="569">yes</text>
+    </g>
+    <rect x="1248" y="534" width="160" height="40" rx="16" ry="16" fill="#ffffc0" stroke="#ff0000" pointer-events="none"/>
+    <g fill="#000000" font-family="Helvetica" text-anchor="middle" font-size="12px">
+      <text x="1328" y="551">axis = scroll_vertical</text>
+      <text x="1328" y="565">delta = dy</text>
+    </g>
+    <path d="M 1408 554 L 1468 554 Q 1478 554 1478 564 L 1478 952 Q 1478 962 1468 962 L 1233 962" fill="none" stroke="#ff0000" stroke-miterlimit="10" pointer-events="none"/>
+    <path d="M 1241 958 L 1232 962 L 1241 967" fill="none" stroke="#ff0000" stroke-miterlimit="10" pointer-events="none"/>
+    <path d="M 1133 318 L 1196 352 L 1133 387 L 1071 352 Z" fill="#ffffc0" stroke="#ff0000" stroke-miterlimit="10" pointer-events="none"/>
+    <g fill="#000000" font-family="Helvetica" text-anchor="middle" font-size="12px">
+      <text x="1133" y="356">edge == none</text>
+    </g>
+    <path d="M 1134 386 L 1134 441 Q 1134 451 1134 461 L 1134 514" fill="none" stroke="#ff0000" stroke-miterlimit="10" pointer-events="none"/>
+    <path d="M 1129 506 L 1134 515 L 1138 506" fill="none" stroke="#ff0000" stroke-miterlimit="10" pointer-events="none"/>
+    <g fill="#000000" font-family="Helvetica" font-size="11px">
+      <rect fill="#ffffff" stroke="none" x="1135" y="449" width="18" height="18" stroke-width="0"/>
+      <text x="1136" y="458">no</text>
+    </g>
+    <path d="M 1133 657 L 1194 692 L 1133 727 L 1072 692 Z" fill="#ffffc0" stroke="#ff0000" stroke-miterlimit="10" pointer-events="none"/>
+    <g fill="#000000" font-family="Helvetica" text-anchor="middle" font-size="12px">
+      <text x="1133" y="696">edge == bottom</text>
+    </g>
+    <path d="M 1194 692 L 1208 692 Q 1218 692 1228 692 L 1249 692" fill="none" stroke="#ff0000" stroke-miterlimit="10" pointer-events="none"/>
+    <path d="M 1241 697 L 1250 692 L 1241 688" fill="none" stroke="#ff0000" stroke-miterlimit="10" pointer-events="none"/>
+    <g fill="#000000" font-family="Helvetica" font-size="11px">
+      <rect fill="#ffffff" stroke="none" x="1213" y="700" width="24" height="18" stroke-width="0"/>
+      <text x="1214" y="709">yes</text>
+    </g>
+    <path d="M 1134 587 L 1133 587 Q 1133 587 1133 597 L 1133 647 Q 1133 657 1133 656 L 1133 655" fill="none" stroke="#ff0000" stroke-miterlimit="10" pointer-events="none"/>
+    <path d="M 1129 647 L 1133 656 L 1138 647" fill="none" stroke="#ff0000" stroke-miterlimit="10" pointer-events="none"/>
+    <g fill="#000000" font-family="Helvetica" font-size="11px">
+      <rect fill="#ffffff" stroke="none" x="1135" y="607" width="18" height="18" stroke-width="0"/>
+      <text x="1135" y="621">no</text>
+    </g>
+    <rect x="1251" y="672" width="160" height="40" rx="16" ry="16" fill="#ffffc0" stroke="#ff0000" pointer-events="none"/>
+    <g fill="#000000" font-family="Helvetica" text-anchor="middle" font-size="12px">
+      <text x="1331" y="689">axis = scroll_horizontal</text>
+      <text x="1331" y="703">delta = dx</text>
+    </g>
+    <path d="M 1133 727 L 1133 727 Q 1134 727 1134 737 L 1134 807 Q 1134 817 1134 816 L 1134 815" fill="none" stroke="#ff0000" stroke-miterlimit="10" pointer-events="none"/>
+    <path d="M 1129 807 L 1134 816 L 1138 807" fill="none" stroke="#ff0000" stroke-miterlimit="10" pointer-events="none"/>
+    <g fill="#000000" font-family="Helvetica" font-size="11px">
+      <rect fill="#ffffff" stroke="none" x="1135" y="756" width="18" height="18" stroke-width="0"/>
+      <text x="1136" y="770">no</text>
+    </g>
+    <rect x="1036" y="917" width="195" height="90" rx="36" ry="36" fill="#ffffc0" stroke="#ff0000" pointer-events="none"/>
+    <g fill="#000000" font-family="Helvetica" text-anchor="middle" font-size="12px">
+      <text x="1134" y="966">get_delta()</text>
+    </g>
+    <path d="M 1134 1007 L 1134 1037 Q 1134 1047 1134 1057 L 1134 1070" fill="none" stroke="#ff0000" stroke-miterlimit="10" pointer-events="none"/>
+    <path d="M 1129 1062 L 1134 1071 L 1138 1062" fill="none" stroke="#ff0000" stroke-miterlimit="10" pointer-events="none"/>
+    <path d="M 1411 692 L 1468 692 Q 1478 692 1478 702 L 1478 952 Q 1478 962 1468 962 L 1233 962" fill="none" stroke="#ff0000" stroke-miterlimit="10" pointer-events="none"/>
+    <path d="M 1241 958 L 1232 962 L 1241 967" fill="none" stroke="#ff0000" stroke-miterlimit="10" pointer-events="none"/>
+    <rect x="1031" y="1197" width="205" height="90" rx="36" ry="36" fill="#ffffc0" stroke="#ff0000" pointer-events="none"/>
+    <g fill="#000000" font-family="Helvetica" text-anchor="middle" font-size="12px">
+      <text x="1134" y="1232">notify_axis(axis, delta)</text>
+      <text x="1134" y="1246">last_axis = axis</text>
+      <text x="1134" y="1260">emit(scroll_event_posted)</text>
+    </g>
+    <path d="M 1134 1072 L 1200 1107 L 1134 1142 L 1067 1107 Z" fill="#ffffc0" stroke="#ff0000" stroke-miterlimit="10" pointer-events="none"/>
+    <g fill="#000000" font-family="Helvetica" text-anchor="middle" font-size="12px">
+      <text x="1134" y="1111">delta < threshold</text>
+    </g>
+    <path d="M 1200 1107 L 1318 1107 Q 1328 1107 1338 1107 L 1456 1107" fill="none" stroke="#ff0000" stroke-miterlimit="10" pointer-events="none"/>
+    <path d="M 1448 1112 L 1457 1107 L 1448 1103" fill="none" stroke="#ff0000" stroke-miterlimit="10" pointer-events="none"/>
+    <g fill="#000000" font-family="Helvetica" font-size="11px">
+      <rect fill="#ffffff" stroke="none" x="1259" y="1118" width="24" height="18" stroke-width="0"/>
+      <text x="1260" y="1131">yes</text>
+    </g>
+    <path d="M 1134 1142 L 1134 1160 Q 1134 1170 1134 1180 L 1134 1195" fill="none" stroke="#ff0000" stroke-miterlimit="10" pointer-events="none"/>
+    <path d="M 1129 1187 L 1134 1196 L 1138 1187" fill="none" stroke="#ff0000" stroke-miterlimit="10" pointer-events="none"/>
+    <g fill="#000000" font-family="Helvetica" font-size="11px">
+      <rect fill="#ffffff" stroke="none" x="1135" y="1150" width="18" height="18" stroke-width="0"/>
+      <text x="1136" y="1159">no</text>
+    </g>
+    <ellipse cx="1473" cy="1107" rx="11" ry="11" fill="#000000" stroke="#ff0000" pointer-events="none"/>
+    <ellipse cx="1473" cy="1107" rx="15" ry="15" fill="transparent" stroke="#ff0000" pointer-events="none"/>
+    <ellipse cx="1473" cy="352" rx="11" ry="11" fill="#000000" stroke="#ff0000" pointer-events="none"/>
+    <ellipse cx="1473" cy="352" rx="15" ry="15" fill="transparent" stroke="#ff0000" pointer-events="none"/>
+    <path d="M 1331 318 L 1394 352 L 1331 387 L 1269 352 Z" fill="#ffffc0" stroke="#ff0000" stroke-miterlimit="10" pointer-events="none"/>
+    <g fill="#000000" font-family="Helvetica" text-anchor="middle" font-size="12px">
+      <text x="1331" y="356">last_axis != -1</text>
+    </g>
+    <rect x="30" y="657" width="150" height="101" rx="6" ry="6" fill="#ccffcc" stroke="#000000" stroke-width="2" pointer-events="none"/>
+    <g fill="#000000" font-family="Helvetica" text-anchor="middle" font-size="12px">
+      <text x="105" y="697">EDGE</text>
+      <text x="105" y="711">on-entry:</text>
+      <text x="105" y="725">threshold = 0.01</text>
+    </g>
+    <path d="M 30 707 L 18 707 Q 8 707 8 697 L 8 42 Q 8 32 18 32 L 214 32" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
+    <path d="M 219 32 L 212 36 L 214 32 L 212 29 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
+    <path d="M 30 436 L 18 436 Q 8 436 8 426 L 8 42 Q 8 32 18 32 L 214 32" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
+    <path d="M 219 32 L 212 36 L 214 32 L 212 29 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
+    <path d="M 64 535 C 68 531 74 528 80 528 L 130 528 C 136 528 142 531 146 535 L 175 570 C 176 571 176 574 175 575 L 146 610 C 142 614 136 617 130 617 L 80 617 C 74 617 68 614 64 610 L 35 575 C 34 574 34 571 35 570 L 64 535 Z" fill="#ffd966" stroke="#000000" stroke-width="2" stroke-miterlimit="10" pointer-events="none"/>
+    <g fill="#000000" font-family="Helvetica" text-anchor="middle" font-size="12px">
+      <text x="105" y="563">timeout ||</text>
+      <text x="105" y="577">scroll_event_posted</text>
+    </g>
+    <path d="M 105 487 L 105 522" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
+    <path d="M 105 527 L 102 520 L 105 522 L 109 520 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
+    <path d="M 260 528 L 261 472" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
+    <path d="M 261 467 L 264 474 L 261 472 L 257 474 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
+    <path d="M 1196 352 L 1222 352 Q 1232 352 1242 352 L 1266 352" fill="none" stroke="#ff0000" stroke-miterlimit="10" pointer-events="none"/>
+    <path d="M 1258 357 L 1267 352 L 1258 348" fill="none" stroke="#ff0000" stroke-miterlimit="10" pointer-events="none"/>
+    <g fill="#000000" font-family="Helvetica" font-size="11px">
+      <rect fill="#ffffff" stroke="none" x="1224" y="360" width="24" height="18" stroke-width="0"/>
+      <text x="1224" y="369">yes</text>
+    </g>
+    <path d="M 1331 387 L 1331 401 Q 1331 411 1331 421 L 1331 434" fill="none" stroke="#ff0000" stroke-miterlimit="10" pointer-events="none"/>
+    <path d="M 1327 426 L 1331 435 L 1336 426" fill="none" stroke="#ff0000" stroke-miterlimit="10" pointer-events="none"/>
+    <g fill="#000000" font-family="Helvetica" font-size="11px">
+      <rect fill="#ffffff" stroke="none" x="1333" y="394" width="24" height="18" stroke-width="0"/>
+      <text x="1333" y="403">yes</text>
+    </g>
+    <path d="M 1394 352 L 1408 352 Q 1418 352 1428 352 L 1456 352" fill="none" stroke="#ff0000" stroke-miterlimit="10" pointer-events="none"/>
+    <path d="M 1448 357 L 1457 352 L 1448 348" fill="none" stroke="#ff0000" stroke-miterlimit="10" pointer-events="none"/>
+    <g fill="#000000" font-family="Helvetica" font-size="11px">
+      <rect fill="#ffffff" stroke="none" x="1417" y="360" width="18" height="18" stroke-width="0"/>
+      <text x="1418" y="369">no</text>
+    </g>
+    <ellipse cx="1473" cy="461" rx="11" ry="11" fill="#000000" stroke="#ff0000" pointer-events="none"/>
+    <ellipse cx="1473" cy="461" rx="15" ry="15" fill="transparent" stroke="#ff0000" pointer-events="none"/>
+    <path d="M 1425 461 L 1438 461 Q 1448 461 1452 461 L 1456 461" fill="none" stroke="#ff0000" stroke-miterlimit="10" pointer-events="none"/>
+    <path d="M 1448 466 L 1457 461 L 1448 457" fill="none" stroke="#ff0000" stroke-miterlimit="10" pointer-events="none"/>
+    <ellipse cx="1134" cy="832" rx="11" ry="11" fill="#000000" stroke="#ff0000" pointer-events="none"/>
+    <ellipse cx="1134" cy="832" rx="15" ry="15" fill="transparent" stroke="#ff0000" pointer-events="none"/>
+    <ellipse cx="1134" cy="1382" rx="11" ry="11" fill="#000000" stroke="#ff0000" pointer-events="none"/>
+    <ellipse cx="1134" cy="1382" rx="15" ry="15" fill="transparent" stroke="#ff0000" pointer-events="none"/>
+    <path d="M 1134 1287 L 1134 1365" fill="none" stroke="#ff0000" stroke-miterlimit="10" pointer-events="none"/>
+    <path d="M 1130 1359 L 1134 1366 L 1137 1359" fill="none" stroke="#ff0000" stroke-miterlimit="10" pointer-events="none"/>
+    <path d="M 261 277 L 261 299" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
+    <path d="M 261 304 L 258 297 L 261 299 L 265 297 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
+    <path d="M 208 337 L 115 337 Q 105 337 105 347 L 105 376 Q 105 386 105 382 L 105 378" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
+    <path d="M 105 385 L 100 376 L 105 378 L 109 376 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
+    <g fill="#000000" font-family="Helvetica" font-size="11px">
+      <rect fill="#ffffff" stroke="none" x="174" y="345" width="24" height="18" stroke-width="0"/>
+      <text x="175" y="354">yes</text>
+    </g>
+    <path d="M 314 337 L 403 337 Q 413 337 413 347 L 413 376 Q 413 386 413 382 L 413 378" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
+    <path d="M 413 385 L 408 376 L 413 378 L 417 376 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
+    <g fill="#000000" font-family="Helvetica" font-size="11px">
+      <rect fill="#ffffff" stroke="none" x="350" y="345" width="18" height="18" stroke-width="0"/>
+      <text x="350" y="354">no</text>
+    </g>
+    <path d="M 261 305 L 314 337 L 261 369 L 208 337 Z" fill="#ffd966" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
+    <g fill="#000000" font-family="Helvetica" text-anchor="middle" font-size="12px">
+      <text x="261" y="341">get_edge()</text>
+    </g>
+    <path d="M 261 406 L 309 436 L 261 466 L 213 436 Z" fill="#ffd966" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
+    <g fill="#000000" font-family="Helvetica" text-anchor="middle" font-size="12px">
+      <text x="261" y="440">edge</text>
+    </g>
+    <path d="M 309 436 L 319 436 Q 328 436 334 436 L 340 436" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
+    <path d="M 347 436 L 338 441 L 340 436 L 338 432 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
+    <g fill="#000000" font-family="Helvetica" font-size="11px">
+      <rect fill="#ffffff" stroke="none" x="311" y="444" width="18" height="18" stroke-width="0"/>
+      <text x="311" y="453">no</text>
+    </g>
+    <path d="M 213 436 L 211 436 Q 208 436 198 436 L 188 436" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
+    <path d="M 181 436 L 190 432 L 188 436 L 190 441 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
+    <g fill="#000000" font-family="Helvetica" font-size="11px">
+      <rect fill="#ffffff" stroke="none" x="189" y="444" width="24" height="18" stroke-width="0"/>
+      <text x="190" y="453">yes</text>
+    </g>
+  </g>
+</svg>
diff --git a/src/Makefile.am b/src/Makefile.am
index 5cc52a6..027e08c 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -15,6 +15,7 @@ libinput_la_SOURCES =			\
 	evdev-mt-touchpad.h		\
 	evdev-mt-touchpad-tap.c		\
 	evdev-mt-touchpad-buttons.c	\
+	evdev-mt-touchpad-edge-scroll.c	\
 	filter.c			\
 	filter.h			\
 	filter-private.h		\
diff --git a/src/evdev-mt-touchpad-edge-scroll.c b/src/evdev-mt-touchpad-edge-scroll.c
new file mode 100644
index 0000000..1dca0ea
--- /dev/null
+++ b/src/evdev-mt-touchpad-edge-scroll.c
@@ -0,0 +1,374 @@
+/*
+ * Copyright © 2014 Red Hat, Inc.
+ *
+ * Permission to use, copy, modify, distribute, and sell this software and
+ * its documentation for any purpose is hereby granted without fee, provided
+ * that the above copyright notice appear in all copies and that both that
+ * copyright notice and this permission notice appear in supporting
+ * documentation, and that the name of the copyright holders not be used in
+ * advertising or publicity pertaining to distribution of the software
+ * without specific, written prior permission.  The copyright holders make
+ * no representations about the suitability of this software for any
+ * purpose.  It is provided "as is" without express or implied warranty.
+ *
+ * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS
+ * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
+ * FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY
+ * SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER
+ * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF
+ * CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
+ * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <errno.h>
+#include <limits.h>
+#include <math.h>
+#include <string.h>
+#include <unistd.h>
+#include "linux/input.h"
+
+#include "evdev-mt-touchpad.h"
+
+#define DEFAULT_SCROLL_LOCK_TIMEOUT 300 /* ms */
+/* Use a reasonably large threshold until locked into scrolling mode, to
+   avoid accidentally locking in scrolling mode when trying to use the entire
+   touchpad to move the pointer. The user can wait for the timeout to trigger
+   to do a small scroll. */
+/* In mm for touchpads with valid resolution, see tp_init_accel() */
+#define DEFAULT_SCROLL_THRESHOLD 10.0
+
+enum scroll_event {
+	SCROLL_EVENT_TOUCH,
+	SCROLL_EVENT_MOTION,
+	SCROLL_EVENT_RELEASE,
+	SCROLL_EVENT_TIMEOUT,
+	SCROLL_EVENT_POSTED,
+};
+
+static uint32_t
+tp_touch_get_edge(struct tp_dispatch *tp, struct tp_touch *touch)
+{
+	uint32_t edge = EDGE_NONE;
+
+	if (tp->scroll.method != LIBINPUT_CONFIG_SCROLL_EDGE)
+		return EDGE_NONE;
+
+	if (touch->x > tp->scroll.right_edge)
+		edge |= EDGE_RIGHT;
+
+	if (touch->y > tp->scroll.bottom_edge)
+		edge |= EDGE_BOTTOM;
+
+	return edge;
+}
+
+static void
+tp_edge_scroll_set_state(struct tp_dispatch *tp,
+			 struct tp_touch *t,
+			 enum tp_edge_scroll_touch_state state)
+{
+	libinput_timer_cancel(&t->scroll.timer);
+
+	t->scroll.state = state;
+
+	switch (state) {
+	case EDGE_SCROLL_TOUCH_STATE_NONE:
+		t->scroll.edge = EDGE_NONE;
+		t->scroll.threshold = DEFAULT_SCROLL_THRESHOLD;
+		break;
+	case EDGE_SCROLL_TOUCH_STATE_EDGE_NEW:
+		t->scroll.edge = tp_touch_get_edge(tp, t);
+		libinput_timer_set(&t->scroll.timer,
+				   t->millis + DEFAULT_SCROLL_LOCK_TIMEOUT);
+		break;
+	case EDGE_SCROLL_TOUCH_STATE_EDGE:
+		t->scroll.threshold = 0.01; /* Do not allow 0.0 events */
+		break;
+	case EDGE_SCROLL_TOUCH_STATE_AREA:
+		t->scroll.edge = EDGE_NONE;
+		tp_set_pointer(tp, t);
+		break;
+	}
+}
+
+static void
+tp_edge_scroll_handle_none(struct tp_dispatch *tp,
+			   struct tp_touch *t,
+			   enum scroll_event event)
+{
+	struct libinput *libinput = tp->device->base.seat->libinput;
+
+	switch (event) {
+	case SCROLL_EVENT_TOUCH:
+		if (tp_touch_get_edge(tp, t)) {
+			tp_edge_scroll_set_state(tp, t,
+					EDGE_SCROLL_TOUCH_STATE_EDGE_NEW);
+		} else {
+			tp_edge_scroll_set_state(tp, t,
+					EDGE_SCROLL_TOUCH_STATE_AREA);
+		}
+		break;
+	case SCROLL_EVENT_MOTION:
+	case SCROLL_EVENT_RELEASE:
+	case SCROLL_EVENT_TIMEOUT:
+	case SCROLL_EVENT_POSTED:
+		log_bug_libinput(libinput,
+				 "unexpect scroll event in none state\n");
+		break;
+	}
+}
+
+static void
+tp_edge_scroll_handle_edge_new(struct tp_dispatch *tp,
+			       struct tp_touch *t,
+			       enum scroll_event event)
+{
+	struct libinput *libinput = tp->device->base.seat->libinput;
+
+	switch (event) {
+	case SCROLL_EVENT_TOUCH:
+		log_bug_libinput(libinput,
+				 "unexpect scroll event in edge new state\n");
+		break;
+	case SCROLL_EVENT_MOTION:
+		t->scroll.edge &= tp_touch_get_edge(tp, t);
+		if (!t->scroll.edge)
+			tp_edge_scroll_set_state(tp, t,
+					EDGE_SCROLL_TOUCH_STATE_AREA);
+		break;
+	case SCROLL_EVENT_RELEASE:
+		tp_edge_scroll_set_state(tp, t, EDGE_SCROLL_TOUCH_STATE_NONE);
+		break;
+	case SCROLL_EVENT_TIMEOUT:
+	case SCROLL_EVENT_POSTED:
+		tp_edge_scroll_set_state(tp, t, EDGE_SCROLL_TOUCH_STATE_EDGE);
+		break;
+	}
+}
+
+static void
+tp_edge_scroll_handle_edge(struct tp_dispatch *tp,
+			   struct tp_touch *t,
+			   enum scroll_event event)
+{
+	struct libinput *libinput = tp->device->base.seat->libinput;
+
+	switch (event) {
+	case SCROLL_EVENT_TOUCH:
+	case SCROLL_EVENT_TIMEOUT:
+		log_bug_libinput(libinput,
+				 "unexpect scroll event in edge state\n");
+		break;
+	case SCROLL_EVENT_MOTION:
+		/* If started at the bottom right, decide in which dir to scroll */
+		if (t->scroll.edge == (EDGE_RIGHT | EDGE_BOTTOM)) {
+			t->scroll.edge &= tp_touch_get_edge(tp, t);
+			if (!t->scroll.edge)
+				tp_edge_scroll_set_state(tp, t,
+						EDGE_SCROLL_TOUCH_STATE_AREA);
+		}
+		break;
+	case SCROLL_EVENT_RELEASE:
+		tp_edge_scroll_set_state(tp, t, EDGE_SCROLL_TOUCH_STATE_NONE);
+		break;
+	case SCROLL_EVENT_POSTED:
+		break;
+	}
+}
+
+static void
+tp_edge_scroll_handle_area(struct tp_dispatch *tp,
+			   struct tp_touch *t,
+			   enum scroll_event event)
+{
+	struct libinput *libinput = tp->device->base.seat->libinput;
+
+	switch (event) {
+	case SCROLL_EVENT_TOUCH:
+	case SCROLL_EVENT_TIMEOUT:
+	case SCROLL_EVENT_POSTED:
+		log_bug_libinput(libinput,
+				 "unexpect scroll event in area state\n");
+		break;
+	case SCROLL_EVENT_MOTION:
+		break;
+	case SCROLL_EVENT_RELEASE:
+		tp_edge_scroll_set_state(tp, t, EDGE_SCROLL_TOUCH_STATE_NONE);
+		break;
+	}
+}
+
+static void
+tp_edge_scroll_handle_event(struct tp_dispatch *tp,
+			    struct tp_touch *t,
+			    enum scroll_event event)
+{
+	switch (t->scroll.state) {
+	case EDGE_SCROLL_TOUCH_STATE_NONE:
+		tp_edge_scroll_handle_none(tp, t, event);
+		break;
+	case EDGE_SCROLL_TOUCH_STATE_EDGE_NEW:
+		tp_edge_scroll_handle_edge_new(tp, t, event);
+		break;
+	case EDGE_SCROLL_TOUCH_STATE_EDGE:
+		tp_edge_scroll_handle_edge(tp, t, event);
+		break;
+	case EDGE_SCROLL_TOUCH_STATE_AREA:
+		tp_edge_scroll_handle_area(tp, t, event);
+		break;
+	}
+}
+
+static void
+tp_edge_scroll_handle_timeout(uint64_t now, void *data)
+{
+	struct tp_touch *t = data;
+
+	tp_edge_scroll_handle_event(t->tp, t, SCROLL_EVENT_TIMEOUT);
+}
+
+int
+tp_edge_scroll_init(struct tp_dispatch *tp, struct evdev_device *device)
+{
+	struct tp_touch *t;
+	int width, height;
+	int edge_width, edge_height;
+
+	width = device->abs.absinfo_x->maximum - device->abs.absinfo_x->minimum;
+	height = device->abs.absinfo_y->maximum - device->abs.absinfo_y->minimum;
+
+	switch (tp->model) {
+	case MODEL_ALPS:
+		edge_width = width * .15;
+		edge_height = height * .15;
+		break;
+	case MODEL_APPLETOUCH: /* unibody are all clickpads, so N/A */
+		edge_width = width * .085;
+		edge_height = height * .085;
+		break;
+	default:
+		/* For elantech and synaptics, note for lenovo #40 series,
+		 * e.g. the T440s min/max are the absolute edges, not the
+		 * recommended ones as usual with synaptics. But these are
+		 * clickpads, so N/A.
+		 */
+		edge_width = width * .04;
+		edge_height = height * .054;
+	}
+
+	tp->scroll.right_edge = device->abs.absinfo_x->maximum - edge_width;
+	tp->scroll.bottom_edge = device->abs.absinfo_y->maximum - edge_height;
+
+	tp_for_each_touch(tp, t) {
+		t->scroll.direction = -1;
+		t->scroll.threshold = DEFAULT_SCROLL_THRESHOLD;
+		libinput_timer_init(&t->scroll.timer,
+				    device->base.seat->libinput,
+				    tp_edge_scroll_handle_timeout, t);
+	}
+
+	return 0;
+}
+
+void
+tp_destroy_edge_scroll(struct tp_dispatch *tp)
+{
+	struct tp_touch *t;
+
+	tp_for_each_touch(tp, t)
+		libinput_timer_cancel(&t->scroll.timer);
+}
+
+void
+tp_edge_scroll_handle_state(struct tp_dispatch *tp, uint64_t time)
+{
+	struct tp_touch *t;
+
+	tp_for_each_touch(tp, t) {
+		if (!t->dirty)
+			continue;
+
+		switch (t->state) {
+		case TOUCH_NONE:
+			break;
+		case TOUCH_BEGIN:
+			tp_edge_scroll_handle_event(tp, t, SCROLL_EVENT_TOUCH);
+			break;
+		case TOUCH_UPDATE:
+			tp_edge_scroll_handle_event(tp, t, SCROLL_EVENT_MOTION);
+			break;
+		case TOUCH_END:
+			tp_edge_scroll_handle_event(tp, t, SCROLL_EVENT_RELEASE);
+			break;
+		}
+	}
+}
+
+int
+tp_edge_scroll_post_events(struct tp_dispatch *tp, uint64_t time)
+{
+	struct libinput_device *device = &tp->device->base;
+	struct tp_touch *t;
+	enum libinput_pointer_axis axis;
+	double dx, dy, *delta;
+
+	tp_for_each_touch(tp, t) {
+		if (!t->dirty)
+			continue;
+
+		switch (t->scroll.edge) {
+			case EDGE_NONE:
+				if (t->scroll.direction != -1) {
+					/* Send stop scroll event */
+					pointer_notify_axis(device, time,
+						t->scroll.direction, 0.0);
+					t->scroll.direction = -1;
+				}
+				continue;
+			case EDGE_RIGHT:
+				axis = LIBINPUT_POINTER_AXIS_SCROLL_VERTICAL;
+				delta = &dy;
+				break;
+			case EDGE_BOTTOM:
+				axis = LIBINPUT_POINTER_AXIS_SCROLL_HORIZONTAL;
+				delta = &dx;
+				break;
+			default: /* EDGE_RIGHT | EDGE_BOTTOM */
+				continue; /* Don't know direction yet, skip */
+		}
+
+		tp_get_delta(t, &dx, &dy);
+		tp_filter_motion(tp, &dx, &dy, time);
+
+		if (fabs(*delta) < t->scroll.threshold)
+			continue;
+
+		pointer_notify_axis(device, time, axis, *delta);
+		t->scroll.direction = axis;
+
+		tp_edge_scroll_handle_event(tp, t, SCROLL_EVENT_POSTED);
+	}
+
+	return 0; /* Edge touches are suppressed by edge_scroll_touch_active */
+}
+
+void
+tp_edge_scroll_stop_events(struct tp_dispatch *tp, uint64_t time)
+{
+	struct libinput_device *device = &tp->device->base;
+	struct tp_touch *t;
+
+	tp_for_each_touch(tp, t) {
+		if (t->scroll.direction != -1) {
+			pointer_notify_axis(device, time,
+					    t->scroll.direction, 0.0);
+			t->scroll.direction = -1;
+		}
+	}
+}
+
+int
+tp_edge_scroll_touch_active(struct tp_dispatch *tp, struct tp_touch *t)
+{
+	return t->scroll.state == EDGE_SCROLL_TOUCH_STATE_AREA;
+}
diff --git a/src/evdev-mt-touchpad.c b/src/evdev-mt-touchpad.c
index 6d4b583..8f76ddb 100644
--- a/src/evdev-mt-touchpad.c
+++ b/src/evdev-mt-touchpad.c
@@ -56,7 +56,7 @@ tp_motion_history_offset(struct tp_touch *t, int offset)
 	return &t->history.samples[offset_index];
 }
 
-static void
+void
 tp_filter_motion(struct tp_dispatch *tp,
 	         double *dx, double *dy, uint64_t time)
 {
@@ -339,7 +339,9 @@ tp_touch_active(struct tp_dispatch *tp, struct tp_touch *t)
 {
 	return (t->state == TOUCH_BEGIN || t->state == TOUCH_UPDATE) &&
 		!t->palm.is_palm &&
-		!t->pinned.is_pinned && tp_button_touch_active(tp, t);
+		!t->pinned.is_pinned &&
+		tp_button_touch_active(tp, t) &&
+		tp_edge_scroll_touch_active(tp, t);
 }
 
 void
@@ -430,15 +432,12 @@ tp_post_twofinger_scroll(struct tp_dispatch *tp, uint64_t time)
 }
 
 static int
-tp_post_scroll_events(struct tp_dispatch *tp, uint64_t time)
+tp_twofinger_scroll_post_events(struct tp_dispatch *tp, uint64_t time)
 {
 	struct tp_touch *t;
 	int nfingers_down = 0;
 
-	if (tp->scroll.method != LIBINPUT_CONFIG_SCROLL_2FG)
-		return 0;
-
-	/* No scrolling during tap-n-drag */
+	/* No 2fg scrolling during tap-n-drag */
 	if (tp_tap_dragging(tp))
 		return 0;
 
@@ -458,6 +457,60 @@ tp_post_scroll_events(struct tp_dispatch *tp, uint64_t time)
 }
 
 static void
+tp_scroll_handle_state(struct tp_dispatch *tp, uint64_t time)
+{
+	/* Note this must be always called, so that it knows the state of
+	 * touches when the scroll-mode changes.
+	 */
+	tp_edge_scroll_handle_state(tp, time);
+}
+
+static int
+tp_post_scroll_events(struct tp_dispatch *tp, uint64_t time)
+{
+	struct libinput *libinput = tp->device->base.seat->libinput;
+
+	switch (tp->scroll.method) {
+	case LIBINPUT_CONFIG_SCROLL_NO_SCROLL:
+		break;
+	case LIBINPUT_CONFIG_SCROLL_2FG:
+		return tp_twofinger_scroll_post_events(tp, time);
+	case LIBINPUT_CONFIG_SCROLL_EDGE:
+		return tp_edge_scroll_post_events(tp, time);
+	case LIBINPUT_CONFIG_SCROLL_ON_BUTTON_DOWN:
+		log_bug_libinput(libinput, "Unexpected scroll mode\n");
+		break;
+	}
+	return 0;
+}
+
+static void
+tp_stop_scroll_events(struct tp_dispatch *tp, uint64_t time)
+{
+	struct libinput *libinput = tp->device->base.seat->libinput;
+
+	switch (tp->scroll.method) {
+	case LIBINPUT_CONFIG_SCROLL_NO_SCROLL:
+		break;
+	case LIBINPUT_CONFIG_SCROLL_2FG:
+		evdev_stop_scroll(tp->device, time);
+		break;
+	case LIBINPUT_CONFIG_SCROLL_EDGE:
+		tp_edge_scroll_stop_events(tp, time);
+		break;
+	case LIBINPUT_CONFIG_SCROLL_ON_BUTTON_DOWN:
+		log_bug_libinput(libinput, "Unexpected scroll mode\n");
+		break;
+	}
+}
+
+static void
+tp_destroy_scroll(struct tp_dispatch *tp)
+{
+	tp_destroy_edge_scroll(tp);
+}
+
+static void
 tp_process_state(struct tp_dispatch *tp, uint64_t time)
 {
 	struct tp_touch *t;
@@ -490,6 +543,7 @@ tp_process_state(struct tp_dispatch *tp, uint64_t time)
 	}
 
 	tp_button_handle_state(tp, time);
+	tp_scroll_handle_state(tp, time);
 
 	/*
 	 * We have a physical button down event on a clickpad. To avoid
@@ -525,6 +579,7 @@ tp_post_process_state(struct tp_dispatch *tp, uint64_t time)
 	tp->queued = TOUCHPAD_EVENT_NONE;
 }
 
+
 static void
 tp_post_events(struct tp_dispatch *tp, uint64_t time)
 {
@@ -542,7 +597,7 @@ tp_post_events(struct tp_dispatch *tp, uint64_t time)
 	filter_motion |= tp_post_button_events(tp, time);
 
 	if (filter_motion || tp->sendevents.trackpoint_active) {
-		evdev_stop_scroll(tp->device, time);
+		tp_stop_scroll_events(tp, time);
 		return;
 	}
 
@@ -621,6 +676,7 @@ tp_destroy(struct evdev_dispatch *dispatch)
 	tp_destroy_tap(tp);
 	tp_destroy_buttons(tp);
 	tp_destroy_sendevents(tp);
+	tp_destroy_scroll(tp);
 
 	free(tp->touches);
 	free(tp);
@@ -912,6 +968,9 @@ tp_scroll_config_scroll_method_get_methods(struct libinput_device *device)
 	if (tp->ntouches >= 2)
 		methods |= LIBINPUT_CONFIG_SCROLL_2FG;
 
+	if (!tp->buttons.is_clickpad)
+		methods |= LIBINPUT_CONFIG_SCROLL_EDGE;
+
 	return methods;
 }
 
@@ -925,7 +984,7 @@ tp_scroll_config_scroll_method_set_method(struct libinput_device *device,
 	if (method == tp->scroll.method)
 		return LIBINPUT_CONFIG_STATUS_SUCCESS;
 
-	evdev_stop_scroll(evdev, libinput_now(device->seat->libinput));
+	tp_stop_scroll_events(tp, libinput_now(device->seat->libinput));
 	tp->scroll.method = method;
 
 	return LIBINPUT_CONFIG_STATUS_SUCCESS;
@@ -941,14 +1000,28 @@ tp_scroll_config_scroll_method_get_method(struct libinput_device *device)
 }
 
 static enum libinput_config_scroll_method
+tp_scroll_get_default_method(struct tp_dispatch *tp)
+{
+	if (tp->ntouches >= 2)
+		return LIBINPUT_CONFIG_SCROLL_2FG;
+	else
+		return LIBINPUT_CONFIG_SCROLL_EDGE;
+}
+
+static enum libinput_config_scroll_method
 tp_scroll_config_scroll_method_get_default_method(struct libinput_device *device)
 {
-	return LIBINPUT_CONFIG_SCROLL_2FG;
+	struct evdev_device *evdev = (struct evdev_device*)device;
+	struct tp_dispatch *tp = (struct tp_dispatch*)evdev->dispatch;
+
+	return tp_scroll_get_default_method(tp);
 }
 
 static int
 tp_init_scroll(struct tp_dispatch *tp, struct evdev_device *device)
 {
+	if (tp_edge_scroll_init(tp, device) != 0)
+		return -1;
 
 	evdev_init_natural_scroll(device);
 
@@ -956,7 +1029,7 @@ tp_init_scroll(struct tp_dispatch *tp, struct evdev_device *device)
 	tp->scroll.config_method.set_method = tp_scroll_config_scroll_method_set_method;
 	tp->scroll.config_method.get_method = tp_scroll_config_scroll_method_get_method;
 	tp->scroll.config_method.get_default_method = tp_scroll_config_scroll_method_get_default_method;
-	tp->scroll.method = tp_scroll_config_scroll_method_get_default_method(&tp->device->base);
+	tp->scroll.method = tp_scroll_get_default_method(tp);
 	tp->device->base.config.scroll_method = &tp->scroll.config_method;
 
 	/* In mm for touchpads with valid resolution, see tp_init_accel() */
diff --git a/src/evdev-mt-touchpad.h b/src/evdev-mt-touchpad.h
index 7f3ce49..b2603b4 100644
--- a/src/evdev-mt-touchpad.h
+++ b/src/evdev-mt-touchpad.h
@@ -103,6 +103,20 @@ enum tp_tap_touch_state {
 	TAP_TOUCH_STATE_DEAD,		/**< exceeded motion/timeout */
 };
 
+/* For edge scrolling, so we only care about right and bottom */
+enum tp_edge {
+	EDGE_NONE = 0,
+	EDGE_RIGHT = (1 << 0),
+	EDGE_BOTTOM = (1 << 1),
+};
+
+enum tp_edge_scroll_touch_state {
+	EDGE_SCROLL_TOUCH_STATE_NONE,
+	EDGE_SCROLL_TOUCH_STATE_EDGE_NEW,
+	EDGE_SCROLL_TOUCH_STATE_EDGE,
+	EDGE_SCROLL_TOUCH_STATE_AREA,
+};
+
 struct tp_motion {
 	int32_t x;
 	int32_t y;
@@ -151,6 +165,14 @@ struct tp_touch {
 	} tap;
 
 	struct {
+		enum tp_edge_scroll_touch_state state;
+		uint32_t edge;
+		int direction;
+		double threshold;
+		struct libinput_timer timer;
+	} scroll;
+
+	struct {
 		bool is_palm;
 		int32_t x, y;  /* first coordinates if is_palm == true */
 		uint32_t time; /* first timestamp if is_palm == true */
@@ -214,6 +236,8 @@ struct tp_dispatch {
 	struct {
 		struct libinput_device_config_scroll_method config_method;
 		enum libinput_config_scroll_method method;
+		int32_t right_edge;
+		int32_t bottom_edge;
 	} scroll;
 
 	enum touchpad_event queued;
@@ -250,6 +274,10 @@ tp_get_delta(struct tp_touch *t, double *dx, double *dy);
 void
 tp_set_pointer(struct tp_dispatch *tp, struct tp_touch *t);
 
+void
+tp_filter_motion(struct tp_dispatch *tp,
+	         double *dx, double *dy, uint64_t time);
+
 int
 tp_tap_handle_state(struct tp_dispatch *tp, uint64_t time);
 
@@ -304,4 +332,22 @@ tp_tap_resume(struct tp_dispatch *tp, uint64_t time);
 bool
 tp_tap_dragging(struct tp_dispatch *tp);
 
+int
+tp_edge_scroll_init(struct tp_dispatch *tp, struct evdev_device *device);
+
+void
+tp_destroy_edge_scroll(struct tp_dispatch *tp);
+
+void
+tp_edge_scroll_handle_state(struct tp_dispatch *tp, uint64_t time);
+
+int
+tp_edge_scroll_post_events(struct tp_dispatch *tp, uint64_t time);
+
+void
+tp_edge_scroll_stop_events(struct tp_dispatch *tp, uint64_t time);
+
+int
+tp_edge_scroll_touch_active(struct tp_dispatch *tp, struct tp_touch *t);
+
 #endif
-- 
2.1.0



More information about the wayland-devel mailing list