Autostart scrcpy on device connection.
Published: 2020-08-16 13:03:40

# OnCreateBlog:

When it comes to programming and setting up my workflow, I always try to find a solution to automate basic tasks and focus on what matters, also it gives me a sense of magic. One of the things that were bugging me lately is manually launching [scrcpy](https://github.com/Genymobile/scrcpy) every time I plug my phone to test my apps, in this blog post I will show you how I automated that process, note that this setup is for macOS.

There's a command-line tool allows to execute a command whenever a new device is connected to adb, but that didn't work for me, you can check out [here](https://github.com/rom1v/autoadb).

# My thought process:

As for each problem a plan is required, my initial thought was to set up some kind of Cron job that runs every second and check for new USB devices, but this is brute force and it's not efficient to run a job every second, even if we set the job to run every minute that would not be a good idea either if I plug my phone I might have to wait one minute for the job to launch [scrcpy](https://github.com/Genymobile/scrcpy).

Okay what other options we have, let's run a job and write an infinite loop that checks for new USB devices. this solves the instant problem but it's not efficient.

I had one last thought and it's to set up some sort of a listener, callback on an event that gets triggered every time a USB device is plugged in. After doing some research I found out that it is possible thanks to `launchd`

# What is launchd:

`launchd` is a service management tool used by macOS it was created by Apple, it's similar in some ways to `systemd` on Linux.

Another definition according to Apple:

> The launchd process is used by macOS to manage daemons and agents, and you can use it to run your shell scripts.

But wait what are daemons and agents?

A Daemon is a computer program that runs on background without any direct control and interaction with the user, they are usually launched when the system boots. An example of a daemon is a backup schedule when you enable Time Machine.

An Agent is very familiar to a Daemon but it runs within the user session, which means that they are launched when the user logs in.

Based on the [Apple documentation](https://support.apple.com/en-gb/guide/terminal/apdc6c1077b-5d5d-4d35-9c19-60f2397b2369/mac) we can find the various daemons and agents managed by `launchd` by looking inside the following folders:

| Tables                        |      Are                                                          |
|-------------------------------|:-----------------------------------------------------------------:|
| /System/Library/LaunchDaemons | Apple-supplied system daemons                                     |
| /System/Library/LaunchAgents  | Apple-supplied agents that apply to all users on a per-user basis |
| /Library/LaunchDaemons        | Third-party system daemons                                        |
| /Library/LaunchAgents         | Third-party agents that apply to all users on a per-user basis    |
| ~/Library/LaunchAgents        | Third-party agents that apply only to the logged-in user          |

# Creating a LaunchDaemon or LaunchAgent:

Based on those definitions, we will proceed with creating an Agent because it's more suitable to solve our problem, we don't want to launch [scrcpy](https://github.com/Genymobile/scrcpy) if the user is not logged in.

To create a new `LaunchAgent` we need to add our config file to the respective directory, I will be adding my config file to `~/Library/LaunchAgents`.

Those files require a specific format: `plist` (property list) it holds the specific information of the task. Below are the required keys to include in the file:

- Label: Unique task identifier.
- Program or ProgramArguments: String Path to executable.
- RunAtLoad: (True or False) Run job immediately when `plist` file is loaded.
- LaunchOnlyOnce: Run job once, and never try again.
- LaunchEvents: there is more than one trigger or launch event but the one that we are going to use is: `I/O kit notifications - USB device.`

Note that the difference between Program and ProgramArguments is that when using ProgramArguments you can specify the path to executable and an array of arguments arg[0],arg[1],....

So hers is my config file, which I name `com.oneplus5t.scrcpy.plist`:

```xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC -//Apple Computer//DTD PLIST 1.0//EN <http://www.apple.com/DTDs/PropertyList-1.0.dtd> >
<plist version="1.0">
<dict>
    <key>Label</key>
    <string>com.oneplus5t.scrcpy</string>
    <key>ProgramArguments</key>
    <array>
        <string>/usr/local/bin/launch_iterm.bash</string>
        <string>scrcpy</string>
    </array>
    <key>LaunchEvents</key>
    <dict>
            <key>com.apple.iokit.matching</key>
            <dict>
                    <key>com.apple.device-attach</key>
                    <dict>
                            <key>idProduct</key>
                            <integer>1234</integer>
                            <key>idVendor</key>
                            <integer>1444</integer>
                            <key>IOProviderClass</key>
                            <string>IOUSBDevice</string>
                            <key>IOMatchLaunchStream</key>
                            <true/>
                    </dict>
            </dict>
    </dict>
</dict>
</plist>
```

To find your phone's idProduct and idVendor run this command:

```bash
ioreg -p IOUSB -l
```

Explaining what the above command does:

1. `ioreg` displays the I/O Kit registry.
2. `-p` option is to traverse `IOUSB` registry
3. `-l` to show more information about the displayed objects.

You may notice that we are running a script `launch_iterm.bash`, thanks to this script we can launch [iTerm2](https://www.iterm2.com/) or any other terminal application, it uses `AppleScript` which is a scripting language created by (wait for it) → Apple. check out [this](https://gist.github.com/geyang/a495a5ea8e6fdd2f79f8e874616ea182).

```bash
#!/bin/bash
#
# Open new iTerm window from the command line using v3 syntax for `AppleScript` as needed in iTerm2 Version 3+
# This script blocks until the cmd is executed in the new iTerm2 window.  It then leaves the window open.
#
# See also <https://www.iterm2.com/documentation-scripting.html>
#
# Usage:
#     iterm                   Opens the current directory in a new iTerm window
#     iterm [PATH]            Open PATH in a new iTerm window
#     iterm [CMD]             Open a new iTerm window and execute CMD
#     iterm [PATH] [CMD] ...  You can probably guess
#
# Example:
#     iterm ~/Code/HelloWorld ./setup.sh
#
# References:
#     iTerm AppleScript Examples:
#     <https://gitlab.com/gnachman/iterm2/wikis/Applescript>
#
# Credit:
#     Forked from <https://gist.github.com/vyder/96891b93f515cb4ac559e9132e1c9086>
#     Inspired by tab.bash by @bobthecow
#     link: <https://gist.github.com/bobthecow/757788>

# OSX only
[ `uname -s` != "Darwin" ] && echo 'OS X Only' &&return

function iterm () {
    local cmd=""
    local wd="$PWD"
    local args="$@"

    if [ -d "$1" ]; then
        wd=$(cd "$1"; pwd)
        args="${@:2}"
    fi

    if [ -n "$args" ]; then
        # echo $args
        cmd="$args"
    fi

osascript <<EOF
	tell application "iTerm"
		activate
		set new_window to (create window with default profile)
		set cSession to current session of new_window
		tell new_window
			tell cSession
				delay 1
				write text "$cmd"
			end tell
		end tell
	end tell
EOF
}
iterm $@
```

One thing left to do is to load the Agent and plug your phone, to load the Agent we use the `launch` tool:

```bash
launchctl load com.oneplus5t.scrcpy.plist
```

After loading the configuration file, you will notice that it's repeatedly executed each 10s, as I understand this is caused because the [`scrcpy`](https://github.com/Genymobile/scrcpy) doesn't return a result and this causes the executable being called repeatedly (if I am mistaken please correct me). Thanks to this [repo](https://github.com/himbeles/mac-device-connect-daemon), our problem can be fixed:

1. Unload the configuration file

    ```bash
    launchctl unload com.oneplus5t.scrcpy.plist
    ```

2. Clone the repo:

    ```bash
    git clone <https://github.com/himbeles/mac-device-connect-daemon>
    ```

3. Build the event stream handler:

    ```bash
    gcc -framework Foundation -o xpc_set_event_stream_handler xpc_set_event_stream_handler.m
    ```

4. Copy the result to `/usr/local/bin/`

    ```bash
    cp xpc_set_event_stream_handler /usr/local/bin/
    ```

5. Update the `LaunchAgent` configuration file:

    ```xml
    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE plist PUBLIC -//Apple Computer//DTD PLIST 1.0//EN <http://www.apple.com/DTDs/PropertyList-1.0.dtd> >
    <plist version="1.0">
    <dict>
        <key>Label</key>
        <string>com.oneplus5t.scrcpy</string>
        <key>ProgramArguments</key>
        <array>
            <string>/usr/local/bin/xpc_set_event_stream_handler</string> <!-- Add this line -->
            <string>/usr/local/bin/launch_iterm.bash</string>
            <string>scrcpy</string>
        </array>
        <key>LaunchEvents</key>
        <dict>
                <key>com.apple.iokit.matching</key>
                <dict>
                        <key>com.apple.device-attach</key>
                        <dict>
                                <key>idProduct</key>
                                <integer>1234</integer>
                                <key>idVendor</key>
                                <integer>1444</integer>
                                <key>IOProviderClass</key>
                                <string>IOUSBDevice</string>
                                <key>IOMatchLaunchStream</key>
                                <true/>
                        </dict>
                </dict>
        </dict>
    </dict>
    </plist>
    ```

# OnDestroyBlog:

This was a fun experiment, it opens a wide variety of automation ideas and improving my workflow, Thank you for reading I hope you learned something new and you had fun as I did, Feel free to reach out on [Twitter](https://twitter.com/anesabml) if you have any questions.

# Resources:

[Launchd - At Your Service!](http://technologeeks.com/docs/launchd.pdf)

[macOS: Know the difference between launch agents and daemons, and use them to automate processes](https://www.techrepublic.com/article/macos-know-the-difference-between-launch-agents-and-daemons-and-use-them-to-automate-processes/)

[Daemons and Agents](https://developer.apple.com/library/archive/technotes/tn2083/_index.html)

[Script management with launchd in Terminal on Mac](https://support.apple.com/en-gb/guide/terminal/apdc6c1077b-5d5d-4d35-9c19-60f2397b2369/mac)

[Mac device connect daemon](https://github.com/himbeles/mac-device-connect-daemon)

[Bash function for opening up iterm form current directory](https://gist.github.com/geyang/a495a5ea8e6fdd2f79f8e874616ea182)