@@ -162,6 +162,18 @@ def open(self) -> Sequence[int]:
162162 self .state : dict = defaultdict (lambda : 0 )
163163 self .rumble = False
164164 self .touchpad_touch = False
165+ self .left_touchpad_touch = False
166+ self .right_touchpad_x = 0
167+ self .right_touchpad_y = 0
168+ self .left_touchpad_x = 0
169+ self .left_touchpad_y = 0
170+ self .tp1_owner = None # Track which touchpad owns TP1: 'right' or 'left'
171+
172+ # Touchpad mapping mode:
173+ # - True: Real DS5 behavior (persistent mapping, TP2-only becomes invisible)
174+ # - False: Practical behavior (auto-transfer to TP1, always visible)
175+ self .touchpad_persistent_mapping = True
176+
165177 curr = time .perf_counter ()
166178 self .touchpad_down = curr
167179 self .last_imu = curr
@@ -376,7 +388,8 @@ def consume(self, events: Sequence[Event]):
376388 code = ev ["code" ]
377389 match ev ["type" ]:
378390 case "axis" :
379- if not self .enable_touchpad and code .startswith ("touchpad" ):
391+ # Filter all touchpad events when touchpad is disabled
392+ if not self .enable_touchpad and code .startswith (("touchpad" , "left_touchpad" )):
380393 continue
381394 if self .left_motion :
382395 # Only left keep imu events for left motion
@@ -417,48 +430,36 @@ def consume(self, events: Sequence[Event]):
417430 )
418431 case "touchpad_x" :
419432 tc = self .touch_correction
420- x = int (
433+ self . right_touchpad_x = int (
421434 min (max (ev ["value" ], tc .x_clamp [0 ]), tc .x_clamp [1 ])
422435 * tc .x_mult
423436 + tc .x_ofs
424437 )
425- new_rep [self .ofs + 33 ] = x & 0xFF
426- new_rep [self .ofs + 34 ] = (new_rep [self .ofs + 34 ] & 0xF0 ) | (
427- x >> 8
428- )
438+ # Coordinate will be written by smart mapping logic at the end
429439 case "touchpad_y" :
430440 tc = self .touch_correction
431- y = int (
441+ self . right_touchpad_y = int (
432442 min (max (ev ["value" ], tc .y_clamp [0 ]), tc .y_clamp [1 ])
433443 * tc .y_mult
434444 + tc .y_ofs
435445 )
436- new_rep [self .ofs + 34 ] = (new_rep [self .ofs + 34 ] & 0x0F ) | (
437- (y & 0x0F ) << 4
438- )
439- new_rep [self .ofs + 35 ] = y >> 4
446+ # Coordinate will be written by smart mapping logic at the end
440447 case "left_touchpad_x" :
441448 tc = LEFT_TOUCH_CORRECTION
442- x = int (
449+ self . left_touchpad_x = int (
443450 min (max (ev ["value" ], tc .x_clamp [0 ]), tc .x_clamp [1 ])
444451 * tc .x_mult
445452 + tc .x_ofs
446453 )
447- new_rep [self .ofs + 37 ] = x & 0xFF
448- new_rep [self .ofs + 38 ] = (new_rep [self .ofs + 34 ] & 0xF0 ) | (
449- x >> 8
450- )
454+ # Coordinate will be written by smart mapping logic at the end
451455 case "left_touchpad_y" :
452456 tc = LEFT_TOUCH_CORRECTION
453- y = int (
457+ self . left_touchpad_y = int (
454458 min (max (ev ["value" ], tc .y_clamp [0 ]), tc .y_clamp [1 ])
455459 * tc .y_mult
456460 + tc .y_ofs
457461 )
458- new_rep [self .ofs + 38 ] = (new_rep [self .ofs + 34 ] & 0x0F ) | (
459- (y & 0x0F ) << 4
460- )
461- new_rep [self .ofs + 39 ] = y >> 4
462+ # Coordinate will be written by smart mapping logic at the end
462463 case "gyro_ts" | "accel_ts" | "imu_ts" :
463464 send = True
464465 self .last_imu = time .perf_counter ()
@@ -470,7 +471,8 @@ def consume(self, events: Sequence[Event]):
470471 if self .left_motion :
471472 # skip buttons for left motion
472473 continue
473- if not self .enable_touchpad and code .startswith ("touchpad" ):
474+ # Filter all touchpad button events when touchpad is disabled
475+ if not self .enable_touchpad and code .startswith (("touchpad" , "left_touchpad" )):
474476 continue
475477 if (self .paddles_to_clicks == "top" and code == "extra_l1" ) or (
476478 self .paddles_to_clicks == "bottom" and code == "extra_l2"
@@ -498,7 +500,15 @@ def consume(self, events: Sequence[Event]):
498500
499501 # Fix touchpad click requiring touch
500502 if code == "touchpad_touch" :
503+ # Track TP1 owner for first-come-first-served mapping
504+ if ev ["value" ] and not self .touchpad_touch and self .tp1_owner is None :
505+ self .tp1_owner = "right"
501506 self .touchpad_touch = ev ["value" ]
507+ if code == "left_touchpad_touch" :
508+ # Track TP1 owner for first-come-first-served mapping
509+ if ev ["value" ] and not self .left_touchpad_touch and self .tp1_owner is None :
510+ self .tp1_owner = "left"
511+ self .left_touchpad_touch = ev ["value" ]
502512 if code == "touchpad_left" :
503513 set_button (
504514 new_rep ,
@@ -539,6 +549,100 @@ def consume(self, events: Sequence[Event]):
539549 max (ev ["value" ] // 10 , 0 )
540550 )
541551
552+ # Smart touchpad mapping: ensure TP2 only activates when TP1 is also active
553+ # Uses first-come-first-served principle: first touched pad owns TP1
554+ def write_tp (offset , x , y ):
555+ """Helper function to write touchpoint coordinates to report"""
556+ new_rep [offset + 1 ] = x & 0xFF
557+ new_rep [offset + 2 ] = (new_rep [offset + 2 ] & 0xF0 ) | (x >> 8 )
558+ new_rep [offset + 2 ] = (new_rep [offset + 2 ] & 0x0F ) | ((y & 0x0F ) << 4 )
559+ new_rep [offset + 3 ] = y >> 4
560+
561+ if self .touchpad_touch or self .left_touchpad_touch :
562+ # Check if both or only one touchpad is touching
563+ both_touching = self .touchpad_touch and self .left_touchpad_touch
564+
565+ if both_touching :
566+ # Both touching: use tp1_owner to decide mapping
567+ if self .tp1_owner == "right" :
568+ # Right owns TP1: TP1 = right, TP2 = left
569+ tp1_x , tp1_y = self .right_touchpad_x , self .right_touchpad_y
570+ tp2_x , tp2_y = self .left_touchpad_x , self .left_touchpad_y
571+ tp1_is_left = False
572+ else : # "left"
573+ # Left owns TP1: TP1 = left, TP2 = right
574+ tp1_x , tp1_y = self .left_touchpad_x , self .left_touchpad_y
575+ tp2_x , tp2_y = self .right_touchpad_x , self .right_touchpad_y
576+ tp1_is_left = True
577+
578+ # Write both TP1 and TP2
579+ write_tp (self .ofs + 32 , tp1_x , tp1_y )
580+ write_tp (self .ofs + 36 , tp2_x , tp2_y )
581+
582+ # Set TP1 touch status if it's the left touchpad
583+ if tp1_is_left :
584+ new_rep [self .ofs + 32 ] = new_rep [self .ofs + 32 ] & 0x7F
585+ else :
586+ # Only one touching: behavior depends on mapping mode
587+ if self .touchpad_persistent_mapping :
588+ # Real DS5 mode: Keep persistent mapping
589+ # Whichever touchpad is still touching keeps its assigned slot
590+ if self .touchpad_touch :
591+ # Right touchpad is touching
592+ if self .tp1_owner == "right" :
593+ # Right owns TP1: write to TP1
594+ write_tp (self .ofs + 32 , self .right_touchpad_x , self .right_touchpad_y )
595+ # Touch status already set by set_button (touchpad_touch -> TP1)
596+ # Clear TP2 (left was released)
597+ new_rep [self .ofs + 36 ] = new_rep [self .ofs + 36 ] | 0x80
598+ else :
599+ # Right owns TP2: write to TP2 (will be invisible in Steam)
600+ write_tp (self .ofs + 36 , self .right_touchpad_x , self .right_touchpad_y )
601+ # Need to manually set TP2 touch status (touchpad_touch -> TP1, not TP2)
602+ new_rep [self .ofs + 36 ] = new_rep [self .ofs + 36 ] & 0x7F
603+ # Clear TP1 (left was released)
604+ new_rep [self .ofs + 32 ] = new_rep [self .ofs + 32 ] | 0x80
605+ else :
606+ # Left touchpad is touching
607+ if self .tp1_owner == "left" :
608+ # Left owns TP1: write to TP1
609+ write_tp (self .ofs + 32 , self .left_touchpad_x , self .left_touchpad_y )
610+ new_rep [self .ofs + 32 ] = new_rep [self .ofs + 32 ] & 0x7F
611+ # Clear TP2 (right was released)
612+ new_rep [self .ofs + 36 ] = new_rep [self .ofs + 36 ] | 0x80
613+ else :
614+ # Left owns TP2: write to TP2 (will be invisible in Steam)
615+ write_tp (self .ofs + 36 , self .left_touchpad_x , self .left_touchpad_y )
616+ # Touch status already set by set_button (left_touchpad_touch -> TP2)
617+ # Clear TP1 (right was released)
618+ new_rep [self .ofs + 32 ] = new_rep [self .ofs + 32 ] | 0x80
619+ else :
620+ # Practical mode: Auto-transfer to TP1 for visibility
621+ if self .touchpad_touch :
622+ tp1_x , tp1_y = self .right_touchpad_x , self .right_touchpad_y
623+ tp1_is_left = False
624+ # Transfer ownership only if it changed
625+ if self .tp1_owner != "right" :
626+ self .tp1_owner = "right"
627+ else :
628+ tp1_x , tp1_y = self .left_touchpad_x , self .left_touchpad_y
629+ tp1_is_left = True
630+ # Transfer ownership only if it changed
631+ if self .tp1_owner != "left" :
632+ self .tp1_owner = "left"
633+
634+ # Write to TP1
635+ write_tp (self .ofs + 32 , tp1_x , tp1_y )
636+
637+ # Set TP1 touch status if it's the left touchpad
638+ if tp1_is_left :
639+ new_rep [self .ofs + 32 ] = new_rep [self .ofs + 32 ] & 0x7F
640+ else :
641+ # Both touchpads are not touching: reset and clear
642+ self .tp1_owner = None
643+ new_rep [self .ofs + 32 ] = new_rep [self .ofs + 32 ] | 0x80 # Clear TP1: bit7=1 means not touching
644+ new_rep [self .ofs + 36 ] = new_rep [self .ofs + 36 ] | 0x80 # Clear TP2: bit7=1 means not touching
645+
542646 # Cache
543647 # Caching can cause issues since receivers expect reports
544648 # at least a couple of times per second
0 commit comments