diff --git a/LEGO1/lego/legoomni/include/legocontrolmanager.h b/LEGO1/lego/legoomni/include/legocontrolmanager.h
index e2861885..5b83a715 100644
--- a/LEGO1/lego/legoomni/include/legocontrolmanager.h
+++ b/LEGO1/lego/legoomni/include/legocontrolmanager.h
@@ -1,6 +1,7 @@
 #ifndef LEGOCONTROLMANAGER_H
 #define LEGOCONTROLMANAGER_H
 
+#include "legoeventnotificationparam.h"
 #include "mxcore.h"
 #include "mxpresenterlist.h"
 
@@ -28,13 +29,20 @@ public:
 	void FUN_10028df0(MxPresenterList* p_presenterList);
 	void Register(MxCore* p_listener);
 	void Unregister(MxCore* p_listener);
+	MxBool FUN_10029210(LegoEventNotificationParam& p_param, MxPresenter* p_presenter);
 	void FUN_100293c0(undefined4, const char*, undefined2);
 
+	inline undefined4 GetUnknown0x0c() { return m_unk0x0c; }
+	inline undefined GetUnknown0x10() { return m_unk0x10; }
+
 	// SYNTHETIC: LEGO1 0x10028d40
 	// LegoControlManager::`scalar deleting destructor'
 
 private:
-	undefined m_padding0x08[0x58]; // 0x08
+	undefined4 m_unk0x08;          // 0x08
+	undefined4 m_unk0x0c;          // 0x0c
+	undefined m_unk0x10;           // 0x10
+	undefined m_padding0x14[0x4c]; // 0x14
 };
 
 #endif // LEGOCONTROLMANAGER_H
diff --git a/LEGO1/lego/legoomni/include/legoeventnotificationparam.h b/LEGO1/lego/legoomni/include/legoeventnotificationparam.h
index a27ee9df..9eb6c7b5 100644
--- a/LEGO1/lego/legoomni/include/legoeventnotificationparam.h
+++ b/LEGO1/lego/legoomni/include/legoeventnotificationparam.h
@@ -3,6 +3,7 @@
 
 #include "mxnotificationparam.h"
 #include "mxtypes.h"
+#include "roi/legoroi.h"
 
 #include <stdlib.h>
 
@@ -15,7 +16,7 @@ public:
 	{
 		LegoEventNotificationParam* clone =
 			new LegoEventNotificationParam(m_type, m_sender, m_modifier, m_x, m_y, m_key);
-		clone->m_unk0x1c = m_unk0x1c;
+		clone->m_roi = m_roi;
 		return clone;
 	}; // vtable+0x4
 
@@ -28,7 +29,7 @@ public:
 		MxS32 p_y,
 		MxU8 p_key
 	)
-		: MxNotificationParam(p_type, p_sender), m_modifier(p_modifier), m_x(p_x), m_y(p_y), m_key(p_key), m_unk0x1c(0)
+		: MxNotificationParam(p_type, p_sender), m_modifier(p_modifier), m_x(p_x), m_y(p_y), m_key(p_key), m_roi(NULL)
 	{
 	}
 
@@ -37,12 +38,14 @@ public:
 	inline MxS32 GetX() const { return m_x; }
 	inline MxS32 GetY() const { return m_y; }
 
+	inline void SetROI(LegoROI* p_roi) { m_roi = p_roi; }
+
 protected:
 	MxU8 m_modifier; // 0x0c
 	MxS32 m_x;       // 0x10
 	MxS32 m_y;       // 0x14
 	MxU8 m_key;      // 0x18
-	MxU32 m_unk0x1c; // 0x1c
+	LegoROI* m_roi;  // 0x1c
 };
 
 // SYNTHETIC: LEGO1 0x10028770
diff --git a/LEGO1/lego/legoomni/include/legoinputmanager.h b/LEGO1/lego/legoomni/include/legoinputmanager.h
index 89e30dc7..eada8f70 100644
--- a/LEGO1/lego/legoomni/include/legoinputmanager.h
+++ b/LEGO1/lego/legoomni/include/legoinputmanager.h
@@ -83,8 +83,8 @@ public:
 	void ReleaseDX();
 	MxResult GetJoystickId();
 	MxResult GetJoystickState(MxU32* p_joystickX, MxU32* p_joystickY, DWORD* p_buttonsState, MxU32* p_povPosition);
-	void SetTimer();
-	void KillTimer();
+	void StartAutoDragTimer();
+	void StopAutoDragTimer();
 	void EnableInputProcessing();
 	void SetCamera(LegoCameraController* p_camera);
 	void ClearCamera();
@@ -109,6 +109,7 @@ public:
 
 	void ProcessEvents();
 	MxBool ProcessOneEvent(LegoEventNotificationParam& p_param);
+	MxBool FUN_1005cdf0(LegoEventNotificationParam& p_param);
 
 	// SYNTHETIC: LEGO1 0x1005b8d0
 	// LegoInputManager::`scalar deleting destructor'
@@ -122,8 +123,8 @@ private:
 	undefined4 m_unk0x6c;                    // 0x6c
 	undefined4 m_unk0x70;                    // 0x70
 	undefined4 m_unk0x74;                    // 0x74
-	UINT m_timer;                            // 0x78
-	UINT m_timeout;                          // 0x7c
+	UINT m_autoDragTimerID;                  // 0x78
+	UINT m_autoDragTime;                     // 0x7c
 	undefined m_unk0x80;                     // 0x80
 	undefined m_unk0x81;                     // 0x81
 	LegoControlManager* m_controlManager;    // 0x84
diff --git a/LEGO1/lego/legoomni/include/legovideomanager.h b/LEGO1/lego/legoomni/include/legovideomanager.h
index 72cb2148..ce9963e4 100644
--- a/LEGO1/lego/legoomni/include/legovideomanager.h
+++ b/LEGO1/lego/legoomni/include/legovideomanager.h
@@ -31,7 +31,8 @@ public:
 		override;                                                                          // vtable+0x2c
 	virtual MxResult RealizePalette(MxPalette*) override;                                  // vtable+0x30
 	virtual void UpdateView(MxU32 p_x, MxU32 p_y, MxU32 p_width, MxU32 p_height) override; // vtable+0x34
-	virtual void VTable0x38(undefined4, undefined4);                                       // vtable+0x38
+	virtual MxPresenter* GetPresenterAt(MxS32 p_x, MxS32 p_y);                             // vtable+0x38
+
 	// FUNCTION: LEGO1 0x1007ab10
 	virtual LegoUnknown100d9d00* VTable0x3c() { return m_unk0x100d9d00; } // vtable+0x3c
 
diff --git a/LEGO1/lego/legoomni/src/control/legocontrolmanager.cpp b/LEGO1/lego/legoomni/src/control/legocontrolmanager.cpp
index 5b51b8ec..465ccf9f 100644
--- a/LEGO1/lego/legoomni/src/control/legocontrolmanager.cpp
+++ b/LEGO1/lego/legoomni/src/control/legocontrolmanager.cpp
@@ -1,5 +1,8 @@
 #include "legocontrolmanager.h"
 
+#include "legoeventnotificationparam.h"
+#include "mxpresenter.h"
+
 DECOMP_SIZE_ASSERT(LegoControlManager, 0x60);
 
 // STUB: LEGO1 0x10028520
@@ -32,6 +35,12 @@ void LegoControlManager::Unregister(MxCore* p_listener)
 	// TODO
 }
 
+// STUB: LEGO1 0x10029210
+MxBool LegoControlManager::FUN_10029210(LegoEventNotificationParam& p_param, MxPresenter* p_presenter)
+{
+	return TRUE;
+}
+
 // STUB: LEGO1 0x100293c0
 void LegoControlManager::FUN_100293c0(undefined4, const char*, undefined2)
 {
diff --git a/LEGO1/lego/legoomni/src/input/legoinputmanager.cpp b/LEGO1/lego/legoomni/src/input/legoinputmanager.cpp
index 2a551c71..05f09061 100644
--- a/LEGO1/lego/legoomni/src/input/legoinputmanager.cpp
+++ b/LEGO1/lego/legoomni/src/input/legoinputmanager.cpp
@@ -2,7 +2,9 @@
 
 #include "legocontrolmanager.h"
 #include "legoomni.h"
+#include "legovideomanager.h"
 #include "mxautolocker.h"
+#include "roi/legoroi.h"
 
 DECOMP_SIZE_ASSERT(LegoInputManager, 0x338)
 DECOMP_SIZE_ASSERT(LegoNotifyList, 0x18)
@@ -23,7 +25,7 @@ LegoInputManager::LegoInputManager()
 	m_camera = NULL;
 	m_eventQueue = NULL;
 	m_unk0x80 = 0;
-	m_timer = 0;
+	m_autoDragTimerID = 0;
 	m_unk0x6c = 0;
 	m_unk0x70 = 0;
 	m_controlManager = NULL;
@@ -39,7 +41,7 @@ LegoInputManager::LegoInputManager()
 	m_unk0x335 = FALSE;
 	m_unk0x336 = FALSE;
 	m_unk0x74 = 0x19;
-	m_timeout = 1000;
+	m_autoDragTime = 1000;
 }
 
 // FUNCTION: LEGO1 0x1005b8b0
@@ -279,28 +281,142 @@ void LegoInputManager::ProcessEvents()
 	}
 }
 
-// STUB: LEGO1 0x1005c9c0
+// FUNCTION: LEGO1 0x1005c9c0
 MxBool LegoInputManager::ProcessOneEvent(LegoEventNotificationParam& p_param)
+{
+	MxBool processRoi;
+
+	if (p_param.GetType() == c_notificationKeyPress) {
+		if (!Lego()->IsTimerRunning() || p_param.GetKey() == 0x13) {
+			if (p_param.GetKey() == 0x10) {
+				if (m_unk0x195) {
+					m_unk0x80 = 0;
+					p_param.SetType(c_notificationDrag);
+
+					if (m_camera) {
+						m_camera->Notify(p_param);
+					}
+				}
+
+				m_unk0x195 = !m_unk0x195;
+				return TRUE;
+			}
+
+			LegoNotifyListCursor cursor(m_keyboardNotifyList);
+			MxCore* target;
+
+			while (cursor.Next(target)) {
+				if (target->Notify(p_param) != 0) {
+					return TRUE;
+				}
+			}
+		}
+	}
+	else {
+		if (!Lego()->IsTimerRunning()) {
+			processRoi = TRUE;
+
+			if (m_unk0x335 != 0) {
+				if (p_param.GetType() == c_notificationButtonDown) {
+					LegoEventNotificationParam notification(c_notificationKeyPress, NULL, 0, 0, 0, ' ');
+					LegoNotifyListCursor cursor(m_keyboardNotifyList);
+					MxCore* target;
+
+					while (cursor.Next(target)) {
+						if (target->Notify(notification) != 0) {
+							return TRUE;
+						}
+					}
+				}
+
+				return TRUE;
+			}
+
+			if (m_unk0x195 && p_param.GetType() == c_notificationButtonDown) {
+				m_unk0x195 = 0;
+				return TRUE;
+			}
+
+			if (m_world != NULL && m_world->Notify(p_param) != 0) {
+				return TRUE;
+			}
+
+			if (p_param.GetType() == c_notificationButtonDown) {
+				MxPresenter* presenter = VideoManager()->GetPresenterAt(p_param.GetX(), p_param.GetY());
+
+				if (presenter) {
+					if (presenter->GetDisplayZ() < 0) {
+						processRoi = FALSE;
+
+						if (m_controlManager->FUN_10029210(p_param, presenter)) {
+							return TRUE;
+						}
+					}
+					else {
+						LegoROI* roi = PickROI(p_param.GetX(), p_param.GetY());
+
+						if (roi == NULL && m_controlManager->FUN_10029210(p_param, presenter)) {
+							return TRUE;
+						}
+					}
+				}
+			}
+			else if (p_param.GetType() == c_notificationButtonUp) {
+				if (g_unk0x100f31b0 != -1 || m_controlManager->GetUnknown0x10() ||
+					m_controlManager->GetUnknown0x0c() == 1) {
+					MxBool result = m_controlManager->FUN_10029210(p_param, NULL);
+					StopAutoDragTimer();
+
+					m_unk0x80 = 0;
+					m_unk0x81 = 0;
+					return result;
+				}
+			}
+
+			if (FUN_1005cdf0(p_param)) {
+				if (processRoi && p_param.GetType() == c_notificationType11) {
+					LegoROI* roi = PickROI(p_param.GetX(), p_param.GetY());
+					p_param.SetROI(roi);
+
+					if (roi && roi->GetUnk0x0c() == 1) {
+						for (OrientableROI* oroi = roi->GetUnknown0xd4(); oroi; oroi = oroi->GetUnknown0xd4())
+							roi = (LegoROI*) oroi;
+
+						LegoEntity* entity = roi->GetUnknown0x104();
+						if (entity && entity->Notify(p_param) != 0) {
+							return TRUE;
+						}
+					}
+				}
+
+				if (m_camera && m_camera->Notify(p_param) != 0) {
+					return TRUE;
+				}
+			}
+		}
+	}
+
+	return FALSE;
+}
+
+// STUB: LEGO1 0x1005cdf0
+MxBool LegoInputManager::FUN_1005cdf0(LegoEventNotificationParam& p_param)
 {
 	// TODO
 	return FALSE;
 }
 
 // FUNCTION: LEGO1 0x1005cfb0
-void LegoInputManager::SetTimer()
+void LegoInputManager::StartAutoDragTimer()
 {
-	LegoOmni* omni = LegoOmni::GetInstance();
-	UINT timer = ::SetTimer(omni->GetWindowHandle(), 1, m_timeout, NULL);
-	m_timer = timer;
+	m_autoDragTimerID = ::SetTimer(LegoOmni::GetInstance()->GetWindowHandle(), 1, m_autoDragTime, NULL);
 }
 
 // FUNCTION: LEGO1 0x1005cfd0
-void LegoInputManager::KillTimer()
+void LegoInputManager::StopAutoDragTimer()
 {
-	if (m_timer != 0) {
-		LegoOmni* omni = LegoOmni::GetInstance();
-		::KillTimer(omni->GetWindowHandle(), m_timer);
-	}
+	if (m_autoDragTimerID)
+		::KillTimer(LegoOmni::GetInstance()->GetWindowHandle(), m_autoDragTimerID);
 }
 
 // FUNCTION: LEGO1 0x1005cff0
diff --git a/LEGO1/lego/legoomni/src/video/legovideomanager.cpp b/LEGO1/lego/legoomni/src/video/legovideomanager.cpp
index f31879b3..cbb68b79 100644
--- a/LEGO1/lego/legoomni/src/video/legovideomanager.cpp
+++ b/LEGO1/lego/legoomni/src/video/legovideomanager.cpp
@@ -330,10 +330,19 @@ void LegoVideoManager::DrawFPS()
 	// TODO
 }
 
-// STUB: LEGO1 0x1007c080
-void LegoVideoManager::VTable0x38(undefined4, undefined4)
+// FUNCTION: LEGO1 0x1007c080
+MxPresenter* LegoVideoManager::GetPresenterAt(MxS32 p_x, MxS32 p_y)
 {
-	// TODO
+	MxPresenterListCursor cursor(m_presenters);
+	MxPresenter* presenter;
+
+	while (cursor.Prev(presenter)) {
+		if (presenter->IsHit(p_x, p_y)) {
+			return presenter;
+		}
+	}
+
+	return NULL;
 }
 
 // FUNCTION: LEGO1 0x1007c290
diff --git a/LEGO1/lego/sources/roi/legoroi.h b/LEGO1/lego/sources/roi/legoroi.h
index 9b586353..6ac30b45 100644
--- a/LEGO1/lego/sources/roi/legoroi.h
+++ b/LEGO1/lego/sources/roi/legoroi.h
@@ -42,6 +42,7 @@ public:
 
 	inline const char* GetUnknown0xe4() { return m_unk0xe4; }
 	inline LegoEntity* GetUnknown0x104() { return m_unk0x104; }
+
 	inline void SetUnknown0x104(LegoEntity* p_unk0x104) { m_unk0x104 = p_unk0x104; }
 
 	// SYNTHETIC: LEGO1 0x100a9ad0
diff --git a/LEGO1/omni/include/mxnotificationparam.h b/LEGO1/omni/include/mxnotificationparam.h
index e990abe6..a6055fa2 100644
--- a/LEGO1/omni/include/mxnotificationparam.h
+++ b/LEGO1/omni/include/mxnotificationparam.h
@@ -48,6 +48,8 @@ public:
 	inline MxCore* GetSender() const { return m_sender; }
 	inline NotificationId GetType() const { return m_type; }
 
+	inline void SetType(NotificationId p_type) { m_type = p_type; }
+
 protected:
 	NotificationId m_type; // 0x04
 	MxCore* m_sender;      // 0x08
diff --git a/LEGO1/realtime/orientableroi.h b/LEGO1/realtime/orientableroi.h
index 36b8ae50..adbd493d 100644
--- a/LEGO1/realtime/orientableroi.h
+++ b/LEGO1/realtime/orientableroi.h
@@ -32,6 +32,7 @@ public:
 	const float* GetWorldPosition() const { return m_local2world[3]; }
 	const float* GetWorldDirection() const { return m_local2world[2]; }
 	const float* GetWorldUp() const { return m_local2world[1]; }
+	OrientableROI* GetUnknown0xd4() const { return m_unk0xd4; }
 
 protected:
 	MxMatrix m_local2world;           // 0x10
@@ -46,7 +47,7 @@ protected:
 	Mx3DPointFloat m_unk0x94;               // 0x94
 	BoundingSphere m_world_bounding_sphere; // 0xa8
 	Mx3DPointFloat m_world_velocity;        // 0xc0
-	undefined4 m_unk0xd4;                   // 0xd4
+	OrientableROI* m_unk0xd4;               // 0xd4
 	undefined4 m_unk0xd8;                   // 0xd8
 };
 
diff --git a/LEGO1/realtime/roi.h b/LEGO1/realtime/roi.h
index 08db5080..b5cbefa5 100644
--- a/LEGO1/realtime/roi.h
+++ b/LEGO1/realtime/roi.h
@@ -103,6 +103,8 @@ public:
 	int GetLODCount() const { return m_lods ? m_lods->Size() : 0; }
 	const CompoundObject* GetComp() const { return m_comp; }
 
+	inline undefined GetUnk0x0c() { return m_unk0xc; }
+
 	// SYNTHETIC: LEGO1 0x100a5d60
 	// ROI::`scalar deleting destructor'