summaryrefslogtreecommitdiff
path: root/lisp/touch-screen.el
blob: c8de1d8ee314feb304d45148e6741a97963a405d (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530
1531
1532
1533
1534
1535
1536
1537
1538
1539
1540
1541
1542
1543
1544
1545
1546
1547
1548
1549
1550
1551
1552
1553
1554
1555
1556
1557
1558
1559
1560
1561
1562
1563
1564
1565
1566
1567
1568
1569
1570
1571
1572
1573
1574
1575
1576
1577
1578
1579
1580
1581
1582
1583
1584
1585
1586
1587
1588
1589
1590
1591
1592
1593
1594
1595
1596
1597
1598
1599
1600
1601
1602
1603
1604
1605
1606
1607
1608
1609
1610
1611
1612
1613
1614
1615
1616
1617
1618
1619
1620
1621
1622
1623
1624
1625
1626
1627
1628
1629
1630
1631
1632
1633
1634
1635
1636
1637
1638
1639
1640
1641
1642
1643
1644
1645
1646
1647
1648
1649
1650
1651
1652
1653
1654
1655
1656
1657
1658
1659
1660
1661
1662
1663
1664
1665
1666
1667
1668
1669
1670
1671
1672
1673
1674
1675
1676
1677
1678
1679
1680
1681
1682
1683
1684
1685
1686
1687
1688
1689
1690
1691
1692
1693
1694
1695
1696
1697
1698
1699
1700
1701
1702
1703
1704
1705
1706
1707
1708
1709
1710
1711
1712
1713
1714
1715
1716
1717
1718
1719
1720
1721
1722
1723
1724
1725
1726
1727
1728
1729
1730
1731
1732
1733
1734
1735
1736
1737
1738
1739
1740
1741
1742
1743
1744
1745
1746
1747
1748
1749
1750
1751
1752
1753
1754
1755
1756
1757
1758
1759
1760
1761
1762
1763
1764
1765
1766
1767
1768
1769
1770
1771
1772
1773
1774
1775
1776
1777
1778
1779
1780
1781
1782
1783
1784
1785
1786
1787
1788
1789
1790
1791
1792
1793
1794
1795
1796
1797
1798
1799
1800
1801
1802
1803
1804
1805
1806
1807
1808
1809
1810
1811
1812
1813
1814
1815
1816
1817
1818
1819
1820
1821
1822
1823
1824
1825
1826
1827
1828
1829
1830
1831
1832
1833
1834
1835
1836
1837
1838
1839
1840
1841
1842
1843
1844
1845
1846
1847
1848
1849
1850
1851
1852
1853
1854
1855
1856
1857
1858
1859
1860
1861
1862
1863
1864
1865
1866
1867
1868
1869
1870
1871
1872
1873
1874
1875
1876
1877
1878
1879
1880
1881
1882
1883
1884
1885
1886
1887
1888
1889
1890
1891
1892
1893
1894
1895
1896
1897
1898
1899
1900
1901
1902
1903
1904
1905
1906
1907
1908
1909
1910
1911
1912
1913
1914
1915
1916
1917
1918
1919
1920
1921
1922
1923
1924
1925
1926
1927
1928
1929
1930
1931
1932
1933
1934
1935
1936
1937
1938
1939
1940
1941
1942
1943
1944
1945
1946
1947
1948
1949
1950
1951
1952
1953
1954
1955
1956
1957
1958
1959
1960
1961
1962
1963
1964
1965
1966
1967
1968
1969
1970
1971
1972
1973
1974
1975
1976
1977
1978
1979
1980
1981
1982
1983
1984
1985
1986
1987
1988
1989
1990
1991
1992
1993
1994
1995
1996
1997
1998
1999
2000
2001
2002
2003
2004
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
2027
2028
2029
2030
2031
2032
2033
2034
2035
2036
2037
2038
2039
2040
2041
2042
2043
;;; touch-screen.el --- touch screen support for X and Android  -*- lexical-binding: t; -*-

;; Copyright (C) 2023-2024 Free Software Foundation, Inc.

;; Maintainer: emacs-devel@gnu.org
;; Package: emacs

;; This file is part of GNU Emacs.

;; GNU Emacs 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 3 of the License, or
;; (at your option) any later version.

;; GNU Emacs 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 GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.

;;; Commentary:

;; This file provides code to recognize simple touch screen gestures.
;; It is used on X and Android, currently the only systems where Emacs
;; supports touch input.
;;
;; See (elisp)Touchscreen Events for a description of the details of
;; touch events.

;;; Code:

(defvar touch-screen-current-tool nil
  "The touch point currently being tracked, or nil.
If non-nil, this is a list of ten elements: the ID of the touch
point being tracked, the window where the touch began, a cons
holding the last registered position of the touch point, relative
to that window, a field used to store data while tracking the
touch point, the initial position of the touchpoint, another four
fields to used store data while tracking the touch point, and the
last known position of the touch point.

See `touch-screen-handle-point-update' and
`touch-screen-handle-point-up' for the meanings of the fourth
element.

The third and last elements differ in that the former is not
modified until after a gesture is recognized in reaction to an
update, whereas the latter is updated upon each apposite
`touchscreen-update' event.")

(defvar touch-screen-aux-tool nil
  "The ancillary tool being tracked, or nil.
If non-nil, this is a vector of ten elements: the ID of the
touch point being tracked, the window where the touch began, a
cons holding the initial position of the touch point, and the
last known position of the touch point, all in the same format as
in `touch-screen-current-tool', the distance in pixels between
the current tool and the aforementioned initial position, the
center of the line formed between those two points, the ratio
between the present distance between both tools and the aforesaid
initial distance when a pinch gesture was last sent, and three
elements into which commands can save data particular to a tool.

The ancillary tool is a second tool whose movement is interpreted
in unison with that of the current tool to recognize gestures
comprising the motion of both such as \"pinch\" gestures, in
which the text scale is adjusted in proportion to the distance
between both tools.")

(defvar touch-screen-set-point-commands '(mouse-set-point)
  "List of commands known to set the point.
This is used to determine whether or not to display the on-screen
keyboard after a mouse command is executed in response to a
`touchscreen-end' event.")

(defvar touch-screen-current-timer nil
  "Timer used to track long-presses.
This is always cleared upon any significant state change.")

(defvar touch-screen-translate-prompt nil
  "Prompt given to the touch screen translation function.
If non-nil, the touch screen key event translation machinery
is being called from `read-sequence' or some similar function.")

(defgroup touch-screen nil
  "Interact with Emacs from touch screen devices."
  :group 'mouse
  :version "30.1")

(defcustom touch-screen-display-keyboard nil
  "If non-nil, always display the on screen keyboard.
A buffer local value means to always display the on screen
keyboard when the buffer is selected."
  :type 'boolean
  :group 'touch-screen
  :version "30.1")

(defcustom touch-screen-delay 0.7
  "Delay in seconds before Emacs considers a touch to be a long-press."
  :type 'number
  :group 'touch-screen
  :version "30.1")

(defcustom touch-screen-precision-scroll nil
  "Whether or not to use precision scrolling for touch screens.
See `pixel-scroll-precision-mode' for more details."
  :type 'boolean
  :group 'touch-screen
  :version "30.1")

(defcustom touch-screen-word-select nil
  "Whether or not to select whole words while dragging to select.
If non-nil, long-press events (see `touch-screen-delay') followed
by dragging will try to select entire words."
  :type 'boolean
  :group 'touch-screen
  :version "30.1")

(defcustom touch-screen-extend-selection nil
  "If non-nil, restart drag-to-select upon a tap on point or mark.
When enabled, tapping on the character containing the point or
mark will resume dragging where it left off while the region is
active."
  :type 'boolean
  :group 'touch-screen
  :version "30.1")

(defcustom touch-screen-preview-select nil
  "If non-nil, display a preview while selecting text.
When enabled, a preview of the visible line within the window
will be displayed in the echo area while dragging combined with
an indication of the position of point within that line."
  :type 'boolean
  :group 'touch-screen
  :version "30.1")

(defcustom touch-screen-enable-hscroll t
  "If non-nil, hscroll can be changed from the touch screen.
When enabled, tapping on a window and dragging your finger left
or right will scroll that window horizontally."
  :type 'boolean
  :group 'touch-screen
  :version "30.1")

(defvar-local touch-screen-word-select-bounds nil
  "The start and end positions of the word last selected.
Normally a cons of those two positions or nil if no word was
selected.")

(defvar-local touch-screen-word-select-initial-word nil
  "The start and end positions of the first word to be selected.
Used in an attempt to keep this word selected during later
dragging.")



;;; Scroll gesture.

(defun touch-screen-relative-xy (posn window)
  "Return the coordinates of POSN, a mouse position list.
However, return the coordinates relative to WINDOW.

If (posn-window posn) is the same as window, simply return the
coordinates in POSN.  Otherwise, convert them to the frame, and
then back again.

If WINDOW is the symbol `frame', simply convert the coordinates
to the frame that they belong in."
  (if (or (eq (posn-window posn) window)
          (and (eq window 'frame)
               (framep (posn-window posn))))
      (posn-x-y posn)
    (let ((xy (posn-x-y posn))
          (edges (and (windowp window)
                      (window-inside-pixel-edges window))))
      ;; Make the X and Y positions frame relative.
      (when (windowp (posn-window posn))
        (let ((edges (window-inside-pixel-edges
                      (posn-window posn))))
          (setq xy (cons (+ (car xy) (car edges))
                         (+ (cdr xy) (cadr edges))))))
      (if (eq window 'frame)
          xy
        ;; Make the X and Y positions window relative again.
        (cons (- (car xy) (car edges))
              (- (cdr xy) (cadr edges)))))))

(defun touch-screen-handle-scroll (dx dy)
  "Scroll the display assuming that a touch point has moved by DX and DY.
Perform vertical scrolling by DY, using `pixel-scroll-precision'
if `touch-screen-precision-scroll' is enabled.  Next, perform
horizontal scrolling according to the movement in DX."
  ;; Perform vertical scrolling first.  Do not ding at buffer limits.
  ;; Show a message instead.
  (condition-case nil
      (if touch-screen-precision-scroll
          (progn
            (if (> dy 0)
                (pixel-scroll-precision-scroll-down-page dy)
              (pixel-scroll-precision-scroll-up-page (- dy)))
            ;; Now set `lines-vscrolled' to an value that will result
            ;; in hscroll being disabled if dy looks as if a
            ;; significant amount of scrolling is about to take
            ;; Otherwise, horizontal scrolling may then interfere with
            ;; precision scrolling.
            (when (> (abs dy) 10)
              (setcar (nthcdr 7 touch-screen-current-tool) 10)))
        ;; Start conventional scrolling.  First, determine the
        ;; direction in which the scrolling is taking place.  Load the
        ;; accumulator value.
        (let ((accumulator (or (nth 5 touch-screen-current-tool) 0))
              (window (cadr touch-screen-current-tool))
              (lines-vscrolled (or (nth 7 touch-screen-current-tool) 0)))
          (setq accumulator (+ accumulator dy)) ; Add dy.
          ;; Figure out how much it has scrolled and how much remains
          ;; on the top or bottom of the window.
          (while (catch 'again
                   (let* ((line-height (window-default-line-height window)))
                     (if (and (< accumulator 0)
                              (>= (- accumulator) line-height))
                         (progn
                           (setq accumulator (+ accumulator line-height))
                           (scroll-down 1)
                           (setq lines-vscrolled (1+ lines-vscrolled))
                           (when (not (zerop accumulator))
                             ;; If there is still an outstanding
                             ;; amount to scroll, do this again.
                             (throw 'again t)))
                       (when (and (> accumulator 0)
                                  (>= accumulator line-height))
                         (setq accumulator (- accumulator line-height))
                         (scroll-up 1)
                         (setq lines-vscrolled (1+ lines-vscrolled))
                         (when (not (zerop accumulator))
                           ;; If there is still an outstanding amount
                           ;; to scroll, do this again.
                           (throw 'again t)))))
                   ;; Scrolling is done.  Move the accumulator back to
                   ;; touch-screen-current-tool and break out of the
                   ;; loop.
                   (setcar (nthcdr 5 touch-screen-current-tool) accumulator)
                   (setcar (nthcdr 7 touch-screen-current-tool)
                           lines-vscrolled)
                   nil))))
    (beginning-of-buffer
     (message (error-message-string '(beginning-of-buffer))))
    (end-of-buffer
     (message (error-message-string '(end-of-buffer)))))

  ;; Perform horizontal scrolling by DX, as this does not signal at
  ;; the beginning of the buffer.
  (let ((accumulator (or (nth 6 touch-screen-current-tool) 0))
        (window (cadr touch-screen-current-tool))
        (lines-vscrolled (or (nth 7 touch-screen-current-tool) 0))
        (lines-hscrolled (or (nth 8 touch-screen-current-tool) 0)))
    (setq accumulator (+ accumulator dx)) ; Add dx.
    ;; Figure out how much it has scrolled and how much remains on the
    ;; left or right of the window.  If a line has already been
    ;; vscrolled but no hscrolling has happened, don't hscroll, as
    ;; otherwise it is too easy to hscroll by accident.
    (if (or (> lines-hscrolled 0)
            (< lines-vscrolled 1))
        (while (catch 'again
                 (let* ((column-width (frame-char-width (window-frame window))))
                   (if (and (< accumulator 0)
                            (>= (- accumulator) column-width))
                       (progn
                         (setq accumulator (+ accumulator column-width))
                         ;; Maintain both hscroll counters even when
                         ;; it's disabled to prevent unintentional or
                         ;; patently horizontal gestures from
                         ;; scrolling the window vertically.
                         (when touch-screen-enable-hscroll
                           (scroll-right 1))
                         (setq lines-hscrolled (1+ lines-hscrolled))
                         (when (not (zerop accumulator))
                           ;; If there is still an outstanding amount
                           ;; to scroll, do this again.
                           (throw 'again t)))
                     (when (and (> accumulator 0)
                                (>= accumulator column-width))
                       (setq accumulator (- accumulator column-width))
                       (when touch-screen-enable-hscroll
                         (scroll-left 1))
                       (setq lines-hscrolled (1+ lines-hscrolled))
                       (when (not (zerop accumulator))
                         ;; If there is still an outstanding amount to
                         ;; scroll, do this again.
                         (throw 'again t)))))
                 ;; Scrolling is done.  Move the accumulator back to
                 ;; touch-screen-current-tool and break out of the
                 ;; loop.
                 (setcar (nthcdr 6 touch-screen-current-tool) accumulator)
                 (setcar (nthcdr 8 touch-screen-current-tool) lines-hscrolled)
                 nil)))))

(defun touch-screen-scroll (event)
  "Scroll the window within EVENT, a `touchscreen-scroll' event.
If `touch-screen-precision-scroll', scroll the window vertically
by the number of pixels specified within that event.  Else,
scroll the window by one line for every
`window-default-line-height' pixels worth of movement.

If EVENT also specifies horizontal motion and no significant
amount of vertical scrolling has taken place, also scroll the
window horizontally in conjunction with the number of pixels in
the event."
  (interactive "e")
  (let ((window (nth 1 event))
        (dx (nth 2 event))
        (dy (nth 3 event)))
    (with-selected-window window
      (touch-screen-handle-scroll dx dy))))

(global-set-key [touchscreen-scroll] #'touch-screen-scroll)



;;; Drag-to-select gesture.

;;;###autoload
(defun touch-screen-hold (event)
  "Handle a long press EVENT.
Ding and select the window at EVENT, then activate the mark.  If
`touch-screen-word-select' is enabled, try to select the whole
word around EVENT; otherwise, set point to the location of EVENT."
  (interactive "e")
  (let* ((posn (cadr event))
         (point (posn-point posn))
         (window (posn-window posn)))
    (when (and point
               ;; Make sure WINDOW is not an inactive minibuffer
               ;; window.
               (or (not (eq window
                            (minibuffer-window
                             (window-frame window))))
                   (minibuffer-window-active-p window)))
      (beep)
      (select-window window)
      (if (or (not touch-screen-word-select)
              (when-let* ((char (char-after point))
                          (class (char-syntax char)))
                ;; Don't select words if point isn't inside a word
                ;; constituent or similar.
                (not (or (eq class ?w) (eq class ?_)))))
          (progn
            ;; Set the mark and activate it.
            (setq touch-screen-word-select-initial-word nil
                  touch-screen-word-select-bounds nil)
            (push-mark point)
            (goto-char point)
            (activate-mark))
        ;; Start word selection by trying to obtain the position
        ;; around point.
        (let ((word-start nil)
              (word-end nil))
          (unless (posn-object posn)
            ;; If there's an object under POSN avoid trying to
            ;; ascertain the bounds of the word surrounding it.
            (save-excursion
              (goto-char point)
              (forward-word-strictly)
              ;; Set word-end to ZV if there is no word after this
              ;; one.
              (setq word-end (point))
              ;; Now try to move backwards.  Set word-start to BEGV if
              ;; this word is there.
              (backward-word-strictly)
              (setq word-start (point))))
          ;; Check if word-start and word-end are identical, if there
          ;; is an object under POSN, or if point is looking at or
          ;; outside a word.
          (if (or (eq word-start word-end)
                  (>= word-start point))
              (progn
                ;; If so, clear the bounds and set and activate the
                ;; mark.
                (setq touch-screen-word-select-bounds nil
                      touch-screen-word-select-initial-word nil)
                (push-mark point)
                (goto-char point)
                (activate-mark))
            ;; Otherwise, select the word.  Move point to either the
            ;; end or the start of the word, depending on which is
            ;; closer to EVENT.
            (let ((diff-beg (- point word-start))
                  (diff-end (- word-end point))
                  use-end)
              (if (> diff-beg diff-end)
                  ;; Set the point to the end of the word.
                  (setq use-end t)
                (if (< diff-end diff-beg)
                    (setq use-end nil)
                  ;; POINT is in the middle of the word.  Use its
                  ;; window coordinates to establish whether or not it
                  ;; is closer to the start of the word or to the end
                  ;; of the word.
                  (let ((posn-beg (posn-at-point word-start))
                        (posn-end (posn-at-point word-end)))
                    ;; Give up if there's an object at either of those
                    ;; positions, or they're not on the same row.
                    ;; If one of the positions isn't visible, use the
                    ;; window end.
                    (if (and posn-beg posn-end
                             (not (posn-object posn-beg))
                             (not (posn-object posn-end))
                             (eq (cdr (posn-col-row posn-beg))
                                 (cdr (posn-col-row posn-end))))
                        (setq use-end nil)
                      ;; Compare the pixel positions.
                      (setq point (car (posn-x-y posn))
                            diff-beg (- point (car (posn-x-y posn-beg)))
                            diff-end (- (car (posn-x-y posn-end)) point))
                      ;; Now determine whether or not point should be
                      ;; moved to the end.
                      (setq use-end (>= diff-beg diff-end))))))
              (if use-end
                  (progn
                    (push-mark word-start)
                    (activate-mark)
                    (goto-char word-end))
                (progn
                    (push-mark word-end)
                    (activate-mark)
                    (goto-char word-start)))
              ;; Record the bounds of the selected word.
              (setq touch-screen-word-select-bounds
                    (cons word-start word-end)
                    ;; Save this for the benefit of touch-screen-drag.
                    touch-screen-word-select-initial-word
                    (cons word-start word-end)))))))))

(defun touch-screen-preview-select ()
  "Display a preview of the line around point in the echo area.
Unless the minibuffer is an active or the current line is
excessively tall, display an indication of the position of point
and the contents of the visible line around it within the echo
area.

If the selected window is hscrolled or lines may be truncated,
attempt to find the extents of the text between column 0 and the
right most column of the window using `posn-at-x-y'."
  (interactive)
  ;; First, establish that the minibuffer isn't active and the line
  ;; isn't taller than two times the frame character height.
  (unless (or (> (minibuffer-depth) 0)
              ;; The code below doesn't adapt well to buffers
              ;; containing long lines.
              (long-line-optimizations-p)
              (let ((window-line-height (window-line-height))
                    (maximum-height (* 2 (frame-char-height))))
                (unless window-line-height
                  ;; `window-line-height' isn't available.
                  ;; Redisplay first and try to ascertain the height
                  ;; of the line again.
                  (redisplay t)
                  (setq window-line-height (window-line-height)))
                ;; `window-line-height' might still be unavailable.
                (and window-line-height
                     (> (car window-line-height)
                        maximum-height))))
    (catch 'hscrolled-away
      (let ((beg nil) end string y)
        ;; Detect whether or not the window is hscrolled.  If it
        ;; is, set beg to the location of the first column
        ;; instead.
        (when (> (window-hscroll) 0)
          (setq y (+ (or (cdr (posn-x-y (posn-at-point)))
                         (throw 'hscrolled-away t))
                     (window-header-line-height)
                     (window-tab-line-height)))
          (let* ((posn (posn-at-x-y 0 y))
                 (point (posn-point posn)))
            (setq beg point)))
        ;; Check if lines are being truncated; if so, use the
        ;; character at the end of the window as the end of the
        ;; text to be displayed, as the visual line may extend
        ;; past the window.
        (when (or truncate-lines beg) ; truncate-lines or hscroll.
          (setq y (or y (+ (or (cdr (posn-x-y (posn-at-point)))
                               (throw 'hscrolled-away t))
                           (window-header-line-height)
                           (window-tab-line-height))))
          (let* ((posn (posn-at-x-y (1- (window-width nil t)) y))
                 (point (posn-point posn)))
            (setq end point)))
        ;; Now find the rest of the visual line.
        (save-excursion
          (unless beg
            (beginning-of-visual-line)
            (setq beg (point)))
          (unless end
            (end-of-visual-line)
            (setq end (point))))
        ;; Obtain a substring containing the beginning of the
        ;; visual line and the end.
        (setq string (buffer-substring beg end))
        ;; Hack `invisible' properties within the new string.
        ;; Look for each change of the property that is a variable
        ;; name and replace it with its actual value according to
        ;; `buffer-invisibility-spec'.
        (when (listp buffer-invisibility-spec)
          (let ((index 0)
                (property (get-text-property 0
                                             'invisible
                                             string))
                index1 invisible)
            (while index
              ;; Find the end of this text property.
              (setq index1 (next-single-property-change index
                                                        'invisible
                                                        string))
              ;; Replace the property with whether or not it is
              ;; non-nil.
              (when property
                (setq invisible nil)
                (catch 'invisible
                  (dolist (spec buffer-invisibility-spec)
                    ;; Process one element of the buffer
                    ;; invisibility specification.
                    (if (consp spec)
                        (when (eq (cdr spec) 't)
                          ;; (ATOM . t) makes N invisible if N is
                          ;; equal to ATOM or a list containing
                          ;; ATOM.
                          (when (or (eq (car spec) property)
                                    (and (listp spec)
                                         (memq (car spec) invisible)))
                            (throw 'invisible (setq invisible t))))
                      ;; Otherwise, N is invisible if SPEC is
                      ;; equal to N.
                      (when (eq spec property)
                        (throw 'invisible (setq invisible t))))))
                (put-text-property index (or index1
                                             (- end beg))
                                   'invisible invisible string))
              ;; Set index to that of the next text property and
              ;; continue.
              (setq index index1
                    property (and index1
                                  (get-text-property index1
                                                     'invisible
                                                     string))))))
        (let ((resize-mini-windows t) difference width
              (message-log-max nil))
          ;; Find the offset of point from beg and display a cursor
          ;; below.
          (setq difference (- (point) beg)
                width (string-pixel-width
                       (substring string 0 difference)))
          (message "%s\n%s^" string
                   (propertize " "
                               'display (list 'space
                                              :width (list width)))))
        nil))))

(defun touch-screen-drag (event)
  "Handle a drag EVENT by setting the region to its new point.
If `touch-screen-word-select' and EVENT lies outside the last
word that was selected, select the word that now contains POINT.
Scroll the window if EVENT's coordinates are outside its text
area."
  (interactive "e")
  (let* ((posn (cadr event)) ; Position of the tool.
         (point (posn-point posn)) ; Point of the event.
         ;; Window where the tap originated.
         (window (nth 1 touch-screen-current-tool))
         ;; The currently selected window.  Used to redisplay within
         ;; the correct window while scrolling.
         (old-window (selected-window))
         ;; Whether or not text should be selected word-by-word.
         (word-select touch-screen-word-select)
         ;; Cons containing the confines of the word initially
         ;; selected when the touchpoint was first held down.
         (initial touch-screen-word-select-initial-word)
         initial-point)
    ;; Keep dragging.
    (with-selected-window window
      ;; Figure out what character to go to.  If this posn is in the
      ;; window, go to (posn-point posn).  If not, then go to the line
      ;; before either window start or window end.
      (setq initial-point (point))
      (when (or (not point)
                (not (eq point initial-point)))
        (if (and (eq (posn-window posn) window)
                 point
                 ;; point must be visible in the window.  If it isn't,
                 ;; the window must be scrolled.
                 (pos-visible-in-window-p point))
            (let* ((bounds touch-screen-word-select-bounds)
                   (maybe-select-word (or (not touch-screen-word-select)
                                          (or (not bounds)
                                              (> point (cdr bounds))
                                              (< point (car bounds))))))
              (if (and word-select
                       ;; point is now outside the last word selected.
                       maybe-select-word
                       (not (posn-object posn))
                       (when-let* ((char (char-after point))
                                   (class (char-syntax char)))
                         ;; Don't select words if point isn't inside a
                         ;; word constituent or similar.
                         (or (eq class ?w) (eq class ?_))))
                  ;; Determine the confines of the word containing
                  ;; POINT.
                  (let (word-start word-end)
                    (save-excursion
                      (goto-char point)
                      (forward-word-strictly)
                      ;; Set word-end to ZV if there is no word after
                      ;; this one.
                      (setq word-end (point))
                      ;; Now try to move backwards.  Set word-start to
                      ;; BEGV if this word is there.
                      (backward-word-strictly)
                      (setq word-start (point)))
                    (let ((mark (mark)))
                      ;; Extend the region to cover either word-end or
                      ;; word-start; whether to goto word-end or
                      ;; word-start is subject to the position of the
                      ;; mark relative to point.
                      (if (< word-start mark)
                          ;; The start of the word is behind mark.
                          ;; Extend the region towards the start.
                          (goto-char word-start)
                        ;; Else, go to the end of the word.
                        (goto-char word-end))
                      ;; If point is less than mark, which is is less
                      ;; than the end of the word that was originally
                      ;; selected, try to keep it selected by moving
                      ;; mark there.
                      (when (and initial (<= (point) mark)
                                 (< mark (cdr initial)))
                        (set-mark (cdr initial)))
                      ;; Do the opposite when the converse is true.
                      (when (and initial (>= (point) mark)
                                 (> mark (car initial)))
                        (set-mark (car initial))))
                    (if bounds
                        (progn (setcar bounds word-start)
                               (setcdr bounds word-end))
                      (setq touch-screen-word-select-bounds
                            (cons word-start word-end))))
                (when maybe-select-word
                  (goto-char (posn-point posn))
                  (when initial
                    ;; If point is less than mark, which is is less
                    ;; than the end of the word that was originally
                    ;; selected, try to keep it selected by moving
                    ;; mark there.
                    (when (and (<= (point) (mark))
                               (< (mark) (cdr initial)))
                      (set-mark (cdr initial)))
                    ;; Do the opposite when the converse is true.
                    (when (and (>= (point) (mark))
                               (> (mark) (car initial)))
                      (set-mark (car initial))))
                  (setq touch-screen-word-select-bounds nil)))
              ;; Finally, display a preview of the line around point
              ;; if requested by the user.
              (when (and touch-screen-preview-select
                         (not (eq (point) initial-point)))
                (touch-screen-preview-select)))
          ;; POSN is outside the window.  Scroll accordingly.
          (let* ((relative-xy
                  (touch-screen-relative-xy posn window))
                 (xy (posn-x-y posn))
                 ;; The height of the window's text area.
                 (body-height (window-body-height nil t))
                 ;; This is used to find the character closest to
                 ;; POSN's column at the bottom of the window.
                 (height (- body-height
                            ;; Use the last row of the window, not its
                            ;; last pixel.
                            (frame-char-height)))
                 (midpoint (/ body-height 2))
                 (scroll-conservatively 101))
            (cond
             ((< (cdr relative-xy) midpoint)
              ;; POSN is before half the window, yet POINT does not
              ;; exist or is not completely visible within.  Scroll
              ;; downwards.
              (ignore-errors
                ;; Scroll down by a single line.
                (scroll-down 1)
                ;; After scrolling, look up the new posn at EVENT's
                ;; column and go there.
                (setq posn (posn-at-x-y (car xy) 0)
                      point (posn-point posn))
                (if point
                    (goto-char point)
                  ;; If there's no buffer position at that column, go
                  ;; to the window start.
                  (goto-char (window-start)))
                ;; If word selection is enabled, now try to keep the
                ;; initially selected word within the active region.
                (when word-select
                  (when initial
                    ;; If point is less than mark, which is is less
                    ;; than the end of the word that was originally
                    ;; selected, try to keep it selected by moving
                    ;; mark there.
                    (when (and (<= (point) (mark))
                               (< (mark) (cdr initial)))
                      (set-mark (cdr initial)))
                    ;; Do the opposite when the converse is true.
                    (when (and (>= (point) (mark))
                               (> (mark) (car initial)))
                      (set-mark (car initial))))
                  (setq touch-screen-word-select-bounds nil))
                ;; Display a preview of the line now around point if
                ;; requested by the user.
                (when touch-screen-preview-select
                  (touch-screen-preview-select))
                ;; Select old-window, so that redisplay doesn't
                ;; display WINDOW as selected if it isn't already.
                (with-selected-window old-window
                  ;; Now repeat this every `mouse-scroll-delay' until
                  ;; input becomes available, but scroll down a few
                  ;; more lines.
                  (while (sit-for mouse-scroll-delay)
                    ;; Select WINDOW again.
                    (with-selected-window window
                      ;; Keep scrolling down until input becomes
                      ;; available.
                      (scroll-down 4)
                      ;; After scrolling, look up the new posn at
                      ;; EVENT's column and go there.
                      (setq posn (posn-at-x-y (car xy) 0)
                            point (posn-point posn))
                      (if point
                          (goto-char point)
                        ;; If there's no buffer position at that
                        ;; column, go to the window start.
                        (goto-char (window-start)))
                      ;; If word selection is enabled, now try to keep
                      ;; the initially selected word within the active
                      ;; region.
                      (when word-select
                        (when initial
                          ;; If point is less than mark, which is is
                          ;; less than the end of the word that was
                          ;; originally selected, try to keep it
                          ;; selected by moving mark there.
                          (when (and (<= (point) (mark))
                                     (< (mark) (cdr initial)))
                            (set-mark (cdr initial)))
                          ;; Do the opposite when the converse is true.
                          (when (and (>= (point) (mark))
                                     (> (mark) (car initial)))
                            (set-mark (car initial))))
                        (setq touch-screen-word-select-bounds nil))
                      ;; Display a preview of the line now around
                      ;; point if requested by the user.
                      (when touch-screen-preview-select
                        (touch-screen-preview-select))))))
              (setq touch-screen-word-select-bounds nil))
             ((>= (cdr relative-xy) midpoint)
              ;; Default to scrolling upwards even if POSN is still
              ;; within the confines of the window.  If POINT is
              ;; partially visible, and the branch above hasn't been
              ;; taken it must be somewhere at the bottom of the
              ;; window, so scroll downwards.
              (ignore-errors
                ;; Scroll up by a single line.
                (scroll-up 1)
                ;; After scrolling, look up the new posn at EVENT's
                ;; column and go there.
                (setq posn (posn-at-x-y (car xy) height)
                      point (posn-point posn))
                (if point
                    (goto-char point)
                  ;; If there's no buffer position at that column, go
                  ;; to the window start.
                  (goto-char (window-end nil t)))
                ;; If word selection is enabled, now try to keep
                ;; the initially selected word within the active
                ;; region.
                (when word-select
                  (when initial
                    ;; If point is less than mark, which is is less
                    ;; than the end of the word that was originally
                    ;; selected, try to keep it selected by moving
                    ;; mark there.
                    (when (and (<= (point) (mark))
                               (< (mark) (cdr initial)))
                      (set-mark (cdr initial)))
                    ;; Do the opposite when the converse is true.
                    (when (and (>= (point) (mark))
                               (> (mark) (car initial)))
                      (set-mark (car initial))))
                  (setq touch-screen-word-select-bounds nil))
                ;; Display a preview of the line now around point if
                ;; requested by the user.
                (when touch-screen-preview-select
                  (touch-screen-preview-select))
                ;; Select old-window, so that redisplay doesn't
                ;; display WINDOW as selected if it isn't already.
                (with-selected-window old-window
                  ;; Now repeat this every `mouse-scroll-delay' until
                  ;; input becomes available, but scroll down a few
                  ;; more lines.
                  (while (sit-for mouse-scroll-delay)
                    ;; Select WINDOW again.
                    (with-selected-window window
                      ;; Keep scrolling down until input becomes
                      ;; available.
                      (scroll-up 4)
                      ;; After scrolling, look up the new posn at
                      ;; EVENT's column and go there.
                      (setq posn (posn-at-x-y (car xy) height)
                            point (posn-point posn))
                      (if point
                          (goto-char point)
                        ;; If there's no buffer position at that
                        ;; column, go to the window start.
                        (goto-char (window-end nil t)))
                      ;; If word selection is enabled, now try to keep
                      ;; the initially selected word within the active
                      ;; region.
                      (when word-select
                        (when initial
                          ;; If point is less than mark, which is is less
                          ;; than the end of the word that was originally
                          ;; selected, try to keep it selected by moving
                          ;; mark there.
                          (when (and (<= (point) (mark))
                                     (< (mark) (cdr initial)))
                            (set-mark (cdr initial)))
                          ;; Do the opposite when the converse is true.
                          (when (and (>= (point) (mark))
                                     (> (mark) (car initial)))
                            (set-mark (car initial))))
                        (setq touch-screen-word-select-bounds nil))
                      ;; Display a preview of the line now around
                      ;; point if requested by the user.
                      (when touch-screen-preview-select
                        (touch-screen-preview-select))))))))))))))

(defun touch-screen-restart-drag (event)
  "Restart dragging to select text.
Set point to the location of EVENT within its window while
keeping the bounds of the region intact, and set up state for
`touch-screen-drag'."
  (interactive "e")
  (let* ((posn (event-start event))
         (window (posn-window posn))
         (point (posn-point posn)))
    (with-selected-window window
      (let ((current-point (point))
            (current-mark (mark)))
        ;; Ensure that mark and point haven't changed since EVENT was
        ;; generated, and the region is still active.
        (when (or (eq point current-point)
                  (eq point current-mark)
                  (region-active-p))
          (when (eq point current-mark)
            ;; Exchange point and mark.
            (exchange-point-and-mark))
          ;; Clear the state necessary to set up dragging.  Don't try
          ;; to select entire words immediately after dragging starts,
          ;; to allow for fine grained selection inside a word.
          (setq touch-screen-word-select-bounds nil
                touch-screen-word-select-initial-word nil))))))

(global-set-key [touchscreen-hold] #'touch-screen-hold)
(global-set-key [touchscreen-drag] #'touch-screen-drag)
(global-set-key [touchscreen-restart-drag] #'touch-screen-restart-drag)



;; Pinch gesture.

(defvar text-scale-mode)
(defvar text-scale-mode-amount)
(defvar text-scale-mode-step)

(defun touch-screen-scroll-point-to-y (target-point target-y)
  "Move the row surrounding TARGET-POINT to TARGET-Y.
Scroll the current window such that the position of TARGET-POINT
within it on the Y axis approaches TARGET-Y."
  (condition-case nil
      (let* ((last-point (point))
	     (current-y (cadr (pos-visible-in-window-p target-point
                                                       nil t)))
	     (direction (if (if current-y
                                (< target-y current-y)
			      (< (window-start) target-point))
			    -1 1)))
	(while (< 0 (* direction (if current-y
				     (- target-y current-y)
				   (- (window-start) target-point))))
	  (scroll-down direction)
	  (setq last-point (point))
	  (setq current-y (cadr (pos-visible-in-window-p target-point nil t))))
	(unless (and (< direction 0) current-y)
	  (scroll-up direction)
	  (goto-char last-point)))
    ;; Ignore BOB and EOB.
    ((beginning-of-buffer end-of-buffer) nil)))

(defun touch-screen-pinch (event)
  "Scroll the window in the touchscreen-pinch event EVENT.
Pan the display by the pan deltas in EVENT, and adjust the
text scale by the ratio therein."
  (interactive "e")
  (require 'face-remap)
  (let* ((posn (cadr event))
         (window (posn-window posn))
         (scale (nth 2 event))
         (ratio-diff (nth 5 event))
         current-scale start-scale)
    (when (windowp window)
      (with-selected-window window
        (setq current-scale (if text-scale-mode
                                text-scale-mode-amount
                              0)
              start-scale (or (aref touch-screen-aux-tool 7)
                              (aset touch-screen-aux-tool 7
                                    current-scale)))
        ;; Set the text scale.
        (text-scale-set (+ start-scale
                           (round (log scale text-scale-mode-step))))
        ;; Subsequently move the row which was at the centrum to its Y
        ;; position.
        (if (and (not (eq current-scale
                          text-scale-mode-amount))
                 (posn-point posn)
                 (cdr (posn-x-y posn)))
            (touch-screen-scroll-point-to-y (posn-point posn)
                                            (cdr (posn-x-y posn)))
          ;; Rather than scroll POSN's point to its old row, scroll the
          ;; display by the Y axis deltas within EVENT.
          (let ((height (window-default-line-height))
                (y-accumulator (or (aref touch-screen-aux-tool 8) 0)))
            (setq y-accumulator (+ y-accumulator (nth 4 event)))
            (when (or (> y-accumulator height)
                      (< y-accumulator (- height)))
              (ignore-errors
                (if (> y-accumulator 0)
                    (scroll-down 1)
                  (scroll-up 1)))
              (setq y-accumulator 0))
            (aset touch-screen-aux-tool 8 y-accumulator))
          ;; Likewise for the X axis deltas.
          (let ((width (frame-char-width))
                (x-accumulator (or (aref touch-screen-aux-tool 9) 0)))
            (setq x-accumulator (+ x-accumulator (nth 3 event)))
            (when (or (> x-accumulator width)
                      (< x-accumulator (- width)))
              ;; Do not hscroll if the ratio has shrunk, for that is
              ;; generally attended by the centerpoint moving left,
              ;; and Emacs can hscroll left even when no lines are
              ;; truncated.
              (unless (and (< x-accumulator 0)
                           (< ratio-diff 0))
                (if (> x-accumulator 0)
                    (scroll-right 1)
                  (scroll-left 1)))
              (setq x-accumulator 0))
            (aset touch-screen-aux-tool 9 x-accumulator)))))))

(define-key global-map [touchscreen-pinch] #'touch-screen-pinch)



;; Touch screen event translation.  The code here translates raw touch
;; screen events into `touchscreen-scroll' events and mouse events in
;; a ``DWIM'' fashion, consulting the keymaps at the position of the
;; mouse event to determine the best course of action, while also
;; recognizing drag-to-select and other gestures.

(defun touch-screen-handle-timeout (arg)
  "Start the touch screen timeout or handle it depending on ARG.
When ARG is nil, start the `touch-screen-current-timer' to go off
in `touch-screen-delay' seconds, and call this function with ARG
t.

When ARG is t, set the fourth element of
`touch-screen-current-tool' to `held', and generate a
`touchscreen-hold' event at the original position of that tool."
  (if (not arg)
      ;; Cancel the touch screen long-press timer, if it is still
      ;; there by any chance.
      (progn
        (when touch-screen-current-timer
          (cancel-timer touch-screen-current-timer))
        (setq touch-screen-current-timer
              (run-at-time touch-screen-delay nil
                           #'touch-screen-handle-timeout
                           t)))
    ;; Set touch-screen-current-timer to nil.
    (setq touch-screen-current-timer nil)
    (when touch-screen-current-tool
      ;; Set the state to `held'.
      (setcar (nthcdr 3 touch-screen-current-tool) 'held)
      ;; Generate an input event at the original position of the mark.
      ;; This assumes that the timer is running within
      ;; `touch-screen-translate-touch'.
      (let ((posn (nth 4 touch-screen-current-tool)))
        (throw 'input-event (list 'touchscreen-hold posn))))))

(defun touch-screen-handle-point-update (point)
  "Notice that the touch point POINT has changed position.
Perform the editing operations or throw to the input translation
function with an input event tied to any gesture that is
recognized.

Update the tenth element of `touch-screen-current-tool' with
POINT relative to the window it was placed on.  Update the third
element in like fashion, once sufficient motion has accumulated
that an event is generated.

POINT must be the touch point currently being tracked as
`touch-screen-current-tool'.

If the fourth element of `touch-screen-current-tool' is nil, then
the touch has just begun.  In a related case, if it is
`ancillary-tool', then the ancillary tool has been removed and
gesture translation must be resumed.  Determine how much POINT
has moved.  If POINT has moved upwards or downwards by a
significant amount, then set the fourth element to `scroll'.
Then, generate a `touchscreen-scroll' event with the window that
POINT was initially placed upon, and pixel deltas describing how
much point has moved relative to its previous position in the X
and Y axes.

If the fourth element of `touch-screen-current-tool' is `scroll',
then generate a `touchscreen-scroll' event with the window that
POINT was initially placed upon, and pixel deltas describing how
much point has moved relative to its previous position in the X
and Y axes.

If the fourth element of `touch-screen-current-tool' is
`mouse-drag' and `track-mouse' is non-nil, then generate a
`mouse-movement' event with the position of POINT.

If the fourth element of `touch-screen-current-tool' is `held',
then the touch has been held down for some time.  If motion
happens, set the field to `drag'.  Then, generate a
`touchscreen-drag' event.

If the fourth element of `touch-screen-current-tool' is
`restart-drag', set the field to `drag' and generate a
`touchscreen-drag'.

If the fourth element of `touch-screen-current-tool' is `drag',
then move point to the position of POINT."
  (let* ((window (nth 1 touch-screen-current-tool))
         (what (nth 3 touch-screen-current-tool))
         (posn (cdr point))
         ;; Now get the position of X and Y relative to WINDOW.
         (relative-xy
         (touch-screen-relative-xy posn window)))
    ;; Update the 10th field of the tool list with RELATIVE-XY.
    (setcar (nthcdr 9 touch-screen-current-tool) relative-xy)
    (cond ((or (null what)
               (eq what 'ancillary-tool))
           (let* ((last-posn (nth 2 touch-screen-current-tool))
                  (diff-x (- (car last-posn) (car relative-xy)))
                  (diff-y (- (cdr last-posn) (cdr relative-xy))))
             (when (or (> diff-y 10)
                       (> diff-x (frame-char-width))
                       (< diff-y -10)
                       (< diff-x (- (frame-char-width))))
               (setcar (nthcdr 3 touch-screen-current-tool)
                       'scroll)
               (setcar (nthcdr 2 touch-screen-current-tool)
                       relative-xy)
               ;; Cancel the touch screen long-press timer, if it is
               ;; still there by any chance.
               (when touch-screen-current-timer
                 (cancel-timer touch-screen-current-timer)
                 (setq touch-screen-current-timer nil))
               ;; Generate a `touchscreen-scroll' event with `diff-x'
               ;; and `diff-y'.
               (throw 'input-event
                      (list 'touchscreen-scroll
                            window diff-x diff-y)))))
          ((eq what 'scroll)
           ;; Cancel the touch screen long-press timer, if it is still
           ;; there by any chance.
           (when touch-screen-current-timer
             (cancel-timer touch-screen-current-timer)
             (setq touch-screen-current-timer nil))
           (let* ((last-posn (nth 2 touch-screen-current-tool))
                  (diff-x (- (car last-posn) (car relative-xy)))
                  (diff-y (- (cdr last-posn) (cdr relative-xy))))
             (setcar (nthcdr 3 touch-screen-current-tool)
                     'scroll)
             (setcar (nthcdr 2 touch-screen-current-tool)
                     relative-xy)
             (unless (and (zerop diff-x) (zerop diff-y))
               (throw 'input-event
                      ;; Generate a `touchscreen-scroll' event with
                      ;; `diff-x' and `diff-y'.
                      (list 'touchscreen-scroll
                            window diff-x diff-y)))))
          ((eq what 'mouse-drag)
           ;; There was a `down-mouse-1' event bound at the starting
           ;; point of the event.  Generate a mouse-motion event if
           ;; mouse movement is being tracked.
           (when track-mouse
             (throw 'input-event (list 'mouse-movement
                                       (cdr point)))))
          ((eq what 'held)
           (let* ((posn (cdr point)))
             ;; Now start dragging.
             (setcar (nthcdr 3 touch-screen-current-tool)
                     'drag)
             ;; Generate a (touchscreen-drag POSN) event.
             ;; `touchscreen-hold' was generated when the timeout
             ;; fired.
             (throw 'input-event (list 'touchscreen-drag posn))))
          ((eq what 'restart-drag)
           (let* ((posn (cdr point)))
             ;; Now start dragging.
             (setcar (nthcdr 3 touch-screen-current-tool)
                     'drag)
             ;; Generate a (touchscreen-drag POSN) event.
             ;; `touchscreen-restart-drag' was generated when the
             ;; timeout fired.
             (throw 'input-event (list 'touchscreen-drag posn))))
          ((eq what 'drag)
           (let* ((posn (cdr point)))
             ;; Generate a (touchscreen-drag POSN) event.
             (throw 'input-event (list 'touchscreen-drag posn)))))))

(defsubst touch-screen-distance (pos1 pos2)
  "Compute the distance in pixels between POS1 and POS2.
Each is a coordinate whose car and cdr are respectively its X and
Y values."
  (let ((v1 (- (cdr pos2) (cdr pos1)))
        (v2 (- (car pos2) (car pos1))))
    (abs (sqrt (+ (* v1 v1) (* v2 v2))))))

(defsubst touch-screen-centrum (pos1 pos2)
  "Compute the center of a line between the points POS1 and POS2.
Each, and value, is a coordinate whose car and cdr are
respectively its X and Y values."
  (let ((v1 (+ (cdr pos2) (cdr pos1)))
        (v2 (+ (car pos2) (car pos1))))
    (cons (/ v2 2) (/ v1 2))))

(defun touch-screen-handle-aux-point-update (point number)
  "Notice that a point being observed has moved.
Register motion from either the current or ancillary tool while
an ancillary tool is present.

POINT must be the cdr of an element of a `touchscreen-update'
event's list of touch points.  NUMBER must be its touch ID.

Calculate the distance between POINT's position and that of the
other tool (which is to say the ancillary tool of POINT is the
current tool, and vice versa).  Compare this distance to that
between both points at the time they were placed on the screen,
and signal a pinch event to adjust the text scale and scroll the
window by the factor so derived.  Such events are lists formed as
so illustrated:

    (touchscreen-pinch CENTRUM RATIO PAN-X PAN-Y RATIO-DIFF)

in which CENTRUM is a posn representing the midpoint of a line
between the present locations of both tools, RATIO is the said
factor, PAN-X is the number of pixels on the X axis that centrum
has moved since the last event, PAN-Y is that on the Y axis, and
RATIO-DIFF is the difference between RATIO and the ratio in the
last such event."
  (let (this-point-position
        other-point-position
        (window (cadr touch-screen-current-tool)))
    (when (windowp window)
      (if (eq number (aref touch-screen-aux-tool 0))
          (progn
            ;; The point pressed is the ancillary tool.  Set
            ;; other-point-position to that of the current tool.
            (setq other-point-position (nth 9 touch-screen-current-tool))
            ;; Update the position within touch-screen-aux-tool.
            (aset touch-screen-aux-tool 3
                  (setq this-point-position
                        (touch-screen-relative-xy point window))))
        (setq other-point-position (aref touch-screen-aux-tool 3))
        (setcar (nthcdr 2 touch-screen-current-tool)
                (setq this-point-position
                      (touch-screen-relative-xy point window)))
        (setcar (nthcdr 9 touch-screen-current-tool)
                this-point-position))
      ;; Now compute, and take the absolute of, this distance.
      (let ((distance (touch-screen-distance this-point-position
                                             other-point-position))
            (centrum (touch-screen-centrum this-point-position
                                           other-point-position))
            (initial-distance (aref touch-screen-aux-tool 4))
            (initial-centrum (aref touch-screen-aux-tool 5)))
        (let* ((ratio (/ distance initial-distance))
               (ratio-diff (- ratio (aref touch-screen-aux-tool 6)))
               (diff (abs (- ratio (aref touch-screen-aux-tool 6))))
               (centrum-diff (+ (abs (- (car initial-centrum)
                                        (car centrum)))
                                (abs (- (cdr initial-centrum)
                                        (cdr centrum))))))
          ;; If the difference in ratio has surpassed a threshold of
          ;; 0.2 or the centrum difference exceeds the frame's char
          ;; width, send a touchscreen-pinch event with this
          ;; information and update that saved in
          ;; touch-screen-aux-tool.
          (when (or (> diff 0.2)
                    (> centrum-diff
                       (/ (frame-char-width) 2)))
            (aset touch-screen-aux-tool 5 centrum)
            (aset touch-screen-aux-tool 6 ratio)
            (throw 'input-event
                   (list 'touchscreen-pinch
                         (if (or (<= (car centrum) 0)
                                 (<= (cdr centrum) 0))
                             (list window nil centrum nil nil
                                   nil nil nil nil nil)
                           (let ((posn (posn-at-x-y (car centrum)
                                                    (cdr centrum)
                                                    window)))
                             (if (eq (posn-window posn)
                                     window)
                                 posn
                               ;; Return a placeholder
                               ;; outside the window if
                               ;; the centrum has moved
                               ;; beyond the confines of
                               ;; the window where the
                               ;; gesture commenced.
                               (list window nil centrum nil nil
                                     nil nil nil nil nil))))
                         ratio
                         (- (car centrum)
                            (car initial-centrum))
                         (- (cdr centrum)
                            (cdr initial-centrum))
                         ratio-diff))))))))

(defun touch-screen-window-selection-changed (frame)
  "Notice that FRAME's selected window has changed.
Cancel any timer that is supposed to hide the keyboard in
response to the minibuffer being closed."
  (with-selected-frame frame
    (unless (and (or buffer-read-only
                     (get-text-property (point) 'read-only))
                 ;; Don't hide the on-screen keyboard if it's always
                 ;; supposed to be displayed.
                 (not touch-screen-display-keyboard))
      ;; Prevent hiding the minibuffer from hiding the on screen
      ;; keyboard.
      (when minibuffer-on-screen-keyboard-timer
        (cancel-timer minibuffer-on-screen-keyboard-timer)
        (setq minibuffer-on-screen-keyboard-timer nil)))))

(defun touch-screen-handle-point-up (point prefix)
  "Notice that POINT has been removed from the screen.
POINT should be the point currently tracked as
`touch-screen-current-tool'.
PREFIX should be a virtual function key used to look up key
bindings.

If an ancillary touch point is being observed, transfer touch
information from `touch-screen-aux-tool' to
`touch-screen-current-tool' and set it to nil, thereby resuming
gesture recognition with that tool replacing the tool removed.

Otherwise:

If the fourth element of `touch-screen-current-tool' is nil or
`restart-drag', move point to the position of POINT, selecting
the window under POINT as well, and deactivate the mark; if there
is a button or link at POINT, call the command bound to `mouse-2'
there.  Otherwise, call the command bound to `mouse-1'.

If the fourth element of `touch-screen-current-tool' is
`mouse-drag', then generate either a `mouse-1' or a
`drag-mouse-1' event depending on how far the position of POINT
is from the starting point of the touch.

If the fourth element of `touch-screen-current-tool' is
`mouse-1-menu', then generate a `down-mouse-1' event at the
original position of the tool to display its bound keymap as a
menu.

If the fourth element of `touch-screen-current-tool' is `drag' or
`held', the region is active, and the tool's initial window's
selected buffer isn't read-only, display the on screen keyboard.

If the command being executed is listed in
`touch-screen-set-point-commands' also display the on-screen
keyboard if the current buffer and the character at the new point
is not read-only."
  (if touch-screen-aux-tool
      (progn
        (let ((point-no (aref touch-screen-aux-tool 0))
              (relative-xy (aref touch-screen-aux-tool 3)))
          ;; Replace the current position of touch-screen-current-tool
          ;; with relative-xy and its number with point-no, but leave
          ;; other information (such as its starting position) intact:
          ;; this touchpoint is meant to continue the gesture
          ;; interrupted by the removal of the last, not to commence a
          ;; new one.
          (setcar touch-screen-current-tool point-no)
          (setcar (nthcdr 2 touch-screen-current-tool)
                  relative-xy)
          (setcar (nthcdr 9 touch-screen-current-tool)
                  relative-xy))
        (setq touch-screen-aux-tool nil))
    (let ((what (nth 3 touch-screen-current-tool))
          (posn (cdr point)) window point)
      (cond ((or (null what)
                 ;; If dragging has been restarted but the touch point
                 ;; hasn't been moved, translate the sequence into a
                 ;; regular mouse click.
                 (eq what 'restart-drag))
             (when (windowp (posn-window posn))
               (setq point (posn-point posn)
                     window (posn-window posn))
               ;; Select the window that was tapped given that it
               ;; isn't an inactive minibuffer window.
               (when (or (not (eq window
                                  (minibuffer-window
                                   (window-frame window))))
                         (minibuffer-window-active-p window))
                 (select-window window))
               ;; Now simulate a mouse click there.  If there is a
               ;; link or a button, use mouse-2 to push it.
               (let* ((event (list (if (or (mouse-on-link-p posn)
                                           (and point (button-at point)))
                                       'mouse-2
                                     'mouse-1)
                                   posn))
                      ;; Look for the command bound to this event.
                      (command (key-binding (if prefix
                                                (vector prefix
                                                        (car event))
                                              (vector (car event)))
                                            t nil posn)))
                 (deactivate-mark)
                 (when point
                   ;; This is necessary for following links.
                   (goto-char point))
                 ;; Figure out if the on screen keyboard needs to be
                 ;; displayed.
                 (when command
                   (if (memq command touch-screen-set-point-commands)
                       (if touch-screen-translate-prompt
                           ;; Forgo displaying the virtual keyboard
                           ;; should touch-screen-translate-prompt be
                           ;; set, for then the key won't be delivered
                           ;; to the command loop, but rather to a
                           ;; caller of read-key-sequence such as
                           ;; describe-key.
                           (throw 'input-event event)
                         (if (and (or (not buffer-read-only)
                                      touch-screen-display-keyboard)
                                  ;; Detect the splash screen and
                                  ;; avoid displaying the on screen
                                  ;; keyboard there.
                                  (not (equal (buffer-name) "*GNU Emacs*")))
                             ;; Once the on-screen keyboard has been
                             ;; opened, add
                             ;; `touch-screen-window-selection-changed'
                             ;; as a window selection change function
                             ;; This then prevents it from being
                             ;; hidden after exiting the minibuffer.
                             (progn
                               (add-hook
                                'window-selection-change-functions
                                #'touch-screen-window-selection-changed)
                               (frame-toggle-on-screen-keyboard
                                (selected-frame) nil))
                           ;; Otherwise, hide the on screen keyboard
                           ;; now.
                           (frame-toggle-on-screen-keyboard (selected-frame)
                                                            t))
                         ;; But if it's being called from `describe-key'
                         ;; or some such, return it as a key sequence.
                         (throw 'input-event event)))
                   ;; If not, return the event.
                   (throw 'input-event event)))))
            ((eq what 'mouse-drag)
             ;; Generate a corresponding `mouse-1' event.
             (let* ((new-window (posn-window posn))
                    (new-point (posn-point posn))
                    (old-posn (nth 4 touch-screen-current-tool))
                    (old-window (posn-window posn))
                    (old-point (posn-point posn)))
               (throw 'input-event
                      ;; If the position of the touch point hasn't
                      ;; changed, or it doesn't start or end on a
                      ;; window...
                      (if (and (not old-point) (not new-point))
                          ;; Should old-point and new-point both equal
                          ;; nil, compare the posn areas and nominal
                          ;; column position.  If either are
                          ;; different, generate a drag event.
                          (let ((new-col-row (posn-col-row posn))
                                (new-area (posn-area posn))
                                (old-col-row (posn-col-row old-posn))
                                (old-area (posn-area old-posn)))
                            (if (and (equal new-col-row old-col-row)
                                     (eq new-area old-area))
                                ;; ... generate a mouse-1 event...
                                (list 'mouse-1 posn)
                              ;; ... otherwise, generate a
                              ;; drag-mouse-1 event.
                              (list 'drag-mouse-1 old-posn posn)))
                        (if (and (eq new-window old-window)
                                 (eq new-point old-point)
                                 (windowp new-window)
                                 (windowp old-window))
                            ;; ... generate a mouse-1 event...
                            (list 'mouse-1 posn)
                          ;; ... otherwise, generate a drag-mouse-1
                          ;; event.
                          (list 'drag-mouse-1 old-posn posn))))))
            ((eq what 'mouse-1-menu)
             ;; Generate a `down-mouse-1' event at the position the tap
             ;; took place.
             (throw 'input-event
                    (list 'down-mouse-1
                          (nth 4 touch-screen-current-tool))))
            ((or (eq what 'drag)
                 ;; Merely initiating a drag is sufficient to select a
                 ;; word if word selection is enabled.
                 (eq what 'held))
             ;; Display the on screen keyboard if the region is now
             ;; active.  Check this within the window where the tool
             ;; was first place.
             (setq window (nth 1 touch-screen-current-tool))
             (when window
               (with-selected-window window
                 (when (and (region-active-p)
                            (not buffer-read-only))
                   ;; Once the on-screen keyboard has been opened, add
                   ;; `touch-screen-window-selection-changed' as a
                   ;; window selection change function.  This then
                   ;; prevents it from being hidden after exiting the
                   ;; minibuffer.
                   (progn
                     (add-hook 'window-selection-change-functions
                               #'touch-screen-window-selection-changed)
                     (frame-toggle-on-screen-keyboard (selected-frame)
                                                      nil))))))))))

(defun touch-screen-handle-touch (event prefix &optional interactive)
  "Handle a single touch EVENT, and perform associated actions.
EVENT can either be a `touchscreen-begin', `touchscreen-update' or
`touchscreen-end' event.
PREFIX is either nil, or a symbol specifying a virtual function
key to apply to EVENT.

If INTERACTIVE, execute the command associated with any event
generated instead of throwing `input-event'.  Otherwise, throw
`input-event' with a single input event if that event should take
the place of EVENT within the key sequence being translated, or
`nil' if all tools have been released.

Set `touch-screen-events-received' to `t' to indicate that touch
screen events have been received, and thus by extension require
functions undertaking event management themselves to call
`read-key' rather than `read-event'."
  (interactive "e\ni\np")
  (unless touch-screen-events-received
    (setq touch-screen-events-received t))
  (if interactive
      ;; Called interactively (probably from wid-edit.el.)
      ;; Add any event generated to `unread-command-events'.
      (let ((event1
             (let ((current-key-remap-sequence (vector event)))
               (touch-screen-translate-touch nil))))
        (when (vectorp event1)
          (setq unread-command-events
                (nconc unread-command-events
                       (nreverse (append event1 nil))))))
    (cond
     ((eq (car event) 'touchscreen-begin)
      ;; A tool was just pressed against the screen.  Figure out the
      ;; window where it is and make it the tool being tracked on the
      ;; window.
      (let* ((touchpoint (caadr event))
             (position (cdadr event))
             (window (posn-window position))
             (point (posn-point position))
             binding tool-list)
        ;; Cancel the touch screen timer, if it is still there by any
        ;; chance.
        (when touch-screen-current-timer
          (cancel-timer touch-screen-current-timer)
          (setq touch-screen-current-timer nil))
        ;; If a tool already exists...
        (if (and touch-screen-current-tool
                 ;; ..and the number of this tool is at variance with
                 ;; that of the current tool: if a `touchscreen-end'
                 ;; event is delivered that is somehow withheld from
                 ;; this function and the system does not assign
                 ;; monotonically increasing touch point identifiers,
                 ;; then the ancillary tool will be set to a tool
                 ;; bearing the same number as the current tool, and
                 ;; consequently the mechanism for detecting
                 ;; erroneously retained touch points upon the
                 ;; registration of `touchscreen-update' events will
                 ;; not be activated.
                 (not (eq touchpoint (car touch-screen-current-tool))))
            ;; Then record this tool as the ``auxiliary tool''.
            ;; Updates to the auxiliary tool are considered in unison
            ;; with those to the current tool; the distance between
            ;; both tools is measured and compared with that when the
            ;; auxiliary tool was first pressed, then interpreted as a
            ;; scale by which to adjust text within the current tool's
            ;; window.
            (when (eq (if (framep window) window (window-frame window))
                      ;; Verify that the new tool was placed on the
                      ;; same frame the current tool has, so as not to
                      ;; consider events distributed across distinct
                      ;; frames components of a single gesture.
                      (window-frame (nth 1 touch-screen-current-tool)))
              ;; Set touch-screen-aux-tool as is proper.  Mind that
              ;; the last field is always relative to the current
              ;; tool's window.
              (let* ((window (nth 1 touch-screen-current-tool))
                     (relative-x-y (touch-screen-relative-xy position
                                                             window))
                     (initial-pos (nth 4 touch-screen-current-tool))
                     (initial-x-y (touch-screen-relative-xy initial-pos
                                                            window))
                     computed-distance computed-centrum)
                ;; Calculate the distance and centrum from this point
                ;; to the initial position of the current tool.
                (setq computed-distance (touch-screen-distance relative-x-y
                                                               initial-x-y)
                      computed-centrum (touch-screen-centrum relative-x-y
                                                             initial-x-y))
                ;; If computed-distance is zero, ignore this tap.
                (unless (zerop computed-distance)
                  (setq touch-screen-aux-tool (vector touchpoint window
                                                      position relative-x-y
                                                      computed-distance
                                                      computed-centrum
                                                      1.0 nil nil nil)))
                ;; When an auxiliary tool is pressed, any gesture
                ;; previously in progress must be terminated, so long
                ;; as it represents a gesture recognized from the
                ;; current tool's motion rather than ones detected by
                ;; this function from circumstances surrounding its
                ;; first press, such as the presence of a menu or
                ;; down-mouse-1 button beneath its first press.
                (unless (memq (nth 3 touch-screen-current-tool)
                              '(mouse-drag mouse-1-menu))
                  ;; Set the what field to the symbol `ancillary-tool'
                  ;; rather than nil, that mouse events may not be
                  ;; generated if no gesture is subsequently
                  ;; recognized; this, among others, prevents
                  ;; undesirable point movement (through the execution
                  ;; of `mouse-set-point') after both points are
                  ;; released without any gesture being detected.
                  (setcar (nthcdr 3 touch-screen-current-tool)
                          'ancillary-tool))))
          ;; Replace any previously ongoing gesture.  If POSITION has no
          ;; window or position, make it nil instead.
          (setq tool-list (and (windowp window)
                               (list touchpoint window
                                     (posn-x-y position)
                                     nil position
                                     nil nil nil nil
                                     (posn-x-y position)))
                touch-screen-current-tool tool-list)
          ;; Select the window underneath the event as the checks below
          ;; will look up keymaps and markers inside its buffer.
          (save-selected-window
            ;; Check if `touch-screen-extend-selection' is enabled,
            ;; the tap lies on the point or the mark, and the region
            ;; is active.  If that's the case, set the fourth element
            ;; of `touch-screen-current-tool' to `restart-drag', then
            ;; generate a `touchscreen-restart-drag' event.
            (when tool-list
              ;; tool-list is always non-nil where the selected window
              ;; matters.
              (select-window window)
              (when (and touch-screen-extend-selection
                         (or (eq point (point))
                             (eq point (mark)))
                         (region-active-p)
                         ;; Only restart drag-to-select if the tap
                         ;; falls on the same row as the selection.
                         ;; This prevents dragging from starting if
                         ;; the tap is below the last window line with
                         ;; text and `point' is at ZV, as the user
                         ;; most likely meant to scroll the window
                         ;; instead.
                         (when-let* ((posn-point (posn-at-point point))
                                     (posn-row (cdr
                                                (posn-col-row posn-point))))
                           (eq (cdr (posn-col-row position)) posn-row)))
                ;; Indicate that a drag is about to restart.
                (setcar (nthcdr 3 tool-list) 'restart-drag)
                ;; Generate the `restart-drag' event.
                (throw 'input-event (list 'touchscreen-restart-drag
                                          position))))
            ;; Determine if there is a command bound to `down-mouse-1'
            ;; at the position of the tap and that command is not a
            ;; command whose functionality is replaced by the
            ;; long-press mechanism.  If so, set the fourth element of
            ;; `touch-screen-current-tool' to `mouse-drag' and
            ;; generate an emulated `mouse-1' event.
            ;;
            ;; If the command in question is a keymap, set that
            ;; element to `mouse-1-menu' instead of `mouse-drag', and
            ;; don't generate a `down-mouse-1' event immediately.
            ;; Instead, wait for the touch point to be released.
            (if (and tool-list
                     (and (setq binding
                                (key-binding (if prefix
                                                 (vector prefix
                                                         'down-mouse-1)
                                               [down-mouse-1])
                                             t nil position))
                          (not (and (symbolp binding)
                                    (get binding 'ignored-mouse-command)))))
                (if (or (keymapp binding)
                        (and (symbolp binding)
                             (get binding 'mouse-1-menu-command)))
                    ;; binding is a keymap, or a command that does
                    ;; almost the same thing.  If a `mouse-1' event is
                    ;; generated after the keyboard command loop
                    ;; displays it as a menu, that event could cause
                    ;; unwanted commands to be run.  Set what to
                    ;; `mouse-1-menu' instead and wait for the up
                    ;; event to display the menu.
                    (setcar (nthcdr 3 tool-list) 'mouse-1-menu)
                  (progn (setcar (nthcdr 3 tool-list) 'mouse-drag)
                         (throw 'input-event (list 'down-mouse-1 position))))
              (and point
                   ;; Start the long-press timer.
                   (touch-screen-handle-timeout nil)))))))
     ((eq (car event) 'touchscreen-update)
      (unless touch-screen-current-tool
        ;; If a stray touchscreen-update event arrives (most likely
        ;; from the menu bar), stop translating this sequence.
        (throw 'input-event nil))
      ;; The positions of tools currently pressed against the screen
      ;; have changed.  If there is a tool being tracked as part of a
      ;; gesture, look it up in the list of tools.
      (if-let ((new-point (assq (car touch-screen-current-tool)
                                (cadr event))))
          (if touch-screen-aux-tool
              (touch-screen-handle-aux-point-update (cdr new-point)
                                                    (car new-point))
            (touch-screen-handle-point-update new-point))
        ;; If the current tool exists no longer, a touchscreen-end
        ;; event is certain to have been disregarded.  So that
        ;; touchscreen gesture translation might continue as usual
        ;; after this aberration to the normal flow of events, delete
        ;; the current tool now.
        (when touch-screen-current-timer
          ;; Cancel the touch screen long-press timer, if it is still
          ;; there by any chance.
          (cancel-timer touch-screen-current-timer)
          (setq touch-screen-current-timer nil))
        ;; Don't call `touch-screen-handle-point-up' when terminating
        ;; translation abnormally.
        (setq touch-screen-current-tool nil
              ;; Delete the ancillary tool while at it.
              touch-screen-aux-tool nil)
        (message "Current touch screen tool vanished!"))
      ;; Check for updates to any ancillary point being monitored.
      (when touch-screen-aux-tool
        (let ((new-point (assq (aref touch-screen-aux-tool 0)
                               (cadr event))))
          (when new-point
            (touch-screen-handle-aux-point-update (cdr new-point)
                                                  (car new-point))))))
     ((eq (car event) 'touchscreen-end)
      ;; A tool has been removed from the screen.  If it is the tool
      ;; currently being tracked, clear `touch-screen-current-tool'.
      (when (eq (caadr event) (car touch-screen-current-tool))
        ;; Cancel the touch screen long-press timer, if it is still
        ;; there by any chance.
        (when touch-screen-current-timer
          (cancel-timer touch-screen-current-timer)
          (setq touch-screen-current-timer nil))
        (let ((old-aux-tool touch-screen-aux-tool))
          (unwind-protect
              ;; Don't perform any actions associated with releasing the
              ;; tool if the touch sequence was intercepted by another
              ;; program.
              (if (caddr event)
                  (setq touch-screen-current-tool nil)
                (touch-screen-handle-point-up (cadr event) prefix))
            ;; If an ancillary tool is present the function call above
            ;; will merely transfer information from it into the current
            ;; tool list, thereby rendering it the new current tool,
            ;; until such time as it too is released.
            (when (not (and old-aux-tool (not touch-screen-aux-tool)))
              ;; Make sure the tool list is cleared even if
              ;; `touch-screen-handle-point-up' throws.
              (setq touch-screen-current-tool nil)))))
      ;; If it is rather the ancillary tool, delete its vector.  No
      ;; further action is required, for the next update received will
      ;; resume regular gesture recognition.
      ;;
      ;; The what field in touch-screen-current-tool is set to a
      ;; signal value when the ancillary tool is pressed, so gesture
      ;; recognition will commence with a clean slate, save for when
      ;; the first touch landed atop a menu or some other area
      ;; down-mouse-1 was bound.
      ;;
      ;; Gesture recognition will be inhibited in that case, so that
      ;; mouse menu or mouse motion events are generated in its place
      ;; as they would be were no ancillary tool ever pressed.
      (when (and touch-screen-aux-tool
                 (eq (caadr event) (aref touch-screen-aux-tool 0)))
        (setq touch-screen-aux-tool nil))
      ;; Throw to the key translation function.
      (throw 'input-event nil)))))

;; Mark `mouse-drag-region' as ignored for the purposes of mouse click
;; emulation.

(put 'mouse-drag-region 'ignored-mouse-command t)

(defun touch-screen-translate-touch (prompt)
  "Translate touch screen events into a sequence of mouse events.
PROMPT is the prompt string given to `read-key-sequence', or nil
if this function is being called from the keyboard command loop.
Value is a new key sequence.

Read the touch screen event within `current-key-remap-sequence'
and give it to `touch-screen-handle-touch'.  Return any key
sequence signaled.

If `touch-screen-handle-touch' does not signal for an event to be
returned after the last element of the key sequence is read,
continue reading touch screen events until
`touch-screen-handle-touch' signals.  Return a sequence
consisting of the first event encountered that is not a touch
screen event.

In addition to non-touchscreen events read, key sequences
returned may contain any one of the following events:

  (touchscreen-scroll WINDOW DX DY)

where WINDOW specifies a window to scroll, and DX and DY are
integers describing how many pixels to be scrolled horizontally
and vertically,

  (touchscreen-hold POSN)
  (touchscreen-drag POSN)

where POSN is the position of the long-press or touchpoint
motion,

  (touchscreen-restart-drag POSN)

where POSN is the position of the tap,

  (down-mouse-1 POSN)
  (drag-mouse-1 POSN)

where POSN is the position of the mouse button press or click,

  (mouse-1 POSN)
  (mouse-2 POSN)

where POSN is the position of the mouse click, either `mouse-2'
if POSN is on a link or a button, or `mouse-1' otherwise."
  (unwind-protect
      ;; Save the virtual function key if this is a mode line event.
      (let* ((prefix-specified
              ;; Virtual prefix keys can be nil for events that fall
              ;; outside a frame or within its internal border.
              (> (length current-key-remap-sequence) 1))
             (prefix (and prefix-specified
                          (aref current-key-remap-sequence 0)))
             (touch-screen-translate-prompt prompt)
             (event (catch 'input-event
                      ;; First, process the one event already within
                      ;; `current-key-remap-sequence'.
                      (touch-screen-handle-touch
                       (aref current-key-remap-sequence
                             (if prefix-specified 1 0))
                       prefix)
                      ;; Next, continue reading input events.
                      (while t
                        (let ((event1 (read-event)))
                          ;; If event1 is a virtual function key, make
                          ;; it the new prefix.
                          (if (memq event1 '(mode-line tab-line nil
                                             vertical-line
                                             header-line tool-bar tab-bar
                                             left-fringe right-fringe
                                             left-margin right-margin
                                             right-divider bottom-divider))
                              (setq prefix event1)
                            ;; If event1 is not a touch screen event,
                            ;; return it.
                            (if (not (memq (car-safe event1)
                                           '(touchscreen-begin
                                             touchscreen-end
                                             touchscreen-update)))
                                (throw 'input-event event1)
                              ;; Process this event as well.
                              (touch-screen-handle-touch event1 prefix))))))))
        ;; Return a key sequence consisting of event
        ;; or an empty vector if it is nil, meaning that
        ;; no key events have been translated.
        (if event (or (and prefix (consp event)
                           ;; Only generate virtual function keys for
                           ;; mouse events.
                           (memq (car event)
                                 '(down-mouse-1 mouse-1
                                   mouse-2 mouse-movement))
                           ;; If this is a mode line event, then
                           ;; generate the appropriate function key.
                           (vector prefix event))
                      (vector event))
          ""))
    ;; Cancel the touch screen long-press timer, if it is still there
    ;; by any chance.  If the timer is to operate correctly, it must
    ;; fire within the catch block above.
    (when touch-screen-current-timer
      (cancel-timer touch-screen-current-timer)
      (setq touch-screen-current-timer nil))))

(define-key function-key-map [touchscreen-begin]
            #'touch-screen-translate-touch)
(define-key function-key-map [touchscreen-update]
            #'touch-screen-translate-touch)
(define-key function-key-map [touchscreen-end]
            #'touch-screen-translate-touch)

(define-key function-key-map [mode-line touchscreen-begin]
            #'touch-screen-translate-touch)
(define-key function-key-map [mode-line touchscreen-end]
            #'touch-screen-translate-touch)

;; These are used to translate events sent from the internal border or
;; from outside the frame.

(define-key function-key-map [nil touchscreen-begin]
            #'touch-screen-translate-touch)
(define-key function-key-map [nil touchscreen-end]
            #'touch-screen-translate-touch)

(define-key function-key-map [header-line touchscreen-begin]
            #'touch-screen-translate-touch)
(define-key function-key-map [header-line touchscreen-end]
            #'touch-screen-translate-touch)

(define-key function-key-map [bottom-divider touchscreen-begin]
            #'touch-screen-translate-touch)
(define-key function-key-map [bottom-divider touchscreen-end]
            #'touch-screen-translate-touch)

(define-key function-key-map [right-divider touchscreen-begin]
            #'touch-screen-translate-touch)
(define-key function-key-map [right-divider touchscreen-end]
            #'touch-screen-translate-touch)

(define-key function-key-map [right-divider touchscreen-begin]
            #'touch-screen-translate-touch)
(define-key function-key-map [right-divider touchscreen-end]
            #'touch-screen-translate-touch)

(define-key function-key-map [left-fringe touchscreen-begin]
            #'touch-screen-translate-touch)
(define-key function-key-map [left-fringe touchscreen-end]
            #'touch-screen-translate-touch)

(define-key function-key-map [right-fringe touchscreen-begin]
            #'touch-screen-translate-touch)
(define-key function-key-map [right-fringe touchscreen-end]
            #'touch-screen-translate-touch)

(define-key function-key-map [left-margin touchscreen-begin]
            #'touch-screen-translate-touch)
(define-key function-key-map [left-margin touchscreen-end]
            #'touch-screen-translate-touch)

(define-key function-key-map [right-margin touchscreen-begin]
            #'touch-screen-translate-touch)
(define-key function-key-map [right-margin touchscreen-end]
            #'touch-screen-translate-touch)

(define-key function-key-map [tool-bar touchscreen-begin]
            #'touch-screen-translate-touch)
(define-key function-key-map [tool-bar touchscreen-end]
            #'touch-screen-translate-touch)

(define-key function-key-map [tab-bar touchscreen-begin]
            #'touch-screen-translate-touch)
(define-key function-key-map [tab-bar touchscreen-end]
            #'touch-screen-translate-touch)

(define-key function-key-map [tab-line touchscreen-begin]
            #'touch-screen-translate-touch)
(define-key function-key-map [tab-line touchscreen-end]
            #'touch-screen-translate-touch)

(define-key function-key-map [vertical-line touchscreen-begin]
            #'touch-screen-translate-touch)
(define-key function-key-map [vertical-line touchscreen-end]
            #'touch-screen-translate-touch)

(define-key function-key-map [nil touchscreen-begin]
            #'touch-screen-translate-touch)
(define-key function-key-map [nil touchscreen-end]
            #'touch-screen-translate-touch)


;; Exports.  These functions are intended for use externally.

;;;###autoload
(defun touch-screen-track-tap (event &optional update data threshold)
  "Track a single tap starting from EVENT.
EVENT should be a `touchscreen-begin' event.

Read touch screen events until a `touchscreen-end' event is
received with the same ID as in EVENT.  If UPDATE is non-nil and
a `touchscreen-update' event is received in the mean time and
contains a touch point with the same ID as in EVENT, call UPDATE
with that event and DATA.

If THRESHOLD is non-nil, enforce a threshold of movement that is
either itself or 10 pixels when it is not a number.  If the
aforementioned touch point moves beyond that threshold on any
axis, return nil immediately, and further resume mouse event
translation for the touch point at hand.

Return nil immediately if any other kind of event is received;
otherwise, return t once the `touchscreen-end' event arrives."
  (let ((disable-inhibit-text-conversion t)
        (threshold (and threshold (or (and (numberp threshold)
                                           threshold)
                                      10)))
        (original-x-y (posn-x-y (cdadr event)))
        (original-window (posn-window (cdadr event))))
    (catch 'finish
      (while t
        (let ((new-event (read-event nil))
              touch-point)
          (cond
           ((eq (car-safe new-event) 'touchscreen-update)
            (when (setq touch-point (assq (caadr event) (cadr new-event)))
              (when update
                (funcall update new-event data))
              (when threshold
                (setq touch-point (cdr touch-point))
                ;; Detect the touch point moving past the threshold.
                (let* ((x-y (touch-screen-relative-xy touch-point
                                                      original-window))
                       (x (car x-y)) (y (cdr x-y)))
                  (when (or (> (abs (- x (car original-x-y))) threshold)
                            (> (abs (- y (cdr original-x-y))) threshold))
                    ;; Resume normal touch-screen to mouse event
                    ;; translation for this touch sequence by
                    ;; supplying both the event starting it and the
                    ;; motion event that overstepped the threshold to
                    ;; touch-screen-handle-touch.
                    (touch-screen-handle-touch event nil t)
                    (touch-screen-handle-touch new-event nil t)
                    (throw 'finish nil))))))
           ((eq (car-safe new-event) 'touchscreen-end)
            (throw 'finish
                   ;; Now determine whether or not the `touchscreen-end'
                   ;; event has the same ID as EVENT.  If it doesn't,
                   ;; then this is another touch, so return nil.
                   (eq (caadr event) (caadr new-event))))
           (t (throw 'finish nil))))))))

;;;###autoload
(defun touch-screen-track-drag (event update &optional data)
  "Track a single drag starting from EVENT.
EVENT should be a `touchscreen-begin' event.

Read touch screen events until a `touchscreen-end' event is
received with the same ID as in EVENT.  For each
`touchscreen-update' event received in the mean time containing a
touch point with the same ID as in EVENT, call UPDATE with the
touch point in event and DATA, once the touch point has moved
significantly by at least 5 pixels from where it was in EVENT.

Return nil immediately if any other kind of event is received;
otherwise, return either t or `no-drag' once the
`touchscreen-end' event arrives; return `no-drag' returned if the
touch point in EVENT did not move significantly, and t otherwise."
  (let ((return-value 'no-drag)
        (start-xy (touch-screen-relative-xy (cdadr event)
                                            'frame))
        (disable-inhibit-text-conversion t))
    (catch 'finish
      (while t
        (let ((new-event (read-event nil)))
          (cond
           ((eq (car-safe new-event) 'touchscreen-update)
            (when-let* ((tool (assq (caadr event) (nth 1 new-event)))
                        (xy (touch-screen-relative-xy (cdr tool) 'frame)))
              (when (or (> (- (car xy) (car start-xy)) 5)
                        (< (- (car xy) (car start-xy)) -5)
                        (> (- (cdr xy) (cdr start-xy)) 5)
                        (< (- (cdr xy) (cdr start-xy)) -5))
                (setq return-value t))
              (when (and update tool (eq return-value t))
                (funcall update new-event data))))
           ((eq (car-safe new-event) 'touchscreen-end)
            (throw 'finish
                   ;; Now determine whether or not the `touchscreen-end'
                   ;; event has the same ID as EVENT.  If it doesn't,
                   ;; then this is another touch, so return nil.
                   (and (eq (caadr event) (caadr new-event))
                        return-value)))
           (t (throw 'finish nil))))))))



;;; Event handling exports.  These functions are intended for use by
;;; Lisp commands bound to touch screen gesture events.

;;;###autoload
(defun touch-screen-inhibit-drag ()
  "Inhibit subsequent `touchscreen-drag' events from being sent.
Prevent `touchscreen-drag' and translated mouse events from being
sent until the touch sequence currently being translated ends.
Must be called from a command bound to a `touchscreen-hold' or
`touchscreen-drag' event."
  (let* ((tool touch-screen-current-tool)
         (current-what (nth 4 tool)))
    ;; Signal an error if no hold or drag is in progress.
    (when (and (not (eq current-what 'hold)
                    (eq current-what 'drag)))
      (error "Calling `touch-screen-inhibit-drag' outside hold or drag"))
    ;; Now set the fourth element of tool to `command-inhibit'.
    (setcar (nthcdr 3 tool) 'command-inhibit)))



(provide 'touch-screen)

;;; touch-screen ends here