Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ jobs:
sudo apt-get install -y --no-install-recommends \
build-essential \
cmake \
libx11-dev \
libssl-dev \
pkg-config \
zlib1g-dev
Expand Down Expand Up @@ -61,6 +62,7 @@ jobs:
gcc-c++ \
gtk3-devel \
libayatana-appindicator3-devel \
libX11-devel \
make \
openssl-devel \
pkgconf-pkg-config \
Expand Down Expand Up @@ -94,6 +96,7 @@ jobs:
sudo apt-get install -y --no-install-recommends \
build-essential \
cmake \
libx11-dev \
libssl-dev \
pkg-config \
zlib1g-dev
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ mwb-lock-report-*.txt
mwb-socket-trace-*.txt
inputflow-windows-pair-*.ps1
AGENTS.md
artifacts/

# Editor / OS noise
.vscode/
Expand Down
38 changes: 37 additions & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -36,13 +36,17 @@ endfunction()
# Find OpenSSL for AES decryption/encryption matching PowerToys MWB
find_package(OpenSSL REQUIRED)
find_package(ZLIB REQUIRED)
find_package(X11 REQUIRED)

add_executable(mwb_client
src/AndroidRelay.cpp
src/AppConfig.cpp
src/AppState.cpp
src/ClientRuntime.cpp
src/Discovery.cpp
src/InputDispatcher.cpp
src/LibeiInputCaptureBridge.cpp
src/LocalAndroidInputBridge.cpp
src/main.cpp
src/PeerRecovery.cpp
src/SecretStore.cpp
Expand All @@ -56,6 +60,7 @@ add_executable(mwb_client

target_include_directories(mwb_client PRIVATE src)
target_include_directories(mwb_client PRIVATE ${OPENSSL_INCLUDE_DIR})
target_include_directories(mwb_client PRIVATE ${X11_INCLUDE_DIR})

if (CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang")
target_compile_options(mwb_client PRIVATE
Expand All @@ -78,26 +83,47 @@ target_link_libraries(mwb_client PRIVATE
OpenSSL::SSL
OpenSSL::Crypto
ZLIB::ZLIB
${X11_LIBRARIES}
pthread
)

include(CTest)

find_package(PkgConfig QUIET)
if (PkgConfig_FOUND)
pkg_check_modules(LIBEI_INPUT_CAPTURE QUIET libei-1.0 gio-unix-2.0 gio-2.0 glib-2.0)
pkg_check_modules(LIBINPUT_GESTURES QUIET libinput)
endif()

if (LIBEI_INPUT_CAPTURE_FOUND)
target_compile_definitions(mwb_client PRIVATE MWB_HAVE_LIBEI_INPUT_CAPTURE=1)
target_include_directories(mwb_client PRIVATE ${LIBEI_INPUT_CAPTURE_INCLUDE_DIRS})
target_link_libraries(mwb_client PRIVATE ${LIBEI_INPUT_CAPTURE_LDFLAGS})
endif()

if (LIBINPUT_GESTURES_FOUND)
message(STATUS "Native libinput gesture monitor enabled")
target_compile_definitions(mwb_client PRIVATE MWB_HAVE_LIBINPUT_GESTURES=1)
target_include_directories(mwb_client PRIVATE ${LIBINPUT_GESTURES_INCLUDE_DIRS})
target_link_libraries(mwb_client PRIVATE ${LIBINPUT_GESTURES_LDFLAGS})
else()
message(STATUS "Native libinput gesture monitor disabled; install libinput-devel to enable it")
endif()

if (BUILD_TESTING)
find_program(PYTHON3_EXECUTABLE python3)

add_executable(mwb_client_unit_tests
tests/test_main.cpp
src/AndroidRelay.cpp
src/AppConfig.cpp
src/AppState.cpp
src/Discovery.cpp
src/PeerRecovery.cpp
)
target_include_directories(mwb_client_unit_tests PRIVATE src)
target_compile_options(mwb_client_unit_tests PRIVATE -Wall -Wextra -Wpedantic)
target_link_libraries(mwb_client_unit_tests PRIVATE pthread)
target_link_libraries(mwb_client_unit_tests PRIVATE OpenSSL::Crypto pthread)
mwb_apply_sanitizers(mwb_client_unit_tests)

add_executable(mwb_input_mapping_tests
Expand Down Expand Up @@ -222,6 +248,16 @@ endif()
if (PkgConfig_FOUND)
pkg_check_modules(MWB_TRAY_DEPS QUIET IMPORTED_TARGET gtk+-3.0 ayatana-appindicator3-0.1)
if (MWB_TRAY_DEPS_FOUND)
# Embed tray + GUI window into the main mwb_client binary
target_sources(mwb_client PRIVATE
src/TrayController.cpp
src/GuiMainWindow.cpp
src/MonitorLayoutWidget.cpp
)
target_compile_definitions(mwb_client PRIVATE MWB_HAVE_GTK_GUI=1)
target_link_libraries(mwb_client PRIVATE PkgConfig::MWB_TRAY_DEPS)

# Keep standalone mwb_tray as a fallback (no embedded runtime)
add_executable(mwb_tray src/TrayController.cpp)
target_link_libraries(mwb_tray PRIVATE PkgConfig::MWB_TRAY_DEPS)
mwb_apply_sanitizers(mwb_tray)
Expand Down
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ User-facing beta operations:
- [Connection quality and latency reporting](docs/beta-workflow.md#connection-quality)
- [Packaging verification](docs/beta-workflow.md#packaging-verification)
- [Topology config contract and layout wizard expectations](docs/topology.md)
- [Android peer MVP](docs/android.md)
- [Migration from other keyboard/mouse sharing tools](docs/migration.md)
- [Compatibility matrix and platform caveats](docs/compatibility.md)

Expand All @@ -117,7 +118,7 @@ User-facing beta operations:
This repository started as a fork of [chrischip/mwb-client-linux](https://github.com/chrischip/mwb-client-linux) and has been substantially expanded with service management, rich clipboard support, and recovery tooling.

### Configuration (`config.ini`)
Supports `key_file`, `key_secret_id` (keyring), `screen_width/height` overrides, `topology_enabled`, `topology_file`, and more. Default path: `~/.config/mwb-client/config.ini`.
Supports `key_file`, `key_secret_id` (keyring), `screen_width/height` overrides, `topology_enabled`, `topology_file`, experimental `android_peers_enabled`, and more. Default path: `~/.config/mwb-client/config.ini`.

Display-level topology is a separate opt-in contract. The default runtime remains MWB-compatible machine placement unless topology is explicitly enabled; see [docs/topology.md](docs/topology.md) for examples, wrap policies, validation, and cross-machine handoff behavior.

Expand Down
4 changes: 4 additions & 0 deletions android/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
.gradle/
build/
local.properties
app/build/
30 changes: 30 additions & 0 deletions android/app/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
plugins {
id "com.android.application"
id "org.jetbrains.kotlin.android"
}

android {
namespace "com.inputflow.android"
compileSdk 35

defaultConfig {
applicationId "com.inputflow.android"
minSdk 26
targetSdk 35
versionCode 1
versionName "0.1.0"
}

compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
}

dependencies {
implementation "com.google.android.material:material:1.12.0"
}

kotlin {
jvmToolchain(8)
}
71 changes: 71 additions & 0 deletions android/app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-permission android:name="android.permission.WAKE_LOCK" />

<application
android:allowBackup="false"
android:label="@string/app_name"
android:theme="@style/AppTheme">
<activity
android:name=".MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data
android:host="android-peer"
android:scheme="inputflow" />
</intent-filter>
</activity>

<activity
android:name=".SettingsActivity"
android:exported="false" />

<activity
android:name=".LayoutEditorActivity"
android:exported="false" />

<activity
android:name=".KeyMapperActivity"
android:exported="false" />

<service
android:name=".RelayForegroundService"
android:exported="false"
android:foregroundServiceType="dataSync" />

<service
android:name=".InputFlowAccessibilityService"
android:exported="true"
android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE">
<intent-filter>
<action android:name="android.accessibilityservice.AccessibilityService" />
</intent-filter>
<meta-data
android:name="android.accessibilityservice"
android:resource="@xml/accessibility_service_config" />
</service>

<service
android:name=".InputFlowImeService"
android:exported="true"
android:permission="android.permission.BIND_INPUT_METHOD">
<intent-filter>
<action android:name="android.view.InputMethod" />
</intent-filter>
<meta-data
android:name="android.view.im"
android:resource="@xml/input_method" />
</service>
</application>
</manifest>
Loading
Loading