├── .gitignore ├── Makefile ├── README.md ├── client └── SteamLinkController │ ├── .idea │ ├── caches │ │ └── build_file_checksums.ser │ ├── codeStyles │ │ └── Project.xml │ ├── gradle.xml │ ├── misc.xml │ └── runConfigurations.xml │ ├── app │ ├── .gitignore │ ├── build.gradle │ ├── proguard-rules.pro │ └── src │ │ ├── androidTest │ │ └── java │ │ │ └── nuno │ │ │ └── steamlinkcontroller │ │ │ └── ExampleInstrumentedTest.java │ │ ├── main │ │ ├── AndroidManifest.xml │ │ ├── ic_launcher-web.png │ │ ├── java │ │ │ └── nuno │ │ │ │ └── steamlinkcontroller │ │ │ │ ├── activities │ │ │ │ ├── DeviceListActivity.java │ │ │ │ ├── MainActivity.java │ │ │ │ └── MousepadKeyboard.java │ │ │ │ └── logic │ │ │ │ ├── OneFingerFSM.java │ │ │ │ ├── OneFingerState.java │ │ │ │ ├── TwoFingerFSM.java │ │ │ │ └── TwoFingerState.java │ │ └── res │ │ │ ├── drawable-hdpi │ │ │ ├── ic_keyboard.png │ │ │ └── ic_windows.png │ │ │ ├── drawable-mdpi │ │ │ ├── ic_keyboard.png │ │ │ └── ic_windows.png │ │ │ ├── drawable-v24 │ │ │ └── ic_launcher_foreground.xml │ │ │ ├── drawable-xhdpi │ │ │ ├── ic_keyboard.png │ │ │ └── ic_windows.png │ │ │ ├── drawable-xxhdpi │ │ │ ├── ic_keyboard.png │ │ │ └── ic_windows.png │ │ │ ├── drawable-xxxhdpi │ │ │ ├── ic_keyboard.png │ │ │ └── ic_windows.png │ │ │ ├── drawable │ │ │ └── ic_launcher_background.xml │ │ │ ├── layout │ │ │ ├── activity_connecting.xml │ │ │ ├── activity_device_list.xml │ │ │ ├── activity_main.xml │ │ │ ├── activity_mousepad_keyboard.xml │ │ │ └── device_name.xml │ │ │ ├── mipmap-anydpi-v26 │ │ │ ├── ic_launcher.xml │ │ │ └── ic_launcher_round.xml │ │ │ ├── mipmap-hdpi │ │ │ ├── ic_launcher.png │ │ │ ├── ic_launcher_foreground.png │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-mdpi │ │ │ ├── ic_launcher.png │ │ │ ├── ic_launcher_foreground.png │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xhdpi │ │ │ ├── ic_launcher.png │ │ │ ├── ic_launcher_foreground.png │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xxhdpi │ │ │ ├── ic_launcher.png │ │ │ ├── ic_launcher_foreground.png │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xxxhdpi │ │ │ ├── ic_launcher.png │ │ │ ├── ic_launcher_foreground.png │ │ │ └── ic_launcher_round.png │ │ │ └── values │ │ │ ├── colors.xml │ │ │ ├── integers.xml │ │ │ ├── strings.xml │ │ │ └── styles.xml │ │ └── test │ │ └── java │ │ └── nuno │ │ └── steamlinkcontroller │ │ └── ExampleUnitTest.java │ ├── build.gradle │ ├── gradle.properties │ ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties │ ├── gradlew │ ├── gradlew.bat │ └── settings.gradle ├── resources ├── DefineXMLConverter │ ├── Converter.java │ ├── input-event-codes.h │ └── keys.xml ├── GimpIcon │ ├── Icon.xcf │ └── Large.xcf ├── Images │ ├── Icon.png │ ├── IconLarge.png │ ├── IconStore.png │ ├── Large.png │ ├── Screenshot_2018-10-15-15-05-02.png │ ├── Screenshot_2018-10-15-15-05-08.png │ ├── Screenshot_2018-10-15-15-05-12.png │ ├── Screenshot_2018-10-15-15-06-28.png │ ├── client_architecture.png │ ├── icon.png │ ├── project_organization.jpg │ └── server_architecture.png ├── Libbluetooth │ └── libbluetooth.a ├── Proto │ ├── bluetooth-server-steamlink.c │ ├── headers.h │ ├── input-server-steamlink.c │ └── input-server-ubuntu.c └── README.md ├── server ├── bluetooth │ ├── bluetooth.c │ ├── bluetooth_headers.h │ └── bluetooth_service.c ├── common │ └── common.h ├── input │ ├── input.c │ ├── input_headers.h │ └── input_service.c ├── protocol │ ├── protocol.c │ └── protocol_headers.h ├── server.c └── signal │ ├── signal.c │ └── signal_headers.h ├── setup-raspbian.sh ├── setup-steamlink.sh └── setup-ubuntu.sh /.gitignore: -------------------------------------------------------------------------------- 1 | steamlink-sdk-master 2 | steamlink-sdk-master.zip 3 | *.o 4 | *.swp 5 | *.iml 6 | server/.vscode/ 7 | client/SteamLinkController/.gradle 8 | client/SteamLinkController/local.properties 9 | client/SteamLinkController/.idea/libraries 10 | client/SteamLinkController/.idea/modules.xml 11 | client/SteamLinkController/.idea/workspace.xml 12 | client/SteamLinkController/.DS_Store 13 | client/SteamLinkController/build 14 | client/SteamLinkController/captures 15 | client/SteamLinkController/.externalNativeBuild 16 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | CC=gcc 2 | SCC=armv7a-cros-linux-gnueabi-gcc 3 | CFLAGS= -Wall -lbluetooth 4 | SCFLAGS= -Wall ./resources/Libbluetooth/libbluetooth.a 5 | DEPS= 6 | 7 | ROOT=./server/ 8 | BLDIR=$(ROOT)bluetooth/ 9 | INDIR=$(ROOT)input/ 10 | PTDIR=$(ROOT)protocol/ 11 | SGDIR=$(ROOT)signal/ 12 | 13 | ROOTOBJS=$(ROOT)server.c 14 | BLOBJS=$(BLDIR)bluetooth.c $(BLDIR)bluetooth_service.c 15 | INOBJS=$(INDIR)input.c $(INDIR)input_service.c 16 | PTOBJS=$(PTDIR)protocol.c 17 | SGOBJS=$(SGDIR)signal.c 18 | 19 | OBJS=$(ROOTOBJS) $(BLOBJS) $(INOBJS) $(PTOBJS) $(SGOBJS) 20 | 21 | BIN=bluetoothcontroller.o 22 | STEAMLINKBIN=bluetoothcontroller.o 23 | 24 | all: ubuntu steamlink 25 | 26 | ubuntu: 27 | $(CC) $(OBJS) -o $(BIN) $(CFLAGS) $(DEPS) 28 | 29 | steamlink: 30 | $(SCC) $(OBJS) -o $(STEAMLINKBIN) $(SCFLAGS) $(DEPS) 31 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Steam Link Bluetooth Keyboard and Mouse Companion 2 | This Application aims to deliver a Mouse and Keyboard interface for some SBC, Single Board Computers, devices. It was first planned to work with SteamLink, given that it requires external peripherals to interact with and it might be annoying. For example tiping in chat with only a gamepad might be very frustrating. So, in order to prevent the user from being constantly plugging and unplugging mouse and keyboard, this project was created. This way, at any time, the user can simply open the app in the smartphone and connect to the SteamLink. A virtual mouse and keyboard will pop up on the screen and the user can interact easily. 3 | 4 | The app was originally planned and developed for Bluetooth, given that it is supported 100% of the time as the user is always close enough to interact via Bluetooth. Wireless is currently being worked on but it was not first planned because some users do not have Wireless support near the SteamLink. 5 | 6 | Although it was planned for SteamLink the author also found feasible to port the same app for other SBC's. In this case a tutorial for Raspbian for Raspberry Pi was also created. 7 | 8 | In theory, this App works for any Linux Distribution, as long as Bluetooth is supported. But the setup script for each distribution must be created. 9 | 10 | ## Introduction 11 | The objective of this project is to create a support keyboard, mouse and gamepad to help with steamk link user interaction. The app is to be developed to android in a first initial release and the communication is featured via bluetooth. No need to buy additional mouse, keyboard or second gamepad. No need to always move the peripherals arround. The bluetooth data transfer was chosen because the steam link supports bluetooth. Given that, in some conditions useres might not have wireless connection due to distance reasons. However, bluetooth will always be close to the players becuase a proper close quarters interactions is needed with steam link. 12 | 13 | ## Setup 14 | This project works also for other linux based distributions, as long as they support Bluetooth. 15 | In the following chapter I will show how to install in SteamLink and Ubuntu. 16 | 17 | ## SteamLink 18 | #### Requirments: 19 | * SteamLink Device 20 | * MacOs, Linux Machine or Windows with Linux terminal (example: windows 10 with ubuntu from Store) 21 | 22 | #### 1st Step: Enable Steamlink SSH and retrieve IP address 23 | The SteamLinkSDK repository explains very well how to enable ssh under SteamLink. 24 | 25 | [Enalbe SSH in Steamlink](https://github.com/ValveSoftware/steamlink-sdk#ssh-access) 26 | 27 | Basically: Format a pen drive to FAT32 format, Inside the pen-drive create folder `steamlink`, inside create `config`, inside again create `system` and finally inside create the text file `enable_ssh.txt`. Open the file and write `SteamLink`, save and close it. Now power cycle the device, this means you need to unplug SteamLink from electricity and plug it again, Shutting down from the SteamLink itself it will only put it to sleep and it will not enable SSH. Do not change the password. 28 | 29 | Now, boot up SteamLink and retrieve the `IP Address` by going down to Settings->Network. If you don't have an IP Address then it means you are not connected to your Home Network. The IP Adress should have the following format: `X.X.X.X` where `X` is a number from 1 to 255. 30 | 31 | Write down the IP Adress, we will need it for the next Step. 32 | 33 | #### 2nd Step: Pair SteamLink with your android device 34 | First, Download the Client App from the Play Store, [SteamLink Keyboard Mouse Companion](https://play.google.com/store/apps/details?id=nuno.steamlinkcontroller), open and notice the underlined number by the format `XX:XX:XX:XX:XX:XX` that is your device MAC Address, note it down we are gonna need that later. 35 | 36 | Since SteamLink does not provide a proper UI for the users to pair devices, we have to pair via terminal. 37 | Open a SSH connection from termianl `ssh root@`. Don't forget to change for the real address that you retrieved last step. 38 | 39 | Now, follow the Tutorial on the Wiki to pair the device, in the last step you will need the MAC address that you wrote down in the beginning of this step. [Bluetooth Pairing With SteamLink](https://github.com/excelsi0r/SteamLinkBluetoothKeybordMouseCompanion/wiki/Research#bluetooth-pairing-with-steamlink) 40 | 41 | #### 3rd Step: Run Script by passing IP 42 | Download the script [SteamLink Setup Script](https://github.com/excelsi0r/SteamLinkBluetoothKeybordMouseCompanion/releases/download/v0.2-beta/setup-steamlink.sh) 43 | 44 | Run the script with your steamlink IP Address. Ex: `./setup-steamlink.sh 192.168.1.105` 45 | 46 | After the script finishes running, power cycle the SteamLink, unplug from electricity and plug again. 47 | 48 | #### 4th Step: Run Android App 49 | Open android app, press Connect and select the steamlink in the list of paired devices. 50 | 51 | ## Raspbian 52 | #### Requirments: 53 | * Raspbian Device (Raspberry Pi) 54 | 55 | #### 1st Step: Pair with Raspberry 56 | Raspbian provides a easy user interaction in order to pair devices. The first thing you should do is pair Raspbian with your device. If you are unfamiliar just search in Google and tons of tutorials will show up. 57 | 58 | #### 2nd Step: Run Script 59 | Run the Provided script in order to install the daemon server in Raspbian. [Raspbian Setup Script](https://github.com/excelsi0r/SteamLinkBluetoothKeybordMouseCompanion/releases/download/v0.2-beta/setup-raspbian.sh) 60 | 61 | I will assume you are inside Raspbian terminal. So, just run the script `sudo ./setup-rapbian.sh` 62 | 63 | Oncd it finishes, reboot Raspbian. 64 | 65 | #### 3rd: Run Android App 66 | Download the Client App from the Play Store, [SteamLink Keyboard Mouse Companion](https://play.google.com/store/apps/details?id=nuno.steamlinkcontroller) 67 | Open the app, press Connect and select the rasperrypi in the list of paired devices. 68 | 69 | ## Ubuntu 70 | * Ubuntu Machine 71 | 72 | #### 1st Step: Pair with Raspberry 73 | Ubuntu provides a easy user interaction in order to pair devices. The first thing you should do is pair Ubuntu with your device. If you are unfamiliar just search in Google and tons of tutorials will show up. 74 | 75 | #### 2nd Step: Run Script 76 | Run the Provided script in order to install the daemon server in Raspbian. [Ubuntu Setup Script](https://github.com/excelsi0r/SteamLinkBluetoothKeybordMouseCompanion/releases/download/v0.2-beta/setup-ubuntu.sh) 77 | 78 | I will assume you are inside Ubuntu terminal. So, just run the script `sudo ./setup-ubuntu.sh` 79 | 80 | Oncd it finishes, reboot Ubuntu. 81 | 82 | #### 3rd: Run Android App 83 | Download the Client App from the Play Store, [SteamLink Keyboard Mouse Companion](https://play.google.com/store/apps/details?id=nuno.steamlinkcontroller) 84 | Open the app, press Connect and select the ubuntu machine in the list of paired devices. 85 | 86 | ## Contribute 87 | If you wish to contribute there is pleanty of work to be done. You can view the Future section in the [Dashboard](https://github.com/excelsi0r/SteamLinkBluetoothKeybordMouseCompanion/projects/1) for contributions. 88 | 89 | Right now: 90 | * Using Wireless TCP and UDP sockets as an alternative for Bluetooth (TCP for keys and Buttons, UDP for mouse movement) 91 | * Support for differente Layouts, right now the only supported layout is US 92 | * Implement Pairing mechanism inside the Android app, right now pairing must be doen using Android native Bluetooth interface 93 | * Implement Gamepad Support in Server 94 | * Create Gamepad in Android 95 | * Scripts for other Linux Distributions 96 | * Implement Application in BLE (currently using deprecated API) 97 | * Implement new transmission protocol, in order to use less bytes (currently always sending and receiving 10 bytes) 98 | 99 | Don't forget to check out the [Wiki](https://github.com/excelsi0r/SteamLinkBluetoothKeybordMouseCompanion/wiki) containing usefull information, such as [Architecture](https://github.com/excelsi0r/SteamLinkBluetoothKeybordMouseCompanion/wiki/Architecture), [Research](https://github.com/excelsi0r/SteamLinkBluetoothKeybordMouseCompanion/wiki/Research) and [References](https://github.com/excelsi0r/SteamLinkBluetoothKeybordMouseCompanion/wiki/References) 100 | 101 | -------------------------------------------------------------------------------- /client/SteamLinkController/.idea/caches/build_file_checksums.ser: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/excelsi0r/SteamLinkBluetoothKeybordMouseCompanion/5dc191f892030f8e830a0033fefb41d8786530c5/client/SteamLinkController/.idea/caches/build_file_checksums.ser -------------------------------------------------------------------------------- /client/SteamLinkController/.idea/codeStyles/Project.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 15 | 16 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /client/SteamLinkController/.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 17 | 18 | -------------------------------------------------------------------------------- /client/SteamLinkController/.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 17 | 27 | 28 | 29 | 30 | 31 | 32 | 34 | -------------------------------------------------------------------------------- /client/SteamLinkController/.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | -------------------------------------------------------------------------------- /client/SteamLinkController/app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /client/SteamLinkController/app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 26 5 | defaultConfig { 6 | applicationId "nuno.steamlinkcontroller" 7 | minSdkVersion 18 8 | targetSdkVersion 26 9 | versionCode 1 10 | versionName "1.0" 11 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 12 | } 13 | buildTypes { 14 | release { 15 | minifyEnabled false 16 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 17 | } 18 | } 19 | } 20 | 21 | dependencies { 22 | implementation fileTree(include: ['*.jar'], dir: 'libs') 23 | implementation 'com.android.support:appcompat-v7:26.1.0' 24 | implementation 'com.android.support.constraint:constraint-layout:1.1.2' 25 | testImplementation 'junit:junit:4.12' 26 | androidTestImplementation 'com.android.support.test:runner:1.0.2' 27 | androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' 28 | } 29 | -------------------------------------------------------------------------------- /client/SteamLinkController/app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /client/SteamLinkController/app/src/androidTest/java/nuno/steamlinkcontroller/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package nuno.steamlinkcontroller; 2 | 3 | import android.content.Context; 4 | import android.support.test.InstrumentationRegistry; 5 | import android.support.test.runner.AndroidJUnit4; 6 | 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | 10 | import static org.junit.Assert.*; 11 | 12 | /** 13 | * Instrumented test, which will execute on an Android device. 14 | * 15 | * @see Testing documentation 16 | */ 17 | @RunWith(AndroidJUnit4.class) 18 | public class ExampleInstrumentedTest { 19 | @Test 20 | public void useAppContext() { 21 | // Context of the app under test. 22 | Context appContext = InstrumentationRegistry.getTargetContext(); 23 | 24 | assertEquals("nuno.steamlinkcontroller", appContext.getPackageName()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /client/SteamLinkController/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 16 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /client/SteamLinkController/app/src/main/ic_launcher-web.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/excelsi0r/SteamLinkBluetoothKeybordMouseCompanion/5dc191f892030f8e830a0033fefb41d8786530c5/client/SteamLinkController/app/src/main/ic_launcher-web.png -------------------------------------------------------------------------------- /client/SteamLinkController/app/src/main/java/nuno/steamlinkcontroller/activities/DeviceListActivity.java: -------------------------------------------------------------------------------- 1 | package nuno.steamlinkcontroller.activities; 2 | 3 | import android.Manifest; 4 | import android.app.Activity; 5 | import android.bluetooth.BluetoothAdapter; 6 | import android.bluetooth.BluetoothDevice; 7 | import android.bluetooth.le.BluetoothLeScanner; 8 | import android.bluetooth.le.ScanCallback; 9 | import android.bluetooth.le.ScanResult; 10 | import android.content.BroadcastReceiver; 11 | import android.content.Context; 12 | import android.content.Intent; 13 | import android.content.IntentFilter; 14 | import android.os.Build; 15 | import android.support.annotation.RequiresApi; 16 | import android.support.v4.content.ContextCompat; 17 | import android.support.v7.app.AppCompatActivity; 18 | import android.os.Bundle; 19 | import android.util.Log; 20 | import android.view.View; 21 | import android.view.ViewGroup; 22 | import android.view.Window; 23 | import android.widget.AdapterView; 24 | import android.widget.ArrayAdapter; 25 | import android.widget.Button; 26 | import android.widget.ListView; 27 | import android.widget.ProgressBar; 28 | import android.widget.TextView; 29 | import android.widget.Toast; 30 | 31 | import java.util.Set; 32 | 33 | import nuno.steamlinkcontroller.R; 34 | 35 | public class DeviceListActivity extends AppCompatActivity 36 | { 37 | public static String EXTRA_DEVICE_ADDRESS = "device_address"; 38 | private BluetoothAdapter mBtAdapter; 39 | private ArrayAdapter mNewDevicesArrayAdapter; 40 | private Button scanButton; 41 | private Button cancelButton; 42 | private ProgressBar progressBar; 43 | private TextView availableText; 44 | 45 | @RequiresApi(api = Build.VERSION_CODES.M) 46 | @Override 47 | protected void onCreate(Bundle savedInstanceState) { 48 | super.onCreate(savedInstanceState); 49 | 50 | setContentView(R.layout.activity_device_list); 51 | setResult(Activity.RESULT_CANCELED); 52 | 53 | TextView pairedText = findViewById(R.id.pairedDevicesText); 54 | availableText = findViewById(R.id.availableDevicesText); 55 | scanButton = findViewById(R.id.scanDevicesButton); 56 | cancelButton = findViewById(R.id.cancelDevicesButton); 57 | progressBar = findViewById(R.id.progressBar); 58 | 59 | pairedText.setVisibility(View.GONE); 60 | availableText.setVisibility(View.GONE); 61 | cancelButton.setVisibility(View.GONE); 62 | 63 | scanButton.setOnClickListener(new View.OnClickListener() 64 | { 65 | @Override 66 | public void onClick(View v) 67 | { 68 | startDiscovery(); 69 | } 70 | }); 71 | 72 | cancelButton.setOnClickListener(new View.OnClickListener() { 73 | @Override 74 | public void onClick(View view) { 75 | stopDiscovery(); 76 | } 77 | }); 78 | 79 | // Quick permission check 80 | int permissionCheck = this.checkSelfPermission("Manifest.permission.ACCESS_FINE_LOCATION"); 81 | permissionCheck += this.checkSelfPermission("Manifest.permission.ACCESS_COARSE_LOCATION"); 82 | if (permissionCheck != 0) { 83 | 84 | this.requestPermissions(new String[]{Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION}, 1001); //Any number 85 | } 86 | 87 | //bluetooth adapter setup 88 | mBtAdapter = BluetoothAdapter.getDefaultAdapter(); 89 | 90 | //PAIRED DEVICES SETUP 91 | ArrayAdapter pairedDevicesArrayAdapter = new ArrayAdapter(this, R.layout.device_name); 92 | ListView pairedListView = findViewById(R.id.pairedDevicesList); 93 | pairedListView.setAdapter(pairedDevicesArrayAdapter); 94 | pairedListView.setOnItemClickListener(mDeviceClickListener); 95 | Set pairedDevices = mBtAdapter.getBondedDevices(); 96 | if (pairedDevices.size() > 0) 97 | { 98 | findViewById(R.id.pairedDevicesText).setVisibility(View.VISIBLE); 99 | 100 | for (BluetoothDevice device : pairedDevices) 101 | { 102 | pairedDevicesArrayAdapter.add(device.getName() + "\n" + device.getAddress()); 103 | } 104 | } 105 | else 106 | findViewById(R.id.pairedDevicesText).setVisibility(View.GONE); 107 | 108 | //SCAN DEVICES SETUP 109 | mNewDevicesArrayAdapter = new ArrayAdapter(this, R.layout.device_name); 110 | ListView scannedDevicesView = findViewById(R.id.availableDeviceList); 111 | scannedDevicesView.setAdapter(mNewDevicesArrayAdapter); 112 | scannedDevicesView.setOnItemClickListener(mDeviceClickListener); 113 | IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_FOUND); 114 | this.registerReceiver(mReceiver, filter); 115 | filter = new IntentFilter(BluetoothAdapter.ACTION_DISCOVERY_FINISHED); 116 | this.registerReceiver(mReceiver, filter); 117 | 118 | 119 | } 120 | 121 | @Override 122 | protected void onDestroy() { 123 | super.onDestroy(); 124 | 125 | if (mBtAdapter != null) { 126 | mBtAdapter.cancelDiscovery(); 127 | } 128 | 129 | this.unregisterReceiver(mReceiver); 130 | } 131 | 132 | private void startDiscovery() 133 | { 134 | scanButton.setVisibility(View.GONE); 135 | progressBar.setVisibility(View.VISIBLE); 136 | cancelButton.setVisibility(View.VISIBLE); 137 | 138 | mNewDevicesArrayAdapter.clear(); 139 | availableText.setVisibility(View.VISIBLE); 140 | 141 | if (mBtAdapter.isDiscovering()) { 142 | mBtAdapter.cancelDiscovery(); 143 | } 144 | mBtAdapter.startDiscovery(); 145 | } 146 | 147 | private void stopDiscovery() 148 | { 149 | scanButton.setVisibility(View.VISIBLE); 150 | progressBar.setVisibility(View.GONE); 151 | cancelButton.setVisibility(View.GONE); 152 | 153 | if(mNewDevicesArrayAdapter.getCount() < 1) 154 | { 155 | availableText.setVisibility(View.GONE); 156 | Toast.makeText(this, "No Devices Found", Toast.LENGTH_SHORT).show(); 157 | } 158 | 159 | mBtAdapter.cancelDiscovery(); 160 | } 161 | 162 | //this is an object not a function 163 | private AdapterView.OnItemClickListener mDeviceClickListener = new AdapterView.OnItemClickListener() 164 | { 165 | public void onItemClick(AdapterView av, View v, int arg2, long arg3) 166 | { 167 | mBtAdapter.cancelDiscovery(); 168 | 169 | // Get the device MAC address, which is the last 17 chars in the View 170 | String info = ((TextView) v).getText().toString(); 171 | String address = info.substring(info.length() - 17); 172 | 173 | // Create the result Intent and include the MAC address 174 | Intent intent = new Intent(); 175 | intent.putExtra(EXTRA_DEVICE_ADDRESS, address); 176 | 177 | // Set result and finish this Activity 178 | setResult(Activity.RESULT_OK, intent); 179 | finish(); 180 | } 181 | }; 182 | 183 | private final BroadcastReceiver mReceiver = new BroadcastReceiver() { 184 | @Override 185 | public void onReceive(Context context, Intent intent) 186 | { 187 | String action = intent.getAction(); 188 | 189 | if (BluetoothDevice.ACTION_FOUND.equals(action)) 190 | { 191 | BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); 192 | if (device.getBondState() != BluetoothDevice.BOND_BONDED) 193 | { 194 | mNewDevicesArrayAdapter.add(device.getName() + "\n" + device.getAddress()); 195 | availableText.setVisibility(View.VISIBLE); 196 | } 197 | } 198 | else if (BluetoothAdapter.ACTION_DISCOVERY_FINISHED.equals(action)) 199 | { 200 | stopDiscovery(); 201 | } 202 | } 203 | }; 204 | 205 | } 206 | -------------------------------------------------------------------------------- /client/SteamLinkController/app/src/main/java/nuno/steamlinkcontroller/activities/MainActivity.java: -------------------------------------------------------------------------------- 1 | package nuno.steamlinkcontroller.activities; 2 | 3 | import android.app.Activity; 4 | import android.app.AlertDialog; 5 | import android.bluetooth.BluetoothAdapter; 6 | import android.bluetooth.BluetoothDevice; 7 | import android.content.DialogInterface; 8 | import android.content.Intent; 9 | import android.content.SharedPreferences; 10 | import android.os.CountDownTimer; 11 | import android.support.v7.app.AppCompatActivity; 12 | import android.os.Bundle; 13 | import android.text.Html; 14 | import android.text.SpannableString; 15 | import android.text.Spanned; 16 | import android.text.method.LinkMovementMethod; 17 | import android.text.style.UnderlineSpan; 18 | import android.view.View; 19 | import android.widget.Button; 20 | import android.widget.TextView; 21 | import android.widget.Toast; 22 | 23 | import nuno.steamlinkcontroller.R; 24 | 25 | public class MainActivity extends AppCompatActivity 26 | { 27 | Button connectButton; 28 | Button exitButton; 29 | TextView myMacView; 30 | BluetoothAdapter mBtAdapter; 31 | 32 | final static String BLUETOOTH_DEVICE_EXTRA = "BluetoothDeviceExtra"; 33 | final static String SAVED_DEVICE = "SteamLinkDevice"; 34 | final int REQUEST_ENABLE_BT = 1; 35 | final int REQUEST_DEVICE_LIST = 2; 36 | 37 | 38 | @Override 39 | protected void onCreate(Bundle savedInstanceState) 40 | { 41 | super.onCreate(savedInstanceState); 42 | setContentView(R.layout.activity_main); 43 | 44 | connectButton = findViewById(R.id.connectButton); 45 | exitButton = findViewById(R.id.exitButton); 46 | myMacView = findViewById(R.id.myMACviewText); 47 | final TextView linkTextView = findViewById(R.id.linkTextView); 48 | 49 | //Checking Bluetooth Supported 50 | SpannableString content = null; 51 | if((mBtAdapter = BluetoothAdapter.getDefaultAdapter()) == null) 52 | { 53 | connectButton.setClickable(false); 54 | content = new SpannableString("Not Supported"); 55 | } 56 | else 57 | { 58 | if (!mBtAdapter.isEnabled()) 59 | { 60 | Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE); 61 | startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT); 62 | } 63 | content = new SpannableString(android.provider.Settings.Secure.getString(getContentResolver(), "bluetooth_address")); 64 | } 65 | content.setSpan(new UnderlineSpan(), 0, content.length(), 0); 66 | myMacView.setText(content); 67 | 68 | //set click listeners 69 | connectButton.setOnClickListener(new View.OnClickListener() 70 | { 71 | @Override 72 | public void onClick(View v) 73 | { 74 | Intent serverIntent = new Intent(getActivity(), DeviceListActivity.class); 75 | startActivityForResult(serverIntent, REQUEST_DEVICE_LIST); 76 | } 77 | }); 78 | 79 | 80 | exitButton.setOnClickListener(new View.OnClickListener() 81 | { 82 | @Override 83 | public void onClick(View v) 84 | { 85 | finish(); 86 | } 87 | }); 88 | 89 | //Retrieve saved device to initiate auto-connect 90 | SharedPreferences mPrefs = getPreferences(MODE_PRIVATE); 91 | String address = mPrefs.getString(MainActivity.SAVED_DEVICE, null); 92 | if(address != null) 93 | { 94 | final BluetoothDevice device = mBtAdapter.getRemoteDevice(address); 95 | 96 | //Verify if the device is available and possible to save and connect 97 | if(verifyDeviceAvailability(device)) 98 | { 99 | AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); 100 | builder.setMessage(R.string.autoConnectDescription).setTitle(R.string.autoConnectTitle); 101 | 102 | //setting buttons and listeners 103 | builder.setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() { 104 | public void onClick(DialogInterface dialog, int id) 105 | { 106 | connect(device); 107 | } 108 | }); 109 | builder.setNegativeButton(R.string.cancel, null); 110 | 111 | final AlertDialog dialog = builder.create(); 112 | dialog.show(); 113 | 114 | //countdown timer, in order to autoConnect after 5seconds, if user hasn't cancelled 115 | new CountDownTimer(5300, 500) { 116 | public void onTick(long millisUntilFinished) 117 | { 118 | dialog.setMessage(getString(R.string.autoConnectDescription) + " " + millisUntilFinished / 1000); 119 | } 120 | public void onFinish() 121 | { 122 | if(dialog.isShowing()) 123 | { 124 | dialog.dismiss(); 125 | connect(device); 126 | } 127 | } 128 | }.start(); 129 | } 130 | else 131 | { 132 | //In case device is not available 133 | Toast.makeText(this, getString(R.string.savedDeviceNotAvailable), Toast.LENGTH_SHORT).show(); 134 | } 135 | } 136 | 137 | Spanned html = Html.fromHtml("Server Setup"); 138 | 139 | linkTextView.setMovementMethod(LinkMovementMethod.getInstance()); 140 | 141 | linkTextView.setText(html); 142 | } 143 | 144 | public void onActivityResult(int requestCode, int resultCode, Intent data) 145 | { 146 | if(requestCode == REQUEST_DEVICE_LIST) 147 | { 148 | if(resultCode == RESULT_OK) 149 | { 150 | String address = data.getStringExtra(DeviceListActivity.EXTRA_DEVICE_ADDRESS); 151 | BluetoothDevice btDevice = mBtAdapter.getRemoteDevice(address); 152 | 153 | //Verify if the device is available and possible to save and connect 154 | if(verifyDeviceAvailability(btDevice)) 155 | { 156 | SharedPreferences mPrefs = getPreferences(MODE_PRIVATE); 157 | mPrefs.edit().putString(MainActivity.SAVED_DEVICE, address).apply(); 158 | 159 | connect(btDevice); 160 | } 161 | else 162 | { 163 | Toast.makeText(this, getString(R.string.cannotSave), Toast.LENGTH_SHORT).show(); 164 | } 165 | } 166 | } 167 | } 168 | 169 | private void connect(BluetoothDevice device) 170 | { 171 | Intent intent = new Intent(getActivity(), MousepadKeyboard.class); 172 | intent.putExtra(BLUETOOTH_DEVICE_EXTRA, device); 173 | startActivity(intent); 174 | } 175 | 176 | private Activity getActivity() 177 | { 178 | return this; 179 | } 180 | 181 | private boolean verifyDeviceAvailability(BluetoothDevice device) 182 | { 183 | return device.getAddress() != null && device.getClass() != null; 184 | } 185 | } 186 | -------------------------------------------------------------------------------- /client/SteamLinkController/app/src/main/java/nuno/steamlinkcontroller/logic/OneFingerFSM.java: -------------------------------------------------------------------------------- 1 | package nuno.steamlinkcontroller.logic; 2 | 3 | public class OneFingerFSM 4 | { 5 | private final int MAX_DELTA = 100; 6 | private OneFingerState oneFingerState = OneFingerState.INIT; 7 | private long lastEvTime = 0; 8 | 9 | public void updateState(boolean tapDown) 10 | { 11 | long curr = System.currentTimeMillis(); 12 | long diff = curr - this.lastEvTime; 13 | 14 | switch (this.oneFingerState) 15 | { 16 | case INIT: 17 | if(tapDown) 18 | this.oneFingerState = OneFingerState.FD1; 19 | break; 20 | case FD1: 21 | if(!tapDown && diff <= MAX_DELTA) 22 | this.oneFingerState = OneFingerState.FU1; 23 | break; 24 | case FU1: 25 | if(tapDown && diff <= MAX_DELTA) 26 | this.oneFingerState = OneFingerState.SD1; 27 | break; 28 | case SD1: 29 | if(!tapDown && diff <= MAX_DELTA) 30 | this.oneFingerState = OneFingerState.SU1; 31 | break; 32 | case SU1: 33 | break; 34 | } 35 | 36 | this.lastEvTime = curr; 37 | } 38 | 39 | public OneFingerState getEventToSend() 40 | { 41 | long diff = System.currentTimeMillis() - this.lastEvTime; 42 | 43 | switch (this.oneFingerState) 44 | { 45 | case INIT: 46 | return OneFingerState.NULL; 47 | case FD1: 48 | if(diff <= MAX_DELTA) 49 | return OneFingerState.NULL; 50 | else 51 | { 52 | this.oneFingerState = OneFingerState.INIT; 53 | return OneFingerState.NULL; 54 | } 55 | case FU1: 56 | if(diff <= MAX_DELTA) 57 | return OneFingerState.NULL; 58 | else 59 | { 60 | this.oneFingerState = OneFingerState.INIT; 61 | return OneFingerState.FU1; 62 | } 63 | case SD1: 64 | if(diff <= MAX_DELTA) 65 | return OneFingerState.NULL; 66 | else 67 | { 68 | this.oneFingerState = OneFingerState.INIT; 69 | return OneFingerState.SD1; 70 | } 71 | case SU1: 72 | this.oneFingerState = OneFingerState.INIT; 73 | return OneFingerState.SU1; 74 | default: 75 | return OneFingerState.NULL; 76 | } 77 | 78 | 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /client/SteamLinkController/app/src/main/java/nuno/steamlinkcontroller/logic/OneFingerState.java: -------------------------------------------------------------------------------- 1 | package nuno.steamlinkcontroller.logic; 2 | 3 | public enum OneFingerState 4 | { 5 | NULL, 6 | INIT, 7 | FD1, 8 | FU1, 9 | SD1, 10 | SU1, 11 | } 12 | -------------------------------------------------------------------------------- /client/SteamLinkController/app/src/main/java/nuno/steamlinkcontroller/logic/TwoFingerFSM.java: -------------------------------------------------------------------------------- 1 | package nuno.steamlinkcontroller.logic; 2 | 3 | public class TwoFingerFSM 4 | { 5 | private final int MAX_DELTA = 100; 6 | private TwoFingerState twoFingerState = TwoFingerState.INIT; 7 | private long lastEvTime = 0; 8 | 9 | public void updateState(boolean tapDown) 10 | { 11 | long curr = System.currentTimeMillis(); 12 | long diff = curr - this.lastEvTime; 13 | 14 | switch (this.twoFingerState) 15 | { 16 | case INIT: 17 | if(tapDown) 18 | this.twoFingerState = TwoFingerState.FD2; 19 | break; 20 | case FD2: 21 | if(!tapDown && diff <= MAX_DELTA) 22 | this.twoFingerState = TwoFingerState.FU2; 23 | break; 24 | case FU2: 25 | break; 26 | } 27 | 28 | this.lastEvTime = curr; 29 | } 30 | 31 | public TwoFingerState getEventToSend() 32 | { 33 | long diff = System.currentTimeMillis() - this.lastEvTime; 34 | 35 | switch (this.twoFingerState) 36 | { 37 | case INIT: 38 | return TwoFingerState.NULL; 39 | case FD2: 40 | if(diff <= MAX_DELTA) 41 | return TwoFingerState.NULL; 42 | else 43 | { 44 | this.twoFingerState = TwoFingerState.INIT; 45 | return TwoFingerState.FD2; 46 | } 47 | case FU2: 48 | this.twoFingerState = TwoFingerState.INIT; 49 | return TwoFingerState.FU2; 50 | default: 51 | return TwoFingerState.NULL; 52 | } 53 | 54 | 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /client/SteamLinkController/app/src/main/java/nuno/steamlinkcontroller/logic/TwoFingerState.java: -------------------------------------------------------------------------------- 1 | package nuno.steamlinkcontroller.logic; 2 | 3 | public enum TwoFingerState 4 | { 5 | NULL, 6 | INIT, 7 | FD2, 8 | FU2 9 | } 10 | -------------------------------------------------------------------------------- /client/SteamLinkController/app/src/main/res/drawable-hdpi/ic_keyboard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/excelsi0r/SteamLinkBluetoothKeybordMouseCompanion/5dc191f892030f8e830a0033fefb41d8786530c5/client/SteamLinkController/app/src/main/res/drawable-hdpi/ic_keyboard.png -------------------------------------------------------------------------------- /client/SteamLinkController/app/src/main/res/drawable-hdpi/ic_windows.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/excelsi0r/SteamLinkBluetoothKeybordMouseCompanion/5dc191f892030f8e830a0033fefb41d8786530c5/client/SteamLinkController/app/src/main/res/drawable-hdpi/ic_windows.png -------------------------------------------------------------------------------- /client/SteamLinkController/app/src/main/res/drawable-mdpi/ic_keyboard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/excelsi0r/SteamLinkBluetoothKeybordMouseCompanion/5dc191f892030f8e830a0033fefb41d8786530c5/client/SteamLinkController/app/src/main/res/drawable-mdpi/ic_keyboard.png -------------------------------------------------------------------------------- /client/SteamLinkController/app/src/main/res/drawable-mdpi/ic_windows.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/excelsi0r/SteamLinkBluetoothKeybordMouseCompanion/5dc191f892030f8e830a0033fefb41d8786530c5/client/SteamLinkController/app/src/main/res/drawable-mdpi/ic_windows.png -------------------------------------------------------------------------------- /client/SteamLinkController/app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 12 | 13 | 19 | 22 | 25 | 26 | 27 | 28 | 34 | 35 | -------------------------------------------------------------------------------- /client/SteamLinkController/app/src/main/res/drawable-xhdpi/ic_keyboard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/excelsi0r/SteamLinkBluetoothKeybordMouseCompanion/5dc191f892030f8e830a0033fefb41d8786530c5/client/SteamLinkController/app/src/main/res/drawable-xhdpi/ic_keyboard.png -------------------------------------------------------------------------------- /client/SteamLinkController/app/src/main/res/drawable-xhdpi/ic_windows.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/excelsi0r/SteamLinkBluetoothKeybordMouseCompanion/5dc191f892030f8e830a0033fefb41d8786530c5/client/SteamLinkController/app/src/main/res/drawable-xhdpi/ic_windows.png -------------------------------------------------------------------------------- /client/SteamLinkController/app/src/main/res/drawable-xxhdpi/ic_keyboard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/excelsi0r/SteamLinkBluetoothKeybordMouseCompanion/5dc191f892030f8e830a0033fefb41d8786530c5/client/SteamLinkController/app/src/main/res/drawable-xxhdpi/ic_keyboard.png -------------------------------------------------------------------------------- /client/SteamLinkController/app/src/main/res/drawable-xxhdpi/ic_windows.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/excelsi0r/SteamLinkBluetoothKeybordMouseCompanion/5dc191f892030f8e830a0033fefb41d8786530c5/client/SteamLinkController/app/src/main/res/drawable-xxhdpi/ic_windows.png -------------------------------------------------------------------------------- /client/SteamLinkController/app/src/main/res/drawable-xxxhdpi/ic_keyboard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/excelsi0r/SteamLinkBluetoothKeybordMouseCompanion/5dc191f892030f8e830a0033fefb41d8786530c5/client/SteamLinkController/app/src/main/res/drawable-xxxhdpi/ic_keyboard.png -------------------------------------------------------------------------------- /client/SteamLinkController/app/src/main/res/drawable-xxxhdpi/ic_windows.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/excelsi0r/SteamLinkBluetoothKeybordMouseCompanion/5dc191f892030f8e830a0033fefb41d8786530c5/client/SteamLinkController/app/src/main/res/drawable-xxxhdpi/ic_windows.png -------------------------------------------------------------------------------- /client/SteamLinkController/app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 10 | 12 | 14 | 16 | 18 | 20 | 22 | 24 | 26 | 28 | 30 | 32 | 34 | 36 | 38 | 40 | 42 | 44 | 46 | 48 | 50 | 52 | 54 | 56 | 58 | 60 | 62 | 64 | 66 | 68 | 70 | 72 | 74 | 75 | -------------------------------------------------------------------------------- /client/SteamLinkController/app/src/main/res/layout/activity_connecting.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 11 | 12 | -------------------------------------------------------------------------------- /client/SteamLinkController/app/src/main/res/layout/activity_device_list.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 18 | 19 | 28 | 29 | 33 | 34 | 43 | 44 | 48 | 49 |