diff --git a/src/core/hle/service/hid/controllers/npad.cpp b/src/core/hle/service/hid/controllers/npad.cpp
index 2e06372a4a..510fa30719 100644
--- a/src/core/hle/service/hid/controllers/npad.cpp
+++ b/src/core/hle/service/hid/controllers/npad.cpp
@@ -397,8 +397,7 @@ void Controller_NPad::OnUpdate(const Core::Timing::CoreTiming& core_timing, u8*
                     std::tie(motion_devices[e].accel, motion_devices[e].gyro,
                              motion_devices[e].rotation, motion_devices[e].orientation) =
                         device->GetStatus();
-                    sixaxis_at_rest =
-                        sixaxis_at_rest && motion_devices[e].gyro.Length2() < 0.00005f;
+                    sixaxis_at_rest = sixaxis_at_rest && motion_devices[e].gyro.Length2() < 0.0001f;
                 }
             }
         }
diff --git a/src/input_common/main.cpp b/src/input_common/main.cpp
index ea1a1cee63..062ec66b5d 100644
--- a/src/input_common/main.cpp
+++ b/src/input_common/main.cpp
@@ -12,6 +12,7 @@
 #include "input_common/main.h"
 #include "input_common/motion_emu.h"
 #include "input_common/touch_from_button.h"
+#include "input_common/udp/client.h"
 #include "input_common/udp/udp.h"
 #ifdef HAVE_SDL2
 #include "input_common/sdl/sdl.h"
@@ -40,7 +41,11 @@ struct InputSubsystem::Impl {
         sdl = SDL::Init();
 #endif
 
-        udp = CemuhookUDP::Init();
+        udp = std::make_shared<InputCommon::CemuhookUDP::Client>();
+        udpmotion = std::make_shared<UDPMotionFactory>(udp);
+        Input::RegisterFactory<Input::MotionDevice>("cemuhookudp", udpmotion);
+        udptouch = std::make_shared<UDPTouchFactory>(udp);
+        Input::RegisterFactory<Input::TouchDevice>("cemuhookudp", udptouch);
     }
 
     void Shutdown() {
@@ -53,12 +58,17 @@ struct InputSubsystem::Impl {
 #ifdef HAVE_SDL2
         sdl.reset();
 #endif
-        udp.reset();
         Input::UnregisterFactory<Input::ButtonDevice>("gcpad");
         Input::UnregisterFactory<Input::AnalogDevice>("gcpad");
 
         gcbuttons.reset();
         gcanalog.reset();
+
+        Input::UnregisterFactory<Input::MotionDevice>("cemuhookudp");
+        Input::UnregisterFactory<Input::TouchDevice>("cemuhookudp");
+
+        udpmotion.reset();
+        udptouch.reset();
     }
 
     [[nodiscard]] std::vector<Common::ParamPackage> GetInputDevices() const {
@@ -109,14 +119,28 @@ struct InputSubsystem::Impl {
         return {};
     }
 
+    [[nodiscard]] MotionMapping GetMotionMappingForDevice(
+        const Common::ParamPackage& params) const {
+        if (!params.Has("class") || params.Get("class", "") == "any") {
+            return {};
+        }
+        if (params.Get("class", "") == "cemuhookudp") {
+            // TODO return the correct motion device
+            return {};
+        }
+        return {};
+    }
+
     std::shared_ptr<Keyboard> keyboard;
     std::shared_ptr<MotionEmu> motion_emu;
 #ifdef HAVE_SDL2
     std::unique_ptr<SDL::State> sdl;
 #endif
-    std::unique_ptr<CemuhookUDP::State> udp;
     std::shared_ptr<GCButtonFactory> gcbuttons;
     std::shared_ptr<GCAnalogFactory> gcanalog;
+    std::shared_ptr<UDPMotionFactory> udpmotion;
+    std::shared_ptr<UDPTouchFactory> udptouch;
+    std::shared_ptr<CemuhookUDP::Client> udp;
 };
 
 InputSubsystem::InputSubsystem() : impl{std::make_unique<Impl>()} {}
@@ -175,6 +199,22 @@ const GCButtonFactory* InputSubsystem::GetGCButtons() const {
     return impl->gcbuttons.get();
 }
 
+UDPMotionFactory* InputSubsystem::GetUDPMotions() {
+    return impl->udpmotion.get();
+}
+
+const UDPMotionFactory* InputSubsystem::GetUDPMotions() const {
+    return impl->udpmotion.get();
+}
+
+UDPTouchFactory* InputSubsystem::GetUDPTouch() {
+    return impl->udptouch.get();
+}
+
+const UDPTouchFactory* InputSubsystem::GetUDPTouch() const {
+    return impl->udptouch.get();
+}
+
 void InputSubsystem::ReloadInputDevices() {
     if (!impl->udp) {
         return;
diff --git a/src/input_common/main.h b/src/input_common/main.h
index 18f44dcc35..dded3f1efb 100644
--- a/src/input_common/main.h
+++ b/src/input_common/main.h
@@ -54,6 +54,8 @@ public:
 
 class GCAnalogFactory;
 class GCButtonFactory;
+class UDPMotionFactory;
+class UDPTouchFactory;
 class Keyboard;
 class MotionEmu;
 
@@ -123,6 +125,18 @@ public:
     /// Retrieves the underlying GameCube button handler.
     [[nodiscard]] const GCButtonFactory* GetGCButtons() const;
 
+    /// Retrieves the underlying udp motion handler.
+    [[nodiscard]] UDPMotionFactory* GetUDPMotions();
+
+    /// Retrieves the underlying udp motion handler.
+    [[nodiscard]] const UDPMotionFactory* GetUDPMotions() const;
+
+    /// Retrieves the underlying udp touch handler.
+    [[nodiscard]] UDPTouchFactory* GetUDPTouch();
+
+    /// Retrieves the underlying udp touch handler.
+    [[nodiscard]] const UDPTouchFactory* GetUDPTouch() const;
+
     /// Reloads the input devices
     void ReloadInputDevices();
 
diff --git a/src/input_common/udp/client.cpp b/src/input_common/udp/client.cpp
index 91e13482db..e0c796040a 100644
--- a/src/input_common/udp/client.cpp
+++ b/src/input_common/udp/client.cpp
@@ -2,14 +2,13 @@
 // Licensed under GPLv2 or any later version
 // Refer to the license.txt file included.
 
-#include <algorithm>
-#include <array>
 #include <chrono>
 #include <cstring>
 #include <functional>
 #include <thread>
 #include <boost/asio.hpp>
 #include "common/logging/log.h"
+#include "core/settings.h"
 #include "input_common/udp/client.h"
 #include "input_common/udp/protocol.h"
 
@@ -131,21 +130,60 @@ static void SocketLoop(Socket* socket) {
     socket->Loop();
 }
 
-Client::Client(std::shared_ptr<DeviceStatus> status, const std::string& host, u16 port,
-               u8 pad_index, u32 client_id)
-    : status(std::move(status)) {
-    StartCommunication(host, port, pad_index, client_id);
+Client::Client() {
+    LOG_INFO(Input, "Udp Initialization started");
+    for (std::size_t client = 0; client < clients.size(); client++) {
+        u8 pad = client % 4;
+        StartCommunication(client, Settings::values.udp_input_address,
+                           Settings::values.udp_input_port, pad, 24872);
+        // Set motion parameters
+        // SetGyroThreshold value should be dependent on GyroscopeZeroDriftMode
+        // Real HW values are unkown, 0.0001 is an aproximate to Standard
+        clients[client].motion.SetGyroThreshold(0.0001f);
+    }
 }
 
 Client::~Client() {
-    socket->Stop();
-    thread.join();
+    Reset();
 }
 
+std::vector<Common::ParamPackage> Client::GetInputDevices() const {
+    std::vector<Common::ParamPackage> devices;
+    for (std::size_t client = 0; client < clients.size(); client++) {
+        if (!DeviceConnected(client)) {
+            continue;
+        }
+        std::string name = fmt::format("UDP Controller{} {} {}", clients[client].active,
+                                       clients[client].active == 1, client);
+        devices.emplace_back(Common::ParamPackage{
+            {"class", "cemuhookudp"},
+            {"display", std::move(name)},
+            {"port", std::to_string(client)},
+        });
+    }
+    return devices;
+}
+
+bool Client::DeviceConnected(std::size_t pad) const {
+    // Use last timestamp to detect if the socket has stopped sending data
+    const auto now = std::chrono::system_clock::now();
+    u64 time_difference =
+        std::chrono::duration_cast<std::chrono::milliseconds>(now - clients[pad].last_motion_update)
+            .count();
+    return time_difference < 1000 && clients[pad].active == 1;
+}
+
+void Client::ReloadUDPClient() {
+    for (std::size_t client = 0; client < clients.size(); client++) {
+        ReloadSocket(Settings::values.udp_input_address, Settings::values.udp_input_port, client);
+    }
+}
 void Client::ReloadSocket(const std::string& host, u16 port, u8 pad_index, u32 client_id) {
-    socket->Stop();
-    thread.join();
-    StartCommunication(host, port, pad_index, client_id);
+    // client number must be determined from host / port and pad index
+    std::size_t client = pad_index;
+    clients[client].socket->Stop();
+    clients[client].thread.join();
+    StartCommunication(client, host, port, pad_index, client_id);
 }
 
 void Client::OnVersion(Response::Version data) {
@@ -157,31 +195,39 @@ void Client::OnPortInfo(Response::PortInfo data) {
 }
 
 void Client::OnPadData(Response::PadData data) {
+    // client number must be determined from host / port and pad index
+    std::size_t client = data.info.id;
     LOG_TRACE(Input, "PadData packet received");
-    if (data.packet_counter <= packet_sequence) {
+    if (data.packet_counter == clients[client].packet_sequence) {
         LOG_WARNING(
             Input,
             "PadData packet dropped because its stale info. Current count: {} Packet count: {}",
-            packet_sequence, data.packet_counter);
+            clients[client].packet_sequence, data.packet_counter);
         return;
     }
-    packet_sequence = data.packet_counter;
-    // TODO: Check how the Switch handles motions and how the CemuhookUDP motion
-    // directions correspond to the ones of the Switch
-    Common::Vec3f accel = Common::MakeVec<float>(data.accel.x, data.accel.y, data.accel.z);
-    Common::Vec3f gyro = Common::MakeVec<float>(data.gyro.pitch, data.gyro.yaw, data.gyro.roll);
+    clients[client].active = data.info.is_pad_active;
+    clients[client].packet_sequence = data.packet_counter;
+    const auto now = std::chrono::system_clock::now();
+    u64 time_difference = std::chrono::duration_cast<std::chrono::microseconds>(
+                              now - clients[client].last_motion_update)
+                              .count();
+    clients[client].last_motion_update = now;
+    Common::Vec3f raw_gyroscope = {data.gyro.pitch, data.gyro.roll, -data.gyro.yaw};
+    clients[client].motion.SetAcceleration({data.accel.x, -data.accel.z, data.accel.y});
+    // Gyroscope values are not it the correct scale from better joy.
+    // By dividing by 312 allow us to make one full turn = 1 turn
+    // This must be a configurable valued called sensitivity
+    clients[client].motion.SetGyroscope(raw_gyroscope / 312.0f);
+    clients[client].motion.UpdateRotation(time_difference);
+    clients[client].motion.UpdateOrientation(time_difference);
+    Common::Vec3f gyroscope = clients[client].motion.GetGyroscope();
+    Common::Vec3f accelerometer = clients[client].motion.GetAcceleration();
+    Common::Vec3f rotation = clients[client].motion.GetRotations();
+    std::array<Common::Vec3f, 3> orientation = clients[client].motion.GetOrientation();
 
-    // TODO: Calculate the correct rotation vector and orientation matrix
-    const auto rotation = Common::MakeVec(0.0f, 0.0f, 0.0f);
-    const std::array orientation{
-        Common::Vec3f(1.0f, 0.0f, 0.0f),
-        Common::Vec3f(0.0f, 1.0f, 0.0f),
-        Common::Vec3f(0.0f, 0.0f, 1.0f),
-    };
     {
-        std::lock_guard guard(status->update_mutex);
-
-        status->motion_status = {accel, gyro, rotation, orientation};
+        std::lock_guard guard(clients[client].status.update_mutex);
+        clients[client].status.motion_status = {accelerometer, gyroscope, rotation, orientation};
 
         // TODO: add a setting for "click" touch. Click touch refers to a device that differentiates
         // between a simple "tap" and a hard press that causes the touch screen to click.
@@ -190,11 +236,11 @@ void Client::OnPadData(Response::PadData data) {
         float x = 0;
         float y = 0;
 
-        if (is_active && status->touch_calibration) {
-            const u16 min_x = status->touch_calibration->min_x;
-            const u16 max_x = status->touch_calibration->max_x;
-            const u16 min_y = status->touch_calibration->min_y;
-            const u16 max_y = status->touch_calibration->max_y;
+        if (is_active && clients[client].status.touch_calibration) {
+            const u16 min_x = clients[client].status.touch_calibration->min_x;
+            const u16 max_x = clients[client].status.touch_calibration->max_x;
+            const u16 min_y = clients[client].status.touch_calibration->min_y;
+            const u16 max_y = clients[client].status.touch_calibration->max_y;
 
             x = (std::clamp(static_cast<u16>(data.touch_1.x), min_x, max_x) - min_x) /
                 static_cast<float>(max_x - min_x);
@@ -202,17 +248,82 @@ void Client::OnPadData(Response::PadData data) {
                 static_cast<float>(max_y - min_y);
         }
 
-        status->touch_status = {x, y, is_active};
+        clients[client].status.touch_status = {x, y, is_active};
+
+        if (configuring) {
+            UpdateYuzuSettings(client, accelerometer, gyroscope, is_active);
+        }
     }
 }
 
-void Client::StartCommunication(const std::string& host, u16 port, u8 pad_index, u32 client_id) {
+void Client::StartCommunication(std::size_t client, const std::string& host, u16 port, u8 pad_index,
+                                u32 client_id) {
     SocketCallback callback{[this](Response::Version version) { OnVersion(version); },
                             [this](Response::PortInfo info) { OnPortInfo(info); },
                             [this](Response::PadData data) { OnPadData(data); }};
     LOG_INFO(Input, "Starting communication with UDP input server on {}:{}", host, port);
-    socket = std::make_unique<Socket>(host, port, pad_index, client_id, callback);
-    thread = std::thread{SocketLoop, this->socket.get()};
+    clients[client].socket = std::make_unique<Socket>(host, port, pad_index, client_id, callback);
+    clients[client].thread = std::thread{SocketLoop, clients[client].socket.get()};
+}
+
+void Client::Reset() {
+    for (std::size_t client = 0; client < clients.size(); client++) {
+        clients[client].socket->Stop();
+        clients[client].thread.join();
+    }
+}
+
+void Client::UpdateYuzuSettings(std::size_t client, const Common::Vec3<float>& acc,
+                                const Common::Vec3<float>& gyro, bool touch) {
+    if (configuring) {
+        UDPPadStatus pad;
+        if (touch) {
+            pad.touch = PadTouch::Click;
+            pad_queue[client].Push(pad);
+        }
+        for (size_t i = 0; i < 3; ++i) {
+            if (gyro[i] > 6.0f || gyro[i] < -6.0f) {
+                pad.motion = static_cast<PadMotion>(i);
+                pad.motion_value = gyro[i];
+                pad_queue[client].Push(pad);
+            }
+            if (acc[i] > 2.0f || acc[i] < -2.0f) {
+                pad.motion = static_cast<PadMotion>(i + 3);
+                pad.motion_value = acc[i];
+                pad_queue[client].Push(pad);
+            }
+        }
+    }
+}
+
+void Client::BeginConfiguration() {
+    for (auto& pq : pad_queue) {
+        pq.Clear();
+    }
+    configuring = true;
+}
+
+void Client::EndConfiguration() {
+    for (auto& pq : pad_queue) {
+        pq.Clear();
+    }
+    configuring = false;
+}
+
+DeviceStatus& Client::GetPadState(std::size_t pad) {
+    return clients[pad].status;
+}
+
+const DeviceStatus& Client::GetPadState(std::size_t pad) const {
+    return clients[pad].status;
+}
+
+std::array<Common::SPSCQueue<UDPPadStatus>, 4>& Client::GetPadQueue() {
+    return pad_queue;
+}
+
+const std::array<Common::SPSCQueue<UDPPadStatus>, 4>& Client::GetPadQueue() const {
+    return pad_queue;
 }
 
 void TestCommunication(const std::string& host, u16 port, u8 pad_index, u32 client_id,
diff --git a/src/input_common/udp/client.h b/src/input_common/udp/client.h
index a73283ae8c..523dc6a7a1 100644
--- a/src/input_common/udp/client.h
+++ b/src/input_common/udp/client.h
@@ -12,9 +12,12 @@
 #include <thread>
 #include <tuple>
 #include "common/common_types.h"
+#include "common/param_package.h"
 #include "common/thread.h"
+#include "common/threadsafe_queue.h"
 #include "common/vector_math.h"
 #include "core/frontend/input.h"
+#include "input_common/motion_input.h"
 
 namespace InputCommon::CemuhookUDP {
 
@@ -29,6 +32,27 @@ struct PortInfo;
 struct Version;
 } // namespace Response
 
+enum class PadMotion {
+    GyroX,
+    GyroY,
+    GyroZ,
+    AccX,
+    AccY,
+    AccZ,
+    Undefined,
+};
+
+enum class PadTouch {
+    Click,
+    Undefined,
+};
+
+struct UDPPadStatus {
+    PadTouch touch{PadTouch::Undefined};
+    PadMotion motion{PadMotion::Undefined};
+    f32 motion_value{0.0f};
+};
+
 struct DeviceStatus {
     std::mutex update_mutex;
     Input::MotionStatus motion_status;
@@ -46,22 +70,58 @@ struct DeviceStatus {
 
 class Client {
 public:
-    explicit Client(std::shared_ptr<DeviceStatus> status, const std::string& host = DEFAULT_ADDR,
-                    u16 port = DEFAULT_PORT, u8 pad_index = 0, u32 client_id = 24872);
+    // Initialize the UDP client capture and read sequence
+    Client();
+
+    // Close and release the client
     ~Client();
+
+    // Used for polling
+    void BeginConfiguration();
+    void EndConfiguration();
+
+    std::vector<Common::ParamPackage> GetInputDevices() const;
+
+    bool DeviceConnected(std::size_t pad) const;
+    void ReloadUDPClient();
     void ReloadSocket(const std::string& host = "127.0.0.1", u16 port = 26760, u8 pad_index = 0,
                       u32 client_id = 24872);
 
+    std::array<Common::SPSCQueue<UDPPadStatus>, 4>& GetPadQueue();
+    const std::array<Common::SPSCQueue<UDPPadStatus>, 4>& GetPadQueue() const;
+
+    DeviceStatus& GetPadState(std::size_t pad);
+    const DeviceStatus& GetPadState(std::size_t pad) const;
+
 private:
+    struct ClientData {
+        std::unique_ptr<Socket> socket;
+        DeviceStatus status;
+        std::thread thread;
+        u64 packet_sequence = 0;
+        u8 active;
+
+        // Realtime values
+        // motion is initalized with PID values for drift correction on joycons
+        InputCommon::MotionInput motion{0.3f, 0.005f, 0.0f};
+        std::chrono::time_point<std::chrono::system_clock> last_motion_update;
+    };
+
+    // For shutting down, clear all data, join all threads, release usb
+    void Reset();
+
     void OnVersion(Response::Version);
     void OnPortInfo(Response::PortInfo);
     void OnPadData(Response::PadData);
-    void StartCommunication(const std::string& host, u16 port, u8 pad_index, u32 client_id);
+    void StartCommunication(std::size_t client, const std::string& host, u16 port, u8 pad_index,
+                            u32 client_id);
+    void UpdateYuzuSettings(std::size_t client, const Common::Vec3<float>& acc,
+                            const Common::Vec3<float>& gyro, bool touch);
 
-    std::unique_ptr<Socket> socket;
-    std::shared_ptr<DeviceStatus> status;
-    std::thread thread;
-    u64 packet_sequence = 0;
+    bool configuring = false;
+
+    std::array<ClientData, 4> clients;
+    std::array<Common::SPSCQueue<UDPPadStatus>, 4> pad_queue;
 };
 
 /// An async job allowing configuration of the touchpad calibration.
diff --git a/src/input_common/udp/udp.cpp b/src/input_common/udp/udp.cpp
index 03bae5752a..eba077a36c 100644
--- a/src/input_common/udp/udp.cpp
+++ b/src/input_common/udp/udp.cpp
@@ -1,105 +1,144 @@
-// Copyright 2018 Citra Emulator Project
+// Copyright 2020 yuzu Emulator Project
 // Licensed under GPLv2 or any later version
 // Refer to the license.txt file included.
 
+#include <atomic>
+#include <list>
 #include <mutex>
-#include <optional>
-#include <tuple>
-
-#include "common/param_package.h"
-#include "core/frontend/input.h"
-#include "core/settings.h"
+#include <utility>
+#include "common/assert.h"
+#include "common/threadsafe_queue.h"
 #include "input_common/udp/client.h"
 #include "input_common/udp/udp.h"
 
-namespace InputCommon::CemuhookUDP {
+namespace InputCommon {
 
-class UDPTouchDevice final : public Input::TouchDevice {
+class UDPMotion final : public Input::MotionDevice {
 public:
-    explicit UDPTouchDevice(std::shared_ptr<DeviceStatus> status_) : status(std::move(status_)) {}
-    std::tuple<float, float, bool> GetStatus() const override {
-        std::lock_guard guard(status->update_mutex);
-        return status->touch_status;
-    }
+    UDPMotion(std::string ip_, int port_, int pad_, CemuhookUDP::Client* client_)
+        : ip(ip_), port(port_), pad(pad_), client(client_) {}
 
-private:
-    std::shared_ptr<DeviceStatus> status;
-};
-
-class UDPMotionDevice final : public Input::MotionDevice {
-public:
-    explicit UDPMotionDevice(std::shared_ptr<DeviceStatus> status_) : status(std::move(status_)) {}
     Input::MotionStatus GetStatus() const override {
-        std::lock_guard guard(status->update_mutex);
-        return status->motion_status;
+        return client->GetPadState(pad).motion_status;
     }
 
 private:
-    std::shared_ptr<DeviceStatus> status;
+    const std::string ip;
+    const int port;
+    const int pad;
+    CemuhookUDP::Client* client;
+    mutable std::mutex mutex;
 };
 
-class UDPTouchFactory final : public Input::Factory<Input::TouchDevice> {
-public:
-    explicit UDPTouchFactory(std::shared_ptr<DeviceStatus> status_) : status(std::move(status_)) {}
+/// A motion device factory that creates motion devices from JC Adapter
+UDPMotionFactory::UDPMotionFactory(std::shared_ptr<CemuhookUDP::Client> client_)
+    : client(std::move(client_)) {}
 
-    std::unique_ptr<Input::TouchDevice> Create(const Common::ParamPackage& params) override {
-        {
-            std::lock_guard guard(status->update_mutex);
-            status->touch_calibration = DeviceStatus::CalibrationData{};
-            // These default values work well for DS4 but probably not other touch inputs
-            status->touch_calibration->min_x = params.Get("min_x", 100);
-            status->touch_calibration->min_y = params.Get("min_y", 50);
-            status->touch_calibration->max_x = params.Get("max_x", 1800);
-            status->touch_calibration->max_y = params.Get("max_y", 850);
+/**
+ * Creates motion device
+ * @param params contains parameters for creating the device:
+ *     - "port": the nth jcpad on the adapter
+ */
+std::unique_ptr<Input::MotionDevice> UDPMotionFactory::Create(const Common::ParamPackage& params) {
+    const std::string ip = params.Get("ip", "127.0.0.1");
+    const int port = params.Get("port", 26760);
+    const int pad = params.Get("pad_index", 0);
+
+    return std::make_unique<UDPMotion>(ip, port, pad, client.get());
+}
+
+void UDPMotionFactory::BeginConfiguration() {
+    polling = true;
+    client->BeginConfiguration();
+}
+
+void UDPMotionFactory::EndConfiguration() {
+    polling = false;
+    client->EndConfiguration();
+}
+
+Common::ParamPackage UDPMotionFactory::GetNextInput() {
+    Common::ParamPackage params;
+    CemuhookUDP::UDPPadStatus pad;
+    auto& queue = client->GetPadQueue();
+    for (std::size_t pad_number = 0; pad_number < queue.size(); ++pad_number) {
+        while (queue[pad_number].Pop(pad)) {
+            if (pad.motion == CemuhookUDP::PadMotion::Undefined || std::abs(pad.motion_value) < 1) {
+                continue;
+            }
+            params.Set("engine", "cemuhookudp");
+            params.Set("ip", "127.0.0.1");
+            params.Set("port", 26760);
+            params.Set("pad_index", static_cast<int>(pad_number));
+            params.Set("motion", static_cast<u16>(pad.motion));
+            return params;
         }
-        return std::make_unique<UDPTouchDevice>(status);
     }
+    return params;
+}
 
-private:
-    std::shared_ptr<DeviceStatus> status;
-};
-
-class UDPMotionFactory final : public Input::Factory<Input::MotionDevice> {
+class UDPTouch final : public Input::TouchDevice {
 public:
-    explicit UDPMotionFactory(std::shared_ptr<DeviceStatus> status_) : status(std::move(status_)) {}
+    UDPTouch(std::string ip_, int port_, int pad_, CemuhookUDP::Client* client_)
+        : ip(std::move(ip_)), port(port_), pad(pad_), client(client_) {}
 
-    std::unique_ptr<Input::MotionDevice> Create(const Common::ParamPackage& params) override {
-        return std::make_unique<UDPMotionDevice>(status);
+    std::tuple<float, float, bool> GetStatus() const override {
+        return client->GetPadState(pad).touch_status;
     }
 
 private:
-    std::shared_ptr<DeviceStatus> status;
+    const std::string ip;
+    const int port;
+    const int pad;
+    CemuhookUDP::Client* client;
+    mutable std::mutex mutex;
 };
 
-State::State() {
-    auto status = std::make_shared<DeviceStatus>();
-    client =
-        std::make_unique<Client>(status, Settings::values.udp_input_address,
-                                 Settings::values.udp_input_port, Settings::values.udp_pad_index);
+/// A motion device factory that creates motion devices from JC Adapter
+UDPTouchFactory::UDPTouchFactory(std::shared_ptr<CemuhookUDP::Client> client_)
+    : client(std::move(client_)) {}
 
-    motion_factory = std::make_shared<UDPMotionFactory>(status);
-    touch_factory = std::make_shared<UDPTouchFactory>(status);
+/**
+ * Creates motion device
+ * @param params contains parameters for creating the device:
+ *     - "port": the nth jcpad on the adapter
+ */
+std::unique_ptr<Input::TouchDevice> UDPTouchFactory::Create(const Common::ParamPackage& params) {
+    const std::string ip = params.Get("ip", "127.0.0.1");
+    const int port = params.Get("port", 26760);
+    const int pad = params.Get("pad_index", 0);
 
-    Input::RegisterFactory<Input::MotionDevice>("cemuhookudp", motion_factory);
-    Input::RegisterFactory<Input::TouchDevice>("cemuhookudp", touch_factory);
+    return std::make_unique<UDPTouch>(ip, port, pad, client.get());
 }
 
-State::~State() {
-    Input::UnregisterFactory<Input::TouchDevice>("cemuhookudp");
-    Input::UnregisterFactory<Input::MotionDevice>("cemuhookudp");
+void UDPTouchFactory::BeginConfiguration() {
+    polling = true;
+    client->BeginConfiguration();
 }
 
-std::vector<Common::ParamPackage> State::GetInputDevices() const {
-    // TODO support binding udp devices
-    return {};
+void UDPTouchFactory::EndConfiguration() {
+    polling = false;
+    client->EndConfiguration();
 }
 
-void State::ReloadUDPClient() {
-    client->ReloadSocket(Settings::values.udp_input_address, Settings::values.udp_input_port,
-                         Settings::values.udp_pad_index);
+Common::ParamPackage UDPTouchFactory::GetNextInput() {
+    Common::ParamPackage params;
+    CemuhookUDP::UDPPadStatus pad;
+    auto& queue = client->GetPadQueue();
+    for (std::size_t pad_number = 0; pad_number < queue.size(); ++pad_number) {
+        while (queue[pad_number].Pop(pad)) {
+            if (pad.touch == CemuhookUDP::PadTouch::Undefined) {
+                continue;
+            }
+            params.Set("engine", "cemuhookudp");
+            params.Set("ip", "127.0.0.1");
+            params.Set("port", 26760);
+            params.Set("pad_index", static_cast<int>(pad_number));
+            params.Set("touch", static_cast<u16>(pad.touch));
+            return params;
+        }
+    }
+    return params;
 }
 
-std::unique_ptr<State> Init() {
-    return std::make_unique<State>();
-}
-} // namespace InputCommon::CemuhookUDP
+} // namespace InputCommon
diff --git a/src/input_common/udp/udp.h b/src/input_common/udp/udp.h
index 672a5c812e..ea3fd41751 100644
--- a/src/input_common/udp/udp.h
+++ b/src/input_common/udp/udp.h
@@ -1,32 +1,57 @@
-// Copyright 2018 Citra Emulator Project
+// Copyright 2020 yuzu Emulator Project
 // Licensed under GPLv2 or any later version
 // Refer to the license.txt file included.
 
 #pragma once
 
 #include <memory>
-#include <vector>
-#include "common/param_package.h"
+#include "core/frontend/input.h"
+#include "input_common/udp/client.h"
 
-namespace InputCommon::CemuhookUDP {
+namespace InputCommon {
 
-class Client;
-class UDPMotionFactory;
-class UDPTouchFactory;
-
-class State {
+/// A motion device factory that creates motion devices from udp clients
+class UDPMotionFactory final : public Input::Factory<Input::MotionDevice> {
 public:
-    State();
-    ~State();
-    void ReloadUDPClient();
-    std::vector<Common::ParamPackage> GetInputDevices() const;
+    explicit UDPMotionFactory(std::shared_ptr<CemuhookUDP::Client> client_);
+
+    std::unique_ptr<Input::MotionDevice> Create(const Common::ParamPackage& params) override;
+
+    Common::ParamPackage GetNextInput();
+
+    /// For device input configuration/polling
+    void BeginConfiguration();
+    void EndConfiguration();
+
+    bool IsPolling() const {
+        return polling;
+    }
 
 private:
-    std::unique_ptr<Client> client;
-    std::shared_ptr<UDPMotionFactory> motion_factory;
-    std::shared_ptr<UDPTouchFactory> touch_factory;
+    std::shared_ptr<CemuhookUDP::Client> client;
+    bool polling = false;
 };
 
-std::unique_ptr<State> Init();
+/// A touch device factory that creates touch devices from udp clients
+class UDPTouchFactory final : public Input::Factory<Input::TouchDevice> {
+public:
+    explicit UDPTouchFactory(std::shared_ptr<CemuhookUDP::Client> client_);
 
-} // namespace InputCommon::CemuhookUDP
+    std::unique_ptr<Input::TouchDevice> Create(const Common::ParamPackage& params) override;
+
+    Common::ParamPackage GetNextInput();
+
+    /// For device input configuration/polling
+    void BeginConfiguration();
+    void EndConfiguration();
+
+    bool IsPolling() const {
+        return polling;
+    }
+
+private:
+    std::shared_ptr<CemuhookUDP::Client> client;
+    bool polling = false;
+};
+
+} // namespace InputCommon
diff --git a/src/yuzu/configuration/configure_input_player.cpp b/src/yuzu/configuration/configure_input_player.cpp
index 7f4b794dc3..55ea7ccdef 100644
--- a/src/yuzu/configuration/configure_input_player.cpp
+++ b/src/yuzu/configuration/configure_input_player.cpp
@@ -18,6 +18,7 @@
 #include "core/hle/service/sm/sm.h"
 #include "input_common/gcadapter/gc_poller.h"
 #include "input_common/main.h"
+#include "input_common/udp/udp.h"
 #include "ui_configure_input_player.h"
 #include "yuzu/configuration/config.h"
 #include "yuzu/configuration/configure_input_player.h"
@@ -149,6 +150,14 @@ QString ButtonToText(const Common::ParamPackage& param) {
         return GetKeyName(param.Get("code", 0));
     }
 
+    if (param.Get("engine", "") == "cemuhookudp") {
+        if (param.Has("pad_index")) {
+            const QString motion_str = QString::fromStdString(param.Get("pad_index", ""));
+            return QObject::tr("Motion %1").arg(motion_str);
+        }
+        return GetKeyName(param.Get("code", 0));
+    }
+
     if (param.Get("engine", "") == "sdl") {
         if (param.Has("hat")) {
             const QString hat_str = QString::fromStdString(param.Get("hat", ""));
@@ -455,6 +464,13 @@ ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_i
                 return;
             }
         }
+        if (input_subsystem->GetUDPMotions()->IsPolling()) {
+            params = input_subsystem->GetUDPMotions()->GetNextInput();
+            if (params.Has("engine")) {
+                SetPollingResult(params, false);
+                return;
+            }
+        }
         for (auto& poller : device_pollers) {
             params = poller->GetNextInput();
             if (params.Has("engine")) {
@@ -746,6 +762,10 @@ void ConfigureInputPlayer::HandleClick(
         input_subsystem->GetGCAnalogs()->BeginConfiguration();
     }
 
+    if (type == InputCommon::Polling::DeviceType::Motion) {
+        input_subsystem->GetUDPMotions()->BeginConfiguration();
+    }
+
     timeout_timer->start(2500); // Cancel after 2.5 seconds
     poll_timer->start(50);      // Check for new inputs every 50ms
 }
@@ -763,6 +783,8 @@ void ConfigureInputPlayer::SetPollingResult(const Common::ParamPackage& params,
     input_subsystem->GetGCButtons()->EndConfiguration();
     input_subsystem->GetGCAnalogs()->EndConfiguration();
 
+    input_subsystem->GetUDPMotions()->EndConfiguration();
+
     if (!abort) {
         (*input_setter)(params);
     }