mirror of
https://github.com/priyanshujain/sanderling.git
synced 2026-10-02 19:17:10 +00:00
feat(ioscompanion): pre-grant pasteboard access so unicode input skips the OS prompt
This commit is contained in:
1 parent
8d38210da9
commit
edd70b661b
1 file changed
+43
-8
@@ -82,7 +82,6 @@ type Driver struct {
|
||||
set bool
|
||||
}
|
||||
|
||||
pastePrimed bool
|
||||
clearStateWarned bool
|
||||
|
||||
// restart rebuilds the transport in place after a connection-level failure.
|
||||
@@ -100,6 +99,10 @@ type Driver struct {
|
||||
// A seam so tests skip the simctl shell-outs.
|
||||
reinstallApp func(ctx context.Context) error
|
||||
|
||||
// grantPaste pre-authorizes the app's pasteboard access. A seam so tests
|
||||
// skip the sqlite shell-out.
|
||||
grantPaste func(ctx context.Context) error
|
||||
|
||||
// idleClock drives WaitForIdle's settle poll. A seam so tests substitute a
|
||||
// fake clock and avoid the real settle cap.
|
||||
idleClock Clock
|
||||
@@ -159,6 +162,7 @@ func New(ctx context.Context, options Options) (*Driver, error) {
|
||||
driverInstance.restart = driverInstance.respawnAndRedial
|
||||
driverInstance.resetContainer = driverInstance.resetDataContainer
|
||||
driverInstance.reinstallApp = driverInstance.simctlReinstall
|
||||
driverInstance.grantPaste = driverInstance.grantPasteboardAccess
|
||||
driverInstance.processContext, driverInstance.processCancel = context.WithCancel(ctx)
|
||||
|
||||
if err := driverInstance.bringUp(ctx); err != nil {
|
||||
@@ -292,18 +296,22 @@ func (d *Driver) Launch(ctx context.Context, bundleID string, clearState bool, e
|
||||
}
|
||||
}
|
||||
|
||||
// Grant the app pasteboard access before it runs so unicode input (which
|
||||
// must go through the pasteboard, since HID cannot express it) never trips
|
||||
// the iOS paste-permission prompt. clearState reinstall resets the grant,
|
||||
// so it is reapplied on every launch. Best effort: if it fails, the paste
|
||||
// path still handles the prompt, just slower.
|
||||
if d.bundleID != "" {
|
||||
if err := d.grantPaste(ctx); err != nil {
|
||||
fmt.Fprintf(d.output, "grant pasteboard access failed (continuing): %v\n", err)
|
||||
}
|
||||
}
|
||||
|
||||
if err := d.withRecovery(ctx, func() error {
|
||||
return d.companion.Launch(ctx, d.bundleID, true)
|
||||
}); err != nil {
|
||||
return fmt.Errorf("launch %s: %w", d.bundleID, err)
|
||||
}
|
||||
|
||||
if !d.pastePrimed {
|
||||
if err := warmUpPaste(ctx, d.makeRunner()); err != nil {
|
||||
fmt.Fprintf(d.output, "paste warm-up failed (continuing): %v\n", err)
|
||||
}
|
||||
d.pastePrimed = true
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -336,6 +344,33 @@ func (d *Driver) simctlReinstall(ctx context.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// grantPasteboardAccess authorizes the app to read the pasteboard without the
|
||||
// iOS permission prompt, by writing an allow row into the simulator's privacy
|
||||
// (TCC) database. This is the simulator counterpart to `simctl privacy grant`,
|
||||
// which does not expose the pasteboard service. Without it, every unicode input
|
||||
// (which must paste, since HID cannot express unicode) blocks on a modal that
|
||||
// costs seconds; with it the paste lands in one frame.
|
||||
func (d *Driver) grantPasteboardAccess(ctx context.Context) error {
|
||||
databasePath := filepath.Join(
|
||||
os.Getenv("HOME"), "Library", "Developer", "CoreSimulator", "Devices",
|
||||
d.udid, "data", "Library", "TCC", "TCC.db",
|
||||
)
|
||||
if _, err := os.Stat(databasePath); err != nil {
|
||||
return fmt.Errorf("locate privacy database: %w", err)
|
||||
}
|
||||
statement := fmt.Sprintf(
|
||||
"INSERT OR REPLACE INTO access "+
|
||||
"(service,client,client_type,auth_value,auth_reason,auth_version,indirect_object_identifier) "+
|
||||
"VALUES ('kTCCServicePasteboard','%s',0,2,4,1,'UNUSED');",
|
||||
d.bundleID,
|
||||
)
|
||||
output, err := exec.CommandContext(ctx, "sqlite3", databasePath, statement).CombinedOutput()
|
||||
if err != nil {
|
||||
return fmt.Errorf("write privacy grant: %w: %s", err, strings.TrimSpace(string(output)))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// resetDataContainer deletes the contents of the app's data container so the
|
||||
// next launch starts with empty storage.
|
||||
func (d *Driver) resetDataContainer(ctx context.Context) error {
|
||||
|
||||
Reference in new issue
Block a user