Skip to content

Commit f91cf95

Browse files
Release v1.0.0 - Initial release
1 parent dd2df4f commit f91cf95

File tree

16 files changed

+2302
-480
lines changed

16 files changed

+2302
-480
lines changed

.github/workflows/release.yml

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
name: Build and Release (Debug APK)
2+
3+
on:
4+
push:
5+
tags:
6+
- 'v*'
7+
8+
jobs:
9+
build:
10+
runs-on: ubuntu-latest
11+
12+
steps:
13+
- name: Checkout code
14+
uses: actions/checkout@v4
15+
16+
- name: Set up JDK 17
17+
uses: actions/setup-java@v4
18+
with:
19+
java-version: '17'
20+
distribution: 'temurin'
21+
22+
- name: Setup Android SDK
23+
uses: android-actions/setup-android@v3
24+
25+
- name: Grant execute permission for gradlew
26+
run: chmod +x gradlew
27+
28+
- name: Build Debug APK
29+
run: ./gradlew assembleDebug
30+
31+
- name: Get version from tag
32+
id: version
33+
run: echo "VERSION=${GITHUB_REF#refs/tags/v}" >> $GITHUB_OUTPUT
34+
35+
- name: Rename APK
36+
run: |
37+
VERSION=${{ steps.version.outputs.VERSION }}
38+
mv app/build/outputs/apk/debug/app-debug.apk PSPLinuxController-${VERSION}.apk
39+
40+
- name: Package Server
41+
run: |
42+
VERSION=${{ steps.version.outputs.VERSION }}
43+
cd server
44+
tar -czvf ../PSPLinuxController-Server-${VERSION}.tar.gz \
45+
psp_controller_server.py \
46+
layout_editor_gui.py \
47+
requirements.txt \
48+
start_server.sh
49+
50+
- name: Create Release
51+
uses: softprops/action-gh-release@v1
52+
with:
53+
name: PSP Linux Controller v${{ steps.version.outputs.VERSION }}
54+
body: |
55+
## PSP Linux Controller v${{ steps.version.outputs.VERSION }}
56+
57+
Use your Android phone as a wireless controller for PPSSPP on Linux.
58+
59+
### Downloads
60+
- **PSPLinuxController-${{ steps.version.outputs.VERSION }}.apk** - Android app (debug build)
61+
- **PSPLinuxController-Server-${{ steps.version.outputs.VERSION }}.tar.gz** - Linux server + layout editor
62+
63+
### Quick Start
64+
1. Extract the server tarball and run `python3 psp_controller_server.py`
65+
2. Install the APK on your phone
66+
3. Connect to your PC's IP address
67+
4. Launch PPSSPP and play!
68+
files: |
69+
PSPLinuxController-${{ steps.version.outputs.VERSION }}.apk
70+
PSPLinuxController-Server-${{ steps.version.outputs.VERSION }}.tar.gz
71+
env:
72+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

README.md

Lines changed: 100 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -1,124 +1,146 @@
11
# PSP Linux Controller
22

3-
A wireless controller app for PPSSPP emulator on Linux. Use your Android phone as a PSP controller over WiFi.
3+
Use your Android phone as a wireless PSP controller for PPSSPP emulator on Linux.
44

5-
## Features
5+
![Android App](images/android.jpg)
6+
![Desktop Layout Editor](images/layout_editor_linux.png)
67

7-
- **Full PSP Controller Layout**: D-pad, action buttons (△, ○, □, ✕), Start/Select, L/R triggers, and analog stick
8-
- **Low Latency**: TCP socket connection (~1-5ms on local network)
9-
- **Connection Status**: Real-time connection indicator
10-
- **Auto-reconnect**: Automatically saves server IP for quick reconnection
11-
- **Fullscreen Mode**: Landscape layout with immersive controller experience
8+
## What It Does
129

13-
## Quick Start
10+
This app lets you control PPSSPP running on your Linux PC using your phone as a gamepad. It connects over WiFi with very low latency (typically 1-5ms on a local network).
1411

15-
### 1. Start the Server
12+
The controller has all the PSP buttons - D-pad, analog stick, action buttons (Triangle, Circle, Square, Cross), shoulders (L/R), and Start/Select.
13+
14+
There's also a desktop layout editor that lets you drag buttons around and see the changes live on your phone. Much easier than fiddling with the phone screen.
15+
16+
## Getting Started
17+
18+
### Step 1: Download
19+
20+
Grab the latest release from the [Releases page](../../releases):
21+
22+
- **PSPLinuxController-x.x.x.apk** - Install this on your Android phone
23+
- **PSPLinuxController-Server-x.x.x.tar.gz** - Extract this on your Linux PC
24+
25+
### Step 2: Set Up the Server
26+
27+
Extract the server tarball somewhere convenient:
28+
29+
```bash
30+
tar -xzf PSPLinuxController-Server-*.tar.gz
31+
cd PSPLinuxController-Server
32+
```
33+
34+
Install xdotool if you don't have it (the server uses this to simulate keyboard input):
1635

17-
**On Linux:**
1836
```bash
19-
# Install xdotool if not already installed
2037
sudo apt install xdotool
38+
```
2139

22-
cd server
40+
Start the server:
41+
42+
```bash
2343
./start_server.sh
2444
```
2545

26-
**On Windows:**
27-
```powershell
28-
# Just run the batch file (Python required)
29-
cd server
30-
start_server.bat
46+
You'll see something like this:
47+
48+
```
49+
==================================================
50+
PSP Controller Server
51+
Made by Uzair
52+
==================================================
53+
Local IP: 192.168.1.100
54+
Port: 5555
55+
==================================================
3156
```
3257

33-
The server will display your local IP address. Note this for the Android app.
58+
Note down that IP address - you'll need it for the phone app.
59+
60+
### Step 3: Install the App
61+
62+
Transfer the APK to your phone and install it. You might need to allow installing from unknown sources in your phone's settings.
63+
64+
### Step 4: Connect and Play
65+
66+
1. Open the app on your phone
67+
2. Tap the Connect button and enter your PC's IP address
68+
3. Launch PPSSPP on your PC and load a game
69+
4. Use your phone as the controller!
3470

35-
### 3. Install the Android App
71+
## Customizing the Layout
3672

37-
Build and install the APK on your Android phone:
73+
If you want to move buttons around, use the desktop layout editor:
3874

3975
```bash
40-
./gradlew assembleDebug
41-
adb install app/build/outputs/apk/debug/app-debug.apk
76+
pip install PyQt5 # Only needed once
77+
python3 layout_editor_gui.py
4278
```
4379

44-
### 4. Connect
45-
46-
1. Open the app on your phone
47-
2. Tap "Connect"
48-
3. Enter the IP address shown by the server
49-
4. Tap "Connect"
80+
Drag controls to reposition them. Changes show up on your phone in real-time. Hit Save when you're happy with the layout.
5081

51-
### 5. Play!
82+
The editor supports undo/redo with Ctrl+Z and Ctrl+Y.
5283

53-
1. Launch PPSSPP on your Linux machine
54-
2. Start a game
55-
3. Use your phone as the controller!
84+
## Default Key Bindings
5685

57-
## Key Mapping
86+
These are the keyboard keys that get pressed when you tap each button:
5887

59-
| PSP Button | Keyboard Key |
60-
|------------|--------------|
88+
| Button | Key |
89+
|--------|-----|
6190
| D-pad | Arrow Keys |
62-
| | Z |
63-
| | X |
64-
| | A |
65-
| | S |
91+
| Cross | Z |
92+
| Circle | X |
93+
| Square | A |
94+
| Triangle | S |
6695
| Start | Space |
6796
| Select | V |
68-
| L Trigger | Q |
69-
| R Trigger | W |
97+
| L / R | Q / W |
7098
| Analog | I/J/K/L |
7199

72-
## Server Options
100+
You can change these in PPSSPP's control settings if needed.
73101

74-
```bash
75-
python3 server/psp_controller_server.py --help
102+
## Troubleshooting
76103

77-
Options:
78-
-p, --port PORT Port to listen on (default: 5555)
79-
--host HOST Host to bind to (default: 0.0.0.0)
80-
```
104+
**Can't connect?**
81105

82-
## Troubleshooting
106+
Make sure your phone and PC are on the same WiFi network. You might also need to allow port 5555 through your firewall:
83107

84-
### "Connection failed" on Android
108+
```bash
109+
sudo ufw allow 5555
110+
```
85111

86-
- Make sure your phone and Linux PC are on the same WiFi network
87-
- Check that the server is running
88-
- Verify the IP address is correct
89-
- Check firewall settings: `sudo ufw allow 5555`
112+
**Button presses not working?**
90113

91-
### Keys not working in PPSSPP
114+
Make sure PPSSPP has focus (click on it). You can test if xdotool is working by running:
92115

93-
- Make sure PPSSPP window is focused
94-
- Verify xdotool is installed: `which xdotool`
95-
- Test xdotool manually: `xdotool key z`
116+
```bash
117+
xdotool key z
118+
```
96119

97-
### High latency
120+
## Building From Source
98121

99-
- Use 5GHz WiFi if available
100-
- Reduce distance from router
101-
- Close bandwidth-heavy apps
122+
If you want to build the app yourself instead of using the releases:
102123

103-
## Project Structure
124+
```bash
125+
# Build the APK
126+
./gradlew assembleDebug
127+
adb install app/build/outputs/apk/debug/app-debug.apk
104128

105-
```
106-
PSPLinuxController/
107-
├── server/
108-
│ ├── psp_controller_server.py # Python TCP server
109-
│ ├── requirements.txt # Dependencies
110-
│ └── start_server.sh # Startup script
111-
├── app/
112-
│ └── src/main/
113-
│ ├── java/.../
114-
│ │ ├── MainActivity.java # Controller UI
115-
│ │ ├── TcpClient.java # TCP connection
116-
│ │ └── ConnectionManager.java # Connection logic
117-
│ └── res/layout/
118-
│ └── activity_main.xml # Controller layout
119-
└── README.md
129+
# Run the server (no build needed, it's Python)
130+
cd server
131+
python3 psp_controller_server.py
120132
```
121133

122134
## License
123135

124136
Apache License 2.0
137+
138+
---
139+
140+
## Contributing
141+
142+
Contributions are welcome! Please open an issue or submit a pull request.
143+
144+
---
145+
146+
Made by Uzair

app/src/main/java/uzair/ppsspp/linuxcontroller/ConnectionManager.java

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ public interface ConnectionListener {
2727
void onDisconnected();
2828
void onConnectionError(String message);
2929
void onLatencyUpdate(long latencyMs);
30+
void onLayoutPreview(String controlId, float x, float y, float scale, float opacity, boolean visible);
31+
void onSetLayout(String layoutJson);
3032
}
3133

3234
public ConnectionManager(Context context) {
@@ -138,8 +140,62 @@ public void onConnected() {
138140
}
139141
// Send initial ping
140142
sendPing();
143+
// Send device info for layout editor
144+
sendDeviceInfo();
141145
}
142146

147+
/**
148+
* Send device info (screen dimensions, density) to server for layout editor.
149+
*/
150+
public void sendDeviceInfo() {
151+
if (!isConnected()) return;
152+
153+
try {
154+
android.util.DisplayMetrics metrics = context.getResources().getDisplayMetrics();
155+
JSONObject json = new JSONObject();
156+
json.put("type", "device_info");
157+
json.put("width", metrics.widthPixels);
158+
json.put("height", metrics.heightPixels);
159+
json.put("density", metrics.density);
160+
tcpClient.send(json.toString());
161+
} catch (JSONException e) {
162+
e.printStackTrace();
163+
}
164+
}
165+
166+
/**
167+
* Send current layout (all control positions, scales, opacities) to server.
168+
* The desktop Layout Editor will use this to initialize with phone's current layout.
169+
*/
170+
public void sendCurrentLayout(LayoutSettingsManager layoutManager) {
171+
if (!isConnected()) return;
172+
173+
try {
174+
JSONObject json = new JSONObject();
175+
json.put("type", "current_layout");
176+
177+
JSONObject controls = new JSONObject();
178+
String[] controlIds = LayoutSettingsManager.getAllControlIds();
179+
180+
for (String controlId : controlIds) {
181+
LayoutSettingsManager.ControlSettings settings = layoutManager.getControlSettings(controlId);
182+
JSONObject controlJson = new JSONObject();
183+
controlJson.put("x", settings.posX);
184+
controlJson.put("y", settings.posY);
185+
controlJson.put("scale", settings.scale);
186+
controlJson.put("opacity", settings.opacity);
187+
controlJson.put("visible", settings.visible);
188+
controls.put(controlId, controlJson);
189+
}
190+
191+
json.put("controls", controls);
192+
tcpClient.send(json.toString());
193+
} catch (JSONException e) {
194+
e.printStackTrace();
195+
}
196+
}
197+
198+
143199
@Override
144200
public void onDisconnected() {
145201
isConnected = false;
@@ -170,6 +226,25 @@ public void onMessageReceived(String message) {
170226
if (listener != null) {
171227
listener.onLatencyUpdate(latency);
172228
}
229+
} else if ("layout_preview".equals(type)) {
230+
// Live preview from desktop editor
231+
if (listener != null) {
232+
String controlId = json.optString("control");
233+
float x = (float) json.optDouble("x", -1);
234+
float y = (float) json.optDouble("y", -1);
235+
float scale = (float) json.optDouble("scale", -1);
236+
float opacity = (float) json.optDouble("opacity", -1);
237+
boolean visible = json.optBoolean("visible", true);
238+
listener.onLayoutPreview(controlId, x, y, scale, opacity, visible);
239+
}
240+
} else if ("set_layout".equals(type)) {
241+
// Save layout from desktop editor
242+
if (listener != null) {
243+
JSONObject layout = json.optJSONObject("layout");
244+
if (layout != null) {
245+
listener.onSetLayout(layout.toString());
246+
}
247+
}
173248
}
174249
} catch (JSONException e) {
175250
// Ignore parse errors

0 commit comments

Comments
 (0)