Skip to content

Commit 699df0a

Browse files
Add ability to enable/disable AssistiveTouch soft "home" button. iOS 11+ (danielpaulus#148)
* Add ability to enable/disable AssistiveTouch soft "home" button. iOS 11+ Using the same technique already used for querying and setting the device language and locale, go-ios can now enable and disable AssistiveTouch using the the magic boolean com.apple.Accessibility.AssistiveTouchEnabledByiTunes This commit slightly modifies the existing SetValueForDomain() and related methods and structs to take an interface{} value (rather than a string) in order to pass a raw bool to lockdown. This commit also replaces a few inconsistently used tabs in the verbose command-line "help" output. Thank you to the team at alibaba/taobao-iphone-device (tidevice) for sharing the magic word to make this happen. Validated on iPhone 5s, 6, 6s, 6s plus, 7, 8, se 1st gen, se 2nd gen, and 11 pro max. iOS 11.4.1, 12.1, 12.5.5, 13.4, 13.6, 14.0.1, 14.7, 15.0, 15.3, and 16.0.1 beta. Failed on an iPhone 6+ running iOS 10.2.1 (Exits with error including hint: “Is this device running iOS 11+?”) * Add iOS11+ version check to "assistivetouch" command, with --force override to attempt anyway. Having tested on a grand total of 1 iOS10.x device (an iPhone 6s plus), we speculate that the technique used to enable/disable assistive touch does not work on devices older than iOS 11. This commit adds a fail-fast check for versions older than iOS 11. However, since only 1 device has been tested, (and older iPad OS has not been tested at all), I have also added a "--force" override so that the end-user can still give it a try. The "--force" option also forces SetValue() to be called even if it would not cause an apparent change. (By default, if assistive touch is already disabled, and the user asks to disable it, nothing happens). "--force" is mostly meant as a way to continue testing older device functionality without having to have a custom debug build of go-ios. If we can definitively prove that iOS12 is the minimum, the --force option can be removed. Co-authored-by: danielpaulus <[email protected]>
1 parent a602952 commit 699df0a

File tree

4 files changed

+134
-8
lines changed

4 files changed

+134
-8
lines changed

ios/lockdown_assistivetouch.go

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
package ios
2+
3+
import log "github.com/sirupsen/logrus"
4+
import "fmt"
5+
6+
const accessibilityDomain = "com.apple.Accessibility"
7+
const assistiveTouchKey = "AssistiveTouchEnabledByiTunes"
8+
9+
// EnableAssistiveTouch creates a new lockdown session for the device and enables or disables
10+
// AssistiveTouch (the on-screen software home button), by using the special key AssistiveTouchEnabledByiTunes.
11+
// Setting to true will enable AssistiveTouch
12+
// Setting to false will disable AssistiveTouch, regardless of whether it was previously-enabled through
13+
// a non-iTunes-related method.
14+
func SetAssistiveTouch(device DeviceEntry, enabled bool) error {
15+
lockDownConn, err := ConnectLockdownWithSession(device)
16+
if err != nil {
17+
return err
18+
}
19+
log.Debugf("Setting %s: %t", assistiveTouchKey, enabled)
20+
defer lockDownConn.Close()
21+
err = lockDownConn.SetValueForDomain(assistiveTouchKey, accessibilityDomain, enabled)
22+
return err
23+
}
24+
func GetAssistiveTouch(device DeviceEntry) (bool, error) {
25+
lockDownConn, err := ConnectLockdownWithSession(device)
26+
27+
if err != nil {
28+
return false, err
29+
}
30+
defer lockDownConn.Close()
31+
32+
enabledIntf, err := lockDownConn.GetValueForDomain(assistiveTouchKey, accessibilityDomain)
33+
34+
if err != nil {
35+
return false, err
36+
}
37+
if enabledIntf == nil{
38+
// In testing, nil was returned in only one case, on an iOS 14.7 device that should already have been paired.
39+
// Calling SetAssistiveTouch() directly returned the somewhat more useful error: SetProhibited
40+
// After re-running "go-ios pair", full functionality returned.
41+
return false, fmt.Errorf("Received null response when querying %s.%s. Try re-pairing the device.", accessibilityDomain, assistiveTouchKey)
42+
}
43+
enabledUint64, ok := enabledIntf.(uint64)
44+
if !ok {
45+
// On iOS 10.x at least, "false" is returned, perhaps for any key at all. Attempting to manipulate AssistiveTouchEnabledByiTunes had no effect
46+
return false, fmt.Errorf("Expected unit64 0 or 1 when querying %s.%s, but received %T:%+v. Is this device running iOS 11+?", accessibilityDomain, assistiveTouchKey, enabledIntf, enabledIntf)
47+
} else if enabledUint64 != 0 && enabledUint64 != 1{
48+
// So far this has never happened
49+
return false, fmt.Errorf("Expected a value of 0 or 1 for %s.%s, received %d instead!", accessibilityDomain, assistiveTouchKey, enabledUint64)
50+
}
51+
52+
return enabledUint64 == 1, nil
53+
}

ios/lockdown_value.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ type valueRequest struct {
114114
Key string `plist:"Key,omitempty"`
115115
Request string
116116
Domain string `plist:"Domain,omitempty"`
117-
Value string `plist:"Value,omitempty"`
117+
Value interface{} `plist:"Value,omitempty"`
118118
}
119119

120120
func newGetValue(key string) valueRequest {
@@ -126,7 +126,7 @@ func newGetValue(key string) valueRequest {
126126
return data
127127
}
128128

129-
func newSetValue(key string, domain string, value string) valueRequest {
129+
func newSetValue(key string, domain string, value interface{}) valueRequest {
130130
data := valueRequest{
131131
Label: "go.ios.control",
132132
Key: key,
@@ -215,7 +215,7 @@ func (lockDownConn *LockDownConnection) GetValueForDomain(key string, domain str
215215
}
216216

217217
//SetValueForDomain sets the string value for the lockdown key and domain. If the device returns an error it will be returned as a go error.
218-
func (lockDownConn *LockDownConnection) SetValueForDomain(key string, domain string, value string) error {
218+
func (lockDownConn *LockDownConnection) SetValueForDomain(key string, domain string, value interface{}) error {
219219
gv := newSetValue(key, domain, value)
220220
lockDownConn.Send(gv)
221221
resp, err := lockDownConn.ReadMessage()

ios/utils.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -94,16 +94,18 @@ func PathExists(path string) (bool, error) {
9494
return false, err
9595
}
9696

97-
//IOS14 semver.MustParse("14.0")
9897
func IOS14() *semver.Version {
9998
return semver.MustParse("14.0")
10099
}
101100

102-
//IOS12 semver.MustParse("12.0")
103101
func IOS12() *semver.Version {
104102
return semver.MustParse("12.0")
105103
}
106104

105+
func IOS11() *semver.Version {
106+
return semver.MustParse("11.0")
107+
}
108+
107109
//FixWindowsPaths replaces backslashes with forward slashes and removes the X: style
108110
//windows drive letters
109111
func FixWindowsPaths(path string) string {

main.go

Lines changed: 74 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,7 @@ Usage:
106106
ios setlocation [options] [--lat=<lat>] [--lon=<lon>]
107107
ios setlocationgpx [options] [--gpxfilepath=<gpxfilepath>]
108108
ios resetlocation [options]
109+
ios assistivetouch (enable | disable | toggle | get) [--force] [options]
109110
110111
Options:
111112
-v --verbose Enable Debug Logging.
@@ -186,9 +187,10 @@ The commands work as following:
186187
ios reboot [options] Reboot the given device
187188
ios -h | --help Prints this screen.
188189
ios --version | version [options] Prints the version
189-
ios setlocation [options] [--lat=<lat>] [--lon=<lon>] Updates the location of the device to the provided by latitude and longitude coordinates. Example: setlocation --lat=40.730610 --lon=-73.935242
190-
ios setlocationgpx [options] [--gpxfilepath=<gpxfilepath>] Updates the location of the device based on the data in a GPX file. Example: setlocationgpx --gpxfilepath=/home/username/location.gpx
191-
ios resetlocation [options] Resets the location of the device to the actual one
190+
ios setlocation [options] [--lat=<lat>] [--lon=<lon>] Updates the location of the device to the provided by latitude and longitude coordinates. Example: setlocation --lat=40.730610 --lon=-73.935242
191+
ios setlocationgpx [options] [--gpxfilepath=<gpxfilepath>] Updates the location of the device based on the data in a GPX file. Example: setlocationgpx --gpxfilepath=/home/username/location.gpx
192+
ios resetlocation [options] Resets the location of the device to the actual one
193+
ios assistivetouch (enable | disable | toggle | get) [--force] [options] Enables, disables, toggles, or returns the state of the "AssistiveTouch" software home-screen button. iOS 11+ only (Use --force to try on older versions).
192194
193195
`, version)
194196
arguments, err := docopt.ParseDoc(usage)
@@ -328,6 +330,29 @@ The commands work as following:
328330
return
329331
}
330332

333+
b, _ = arguments.Bool("assistivetouch")
334+
if b {
335+
force, _ := arguments.Bool("--force")
336+
b, _ = arguments.Bool("enable")
337+
if b {
338+
assistiveTouch(device, "enable", force)
339+
}
340+
b, _ = arguments.Bool("disable")
341+
if b {
342+
assistiveTouch(device, "disable", force)
343+
}
344+
b, _ = arguments.Bool("toggle")
345+
if b {
346+
assistiveTouch(device, "toggle" , force)
347+
}
348+
b, _ = arguments.Bool("get")
349+
if b {
350+
assistiveTouch(device, "get", force)
351+
}
352+
}
353+
354+
355+
331356
b, _ = arguments.Bool("dproxy")
332357
if b {
333358
log.SetFormatter(&log.TextFormatter{})
@@ -920,6 +945,52 @@ func language(device ios.DeviceEntry, locale string, language string) {
920945
fmt.Println(convertToJSONString(lang))
921946
}
922947

948+
func assistiveTouch(device ios.DeviceEntry, operation string, force bool) {
949+
var enable bool
950+
951+
if !force {
952+
version, err := ios.GetProductVersion(device)
953+
exitIfError("failed getting device product version", err)
954+
955+
if version.LessThan(ios.IOS11()) {
956+
log.Errorf("iOS Version 11.0+ required to manipulate AssistiveTouch. iOS version: %s detected. Use --force to override.", version)
957+
os.Exit(1)
958+
}
959+
}
960+
961+
wasEnabled, err := ios.GetAssistiveTouch(device)
962+
963+
if err != nil{
964+
if force && ( operation == "enable" || operation == "disable" ) {
965+
log.WithFields(log.Fields{"error": err}).Warn("Failed getting current AssistiveTouch status. Continuing anyway.")
966+
} else{
967+
exitIfError("failed getting current AssistiveTouch status", err)
968+
}
969+
}
970+
971+
switch {
972+
case operation == "enable":
973+
enable = true
974+
case operation == "disable":
975+
enable = false
976+
case operation == "toggle":
977+
enable = !wasEnabled
978+
default: // get
979+
enable = wasEnabled
980+
}
981+
if operation != "get" && ( force || wasEnabled != enable ) {
982+
err = ios.SetAssistiveTouch(device, enable)
983+
exitIfError("failed setting AssistiveTouch", err)
984+
}
985+
if operation == "get" {
986+
if JSONdisabled {
987+
fmt.Printf("%t\n", enable)
988+
} else {
989+
fmt.Println(convertToJSONString(map[string]bool{"AssistiveTouchEnabled":enable}))
990+
}
991+
}
992+
}
993+
923994
func startAx(device ios.DeviceEntry) {
924995
go func() {
925996
deviceList, err := ios.ListDevices()

0 commit comments

Comments
 (0)