My "solution" for key/mouse remote events handling

This is the code that I’m using to send key/mouse events to a non-window component. It also draws the component and has some extra options. Just sharing as from my searches I couldn’t find anything and getPeer()->handleMouseEvent didn’t work (I guess it works only when you have an actual window somewhere?) The code is what it is, but feel free to ask anything. Cheers! :slight_smile:

/*
BEGIN_JUCE_MODULE_DECLARATION
    ID:               WMemoryUI
    vendor:           Wusik Dot Com
    version:          1.0.0
    name:             Wusik Memory Interface
    description:      Allows an interface to be drawn from memory (windowless) and receive mouse and keyboard events.
    website:          https://www.Wusik.com
    dependencies:     
	license:          Open-Source
END_JUCE_MODULE_DECLARATION 
*/
//
#ifndef WMEMUI_H_INCLUDED
#define WMEMUI_H_INCLUDED
//
// -------------------------------------------------------------------------------------------------------------------------------
#include <juce_core/juce_core.h>
#include <juce_gui_basics/juce_gui_basics.h>
using namespace juce;
//
// -------------------------------------------------------------------------------------------------------------------------------
/*
	To use this code you just create a variable based on WMemoryInterface, like this:
	ScopedPointer<WMemoryInterface> memoryInterface;
	memoryInterface = new WMemoryInterface(editor, tool tip time (in ms), tool tip window); // The Tool Tip Window is optional. 
		Also, to be able to use it you must set all your components to set 
		memoryInterface->showToolTip("The Tool Tip Text");

	Somewhere you will receive your Mouse Events from another process and will add using:
		memoryInterface->addEvent(theEvent); // This uses the WKeyMouseEvent structure

	In your editor, you must use a timer that will call the process that will handle keyboard, mouse and the interface draw.
		memoryInterface->process();

	The process() will check for all mouse/key events and in the end it will redraw the interfaceBuffer image. Once that's done,
		the following function will return as TRUE and you can use the image to drawn somewhere: isInterfaceBufferReady();
		Once you have used the image you must call setInterfaceBufferReadyNext() so process() can redo the image with the updated interface.

	If you change something in the interface, first stop calling process(), do the changes, and in the end call memoryInterface->reset();

*/
class WMemoryInterface
{
public:
	enum
	{
		kMouse_Down = 0,
		kMouse_Up,
		kMouse_Move,
		kMouse_Drag,
		kMouse_Wheel,
		kMouse_DoubleClick,
		kMouse_KeyPressed, // x+y holds the key code //
		//
		kMouseFlag_LeftButton = 0,
		kMouseFlag_RightButton,
		kMouseFlag_MiddleButton,
		kMouseFlag_Shift,
		kMouseFlag_Ctrl,
		kMouseFlag_Alt,
		kMouseFlag_Command,
		kMouseFlag_Menu
	};
	//
	class WFlagOp
	{
	public:
		static inline void set(const int16 flagEnum, int16 &flag, bool shouldSet = true) { if (shouldSet) flag |= bitToMask(flagEnum); else flag &= ~bitToMask(flagEnum); }
		static inline bool get(const int16 flagEnum, int16 &flag) { return ((flag & bitToMask(flagEnum)) > 0); }
		static inline bool get(const uint8 flagEnum, uint8 &flag) { return ((flag & bitToMask(flagEnum)) > 0); }
		static inline int16 bitToMask(const int16 bit) { return (int16)1 << (bit & 31); }
	};
	//
	class WKeyMouseEvent
	{
	public:
		WKeyMouseEvent() { };
		WKeyMouseEvent(int16 _x, int16 _y, uint8 _flags, uint8 _type) : x(_x), y(_y), flags(_flags), type(_type) { };
		WKeyMouseEvent(WKeyMouseEvent& event, Rectangle<int> position)
		{
			x = event.x - position.getX();
			y = event.y - position.getY();
			flags = event.flags;
			type = event.type;
		}
		WKeyMouseEvent(WKeyMouseEvent& event)
		{
			x = event.x;
			y = event.y;
			flags = event.flags;
			type = event.type;
		}
		//
		int16 x = 0, y = 0;
		uint8 flags = 0;
		uint8 type = 0;
	};
	//
	class WEvents
	{
	public:
		WKeyMouseEvent* getNext()
		{
			int16 curPost = mouseEventsRead.get();
			int16 newPos = curPost + 1;
			if (newPos >= WMAX_STACK) newPos = 0;
			mouseEventsRead.set(newPos);
			return &mouseEventsStack[curPost];
		};
		void add(WKeyMouseEvent& event)
		{
			mouseEventsStack[mouseEventsWrite.get()] = event;
			int16 newPos = mouseEventsWrite.get() + 1;
			if (newPos >= WMAX_STACK) newPos = 0;
			mouseEventsWrite.set(newPos);
		};
		bool hasEvents() { return mouseEventsWrite.get() != mouseEventsRead.get(); };
		void reset() { mouseEventsWrite = mouseEventsRead = 0; };
		//
	private:
		static const int WMAX_STACK = 1024;
		WKeyMouseEvent mouseEventsStack[WMAX_STACK];
		Atomic<int16> mouseEventsWrite = 0;
		Atomic<int16> mouseEventsRead = 0;
	};
	//
	// ----------------------------------------------------------------------------------------------------- //
	//
	WMemoryInterface(Component* _mainComponent, int _toolTipTime = 0, TooltipWindow* _tooltipWindow = nullptr) : 
		mainComponent(_mainComponent), toolTipTime(_toolTipTime), tooltipWindow(_tooltipWindow),
		mouseInput(Desktop::getInstance().getMainMouseSource()) { };
	//
	void process()
	{
		if (!editorOpen) return;
		//
		theTime = Time::getCurrentTime();
		while (theEvents.hasEvents())
		{
			WKeyMouseEvent* wEvent = theEvents.getNext();
			//
			if (wEvent->type != kMouse_KeyPressed) lastMousePosition = Point<int>(wEvent->x, wEvent->y);
			if (wEvent->type == kMouse_Down) lastDownPosition = Point<int>(wEvent->x, wEvent->y);
			//
			int theFlags = 0;
			if (WFlagOp::get(kMouseFlag_LeftButton, wEvent->flags)) theFlags |= ModifierKeys::leftButtonModifier;
			if (WFlagOp::get(kMouseFlag_RightButton, wEvent->flags)) theFlags |= ModifierKeys::rightButtonModifier;
			if (WFlagOp::get(kMouseFlag_MiddleButton, wEvent->flags)) theFlags |= ModifierKeys::middleButtonModifier;
			//
			if (WFlagOp::get(kMouseFlag_Menu, wEvent->flags)) theFlags |= ModifierKeys::popupMenuClickModifier;
			if (WFlagOp::get(kMouseFlag_Shift, wEvent->flags)) theFlags |= ModifierKeys::shiftModifier;
			if (WFlagOp::get(kMouseFlag_Ctrl, wEvent->flags)) theFlags |= ModifierKeys::ctrlModifier;
			if (WFlagOp::get(kMouseFlag_Command, wEvent->flags)) theFlags |= ModifierKeys::commandModifier;
			if (WFlagOp::get(kMouseFlag_Alt, wEvent->flags)) theFlags |= ModifierKeys::altModifier;
			//
			ModifierKeys modEvent(theFlags);
			//
			if (wEvent->type == kMouse_KeyPressed)
			{
				int keyCode = wEvent->x;
				keyCode |= wEvent->y << 16;
				KeyPress key(keyCode, modEvent, 'A');
				//
				if (keyComponent != nullptr)
				{
					if (!keyComponent->keyPressed(key)) mainComponent->keyPressed(key);
				}
			}
			else if (wEvent->type == kMouse_Wheel)
			{
				if (edComponent != nullptr)
				{
					Point<float> thePoint(wEvent->x - mouseOffset.getX(), wEvent->y - mouseOffset.getY());
					MouseEvent theEvent(mouseInput, thePoint, modEvent, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, mainComponent, mainComponent, theTime, thePoint, theTime, 1, false);
					//
					MouseWheelDetails wheelDetails;
					wheelDetails.deltaX = float(wEvent->x) / 1000.0f;
					wheelDetails.deltaY = float(wEvent->y) / 1000.0f;
					edComponent->mouseWheelMove(theEvent, wheelDetails);
				}
			}
			else if (wEvent->type == kMouse_Drag)
			{
				if (tooltipWindow != nullptr) tooltipWindow->hideTip();
				//
				if (edComponent != nullptr)
				{
					waDragged = true;
					Point<float> thePoint(wEvent->x - mouseOffset.getX(), wEvent->y - mouseOffset.getY());
					MouseEvent theEvent(mouseInput, thePoint, modEvent, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, mainComponent, mainComponent, theTime, mouseDownPos, theTime, 1, true);
					edComponent->mouseDrag(theEvent);
				}
			}
			else
			{
				mouseOffset.setXY(0, 0);
				if (!processComponents(mainComponent, wEvent, &modEvent, false))
				{
					Point<float> thePoint(wEvent->x, wEvent->y);
					MouseEvent theEvent(mouseInput, thePoint, modEvent, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, mainComponent, mainComponent, theTime, thePoint, theTime, 1, waDragged);
					//
					if (edComponent != nullptr) edComponent->mouseExit(theEvent);
					edComponent = nullptr;
					if (wEvent->type == kMouse_Down) keyComponent = nullptr;
					if (tooltipWindow != nullptr)
					{
						tooltipWindow->hideTip();
						showThisToolTip = String();
					}
					//
					switch (wEvent->type)
					{
						case kMouse_Up:		mainComponent->mouseUp(theEvent);   break;
						case kMouse_Down:	mainComponent->mouseDown(theEvent); break;
						case kMouse_Move:	mainComponent->mouseMove(theEvent); break;
						case kMouse_DoubleClick: mainComponent->mouseDoubleClick(theEvent); break;
					}
				}
			}
		}
		//
		// ========================================================================== /
		//
		if (toolTipTime > 0 && tooltipWindow != nullptr && (Time::getApproximateMillisecondCounter() - lastMouseTime) > toolTipTime)
		{
			if (showThisToolTip.isNotEmpty())
			{
				tooltipWindow->displayTip(lastMousePosition, showThisToolTip);
				showThisToolTip = String();
			}
		}
		//
		if (!interfaceBufferReady.get())
		{
			if (!interfaceBuffer.isValid() || interfaceBuffer.getWidth() != mainComponent->getWidth() || interfaceBuffer.getHeight() != mainComponent->getHeight()) interfaceBuffer = Image(Image::PixelFormat::ARGB, mainComponent->getWidth(), mainComponent->getHeight(), true);
			Graphics gg(interfaceBuffer);
			mainComponent->paintEntireComponent(gg, false);
			interfaceBufferReady.set(true);
		}
		//
		if (timeEditorOpened > 0 && (Time::getApproximateMillisecondCounter() - timeEditorOpened) > 6000)
		{
			if (lastSize.x != mainComponent->getWidth() || lastSize.y != mainComponent->getHeight())
			{
				lastSize.x = mainComponent->getWidth();
				lastSize.y = mainComponent->getHeight();
				createInitScreenDeleteFile = true;
			}
		}
		else
		{
			lastSize.x = mainComponent->getWidth();
			lastSize.y = mainComponent->getHeight();
		}
		//
		if (createInitScreen)
		{
			createInitScreen = false;
			if (createInitScreenImageFile.isNotEmpty())
			{
				File xFile = createInitScreenImageFile;
				if (!xFile.existsAsFile())
				{
					PNGImageFormat lastImagePNG;
					ScopedPointer<FileOutputStream> imageStream = xFile.createOutputStream();
					lastImagePNG.writeImageToStream(interfaceBuffer, *imageStream.get());
				}
			}
		}
	};
	//
	bool processComponents(Component* theComponent, WKeyMouseEvent* theEvent, ModifierKeys* theModifiers, bool forceNoChilds)
	{
		if (theComponent->getNumChildComponents() == 0 || forceNoChilds)
		{
			Point<float> thePoint(theEvent->x, theEvent->y);
			mouseDownPos = Point<float>(theEvent->x, theEvent->y);
			//
			MouseEvent theMouseEvent(mouseInput, thePoint, *theModifiers, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, mainComponent, mainComponent, theTime, thePoint, theTime, 1, waDragged);
			//
			if (edComponent != nullptr)
			{
				if (edComponent != theComponent)
				{
					edComponent->mouseExit(theMouseEvent);
					//
					if (tooltipWindow != nullptr)
					{
						showThisToolTip = String();
						tooltipWindow->hideTip();
					}
					//
					theComponent->mouseEnter(theMouseEvent);
				}
			}
			else
			{
				if (tooltipWindow != nullptr)
				{
					showThisToolTip = String();
					tooltipWindow->hideTip();
				}
				//
				theComponent->mouseEnter(theMouseEvent);
			}
			//
			switch (theEvent->type)
			{
				case kMouse_Up:
				{
					if (edComponent != nullptr)
					{
						edComponent->mouseUp(theMouseEvent);
						edComponent->mouseExit(theMouseEvent);
					}
					else theComponent->mouseUp(theMouseEvent);
					edComponent = nullptr;
					break;
				}
				//
				case kMouse_Down:
				{
					waDragged = false;
					edComponent = theComponent;
					keyComponent = theComponent;
					theComponent->mouseDown(theMouseEvent);
					//
					if (tooltipWindow != nullptr)
					{
						tooltipWindow->hideTip();
						showThisToolTip = String();
					}
					break;
				}
				//
				case kMouse_DoubleClick:
				{
					edComponent = theComponent;
					keyComponent = theComponent;
					theComponent->mouseDoubleClick(theMouseEvent);
					//
					if (tooltipWindow != nullptr)
					{
						tooltipWindow->hideTip();
						showThisToolTip = String();
					}
					break;
				}
				//
				case kMouse_Move:
				{
					edComponent = theComponent;
					theComponent->mouseMove(theMouseEvent);
					lastMouseTime = Time::getApproximateMillisecondCounter();
					break;
				}
			}
			//
			return true;
		
		}
		else
		{
			int nTotal = theComponent->getNumChildComponents();
			for (int nc = nTotal - 1; nc >= 0; nc--)
			{
				Component* theChild = theComponent->getChildComponent(nc);
				WKeyMouseEvent newEvent(*theEvent, theChild->getBounds());
				//
				if (theChild->isVisible() && theChild->getBounds().contains(theEvent->x, theEvent->y) &&
					(hitTestID.isEmpty() || !theChild->getComponentID().containsIgnoreCase(hitTestID) || theChild->hitTest(theEvent->x, theEvent->y)))
				{
					mouseOffset.addXY(theChild->getBounds().getX(), theChild->getBounds().getY());
					if (processComponents(theChild, &newEvent, theModifiers, false)) return true;
					processComponents(theChild, &newEvent, theModifiers, true);
					return true;
				}
			}
		}
		//
		return false;
	};
	//
	void reset() 
	{ 
		edComponent = nullptr; 
		if (onResetKeyReset) keyComponent = nullptr; 
		theEvents.reset();
	};
	void closeEditor()
	{
		if (createInitScreenDeleteFile && createInitScreenImageFile.isNotEmpty()) File(createInitScreenImageFile).deleteFile();
		editorOpen = false;
	};
	void addEvent(WKeyMouseEvent& theEvent) { theEvents.add(theEvent); };
	void setOnResetKeyReset() { onResetKeyReset = true; };
	void setHitTestID(String theID) { hitTestID = theID; };
	bool isInterfaceBufferReady() { return interfaceBufferReady.get(); };
	void setInterfaceBufferReadyNext() { interfaceBufferReady.set(false); };
	void openEditor() { timeEditorOpened = Time::getApproximateMillisecondCounter(); editorOpen = true; };
	void showToolTip(String toolTip) { showThisToolTip = toolTip; };
	//
	Image interfaceBuffer;
	Point<int> lastMousePosition;
	Point<int> lastDownPosition;
	bool createInitScreenDeleteFile = false;
	String createInitScreenImageFile = String();
	//
private:
	bool editorOpen = false;
	Component* mainComponent;
	String showThisToolTip = String();
	uint32 lastMouseTime = 0;
	Atomic<bool> interfaceBufferReady = false;
	uint32 timeEditorOpened = 0;
	Point<int> lastSize;
	Point<int> mouseOffset;
	uint16 toolTipTime = 0;
	TooltipWindow* tooltipWindow = nullptr;
	String hitTestID = String();
	bool onResetKeyReset = false;
	Component* edComponent = nullptr;
	Component* keyComponent = nullptr;
	MouseInputSource mouseInput;
	Time theTime = Time::getCurrentTime();
	Point<float> mouseDownPos;
	bool waDragged = false;
	bool createInitScreen = true;
	WEvents theEvents;
};
//
#endif

Updated the tooltip code, wasn’t working at all. :shamrock: