/*
 * Copyright 2018-2024, ttcoder of TTS-LLC
 * All rights reserved. Distributed under the terms of the MIT license.
 */


// genode
#include <format/snprintf.h>

// libc.lib.so
#include <ctype.h>  // toupper()
#include <stdio.h>  // sprintf()

// haiku.lib.so
#include <app/Application.h>  // be_app
#include "_CanvasGenode.h"
#include "_GeInput.h"
#include <interface/Window.h>

// this
#include "_WindowNitpickerPriv.h"


// - namespaces -
typedef
	Gui::Session::Command
	Command
	;

// - macros -
#if 0
#define PDBG(...) Genode::log( "\033[33m", __VA_ARGS__, "\033[0m" )
#else
#define PDBG(...) ;
#endif


/**************/
static int debug = 0;


static BString WhatAsStr( int32 what )
{
	char what_str[4 +sizeof('\0')] = {};
	
	// Use the Genode implementation (the libc snprintf() trips a pthread assertion when run in a sig-handler):
	Format::snprintf( what_str, sizeof(what_str), "%4s", (const char*)&what );
	
	return what_str;
}



//#pragma mark - Nitpicker win -


struct NitpickerInternals : public hog::NitWindowBase
{
public:

		NitpickerInternals( BBitmap * paint_offscreen = NULL );
		
		//
		// Override/implement NitWindowBase:
		//
		
		CanvasGenode & Canvas() override; //+ "PaintingSurface" ?
		Input::Session_client & Input() override;
		
		void Create_Bare_Window() override;
		void Destroy_Bare_Window() override;
		void Set_Title( const char * title )  override;
		void To_Front() override;
		void To_Back() override;
		void Background() override;
		void SetClientGeometry( BRect inner_rect ) override;
			///++ "SetInnerGeometry( Rect inner_rect ) !
		
#if defined( HAS_MICRO_WINDOW_MANAGER )
		Gui::Connection & ConnectionAndGeom() override;//nah!: ///+ "DecoratedGeom" ?
		void Set_ClientSide_Decorations(
			BRect inner_frame,
			BRect tab_frame,
			float border_thickness
			) override;
#endif


private:  // Code

		void reGeom(   Gui::Rect inner_rect );
		void reBuffer( Gui::Rect inner_rect );


private:  // Data

		Gui::Connection //*
			nitpicker;
		CanvasGenode //*
			geCanvas;
		
		// Our window (aka 'view') ID
///FIXME: migrate to 'Top_level_view winView' ?
		Gui::View_id
			_handle;// or "bareWindow" or "undecoratedWindow"
				// or just "windowviewId"
		///later: we let the ctor initialize _handle to its default value (0)
		// is that good enough ? Seems to be, given we only have one view/window, so the ID does not matter
};



//#pragma mark - Nitpicker win BASE -


hog::NitWindowBase::~NitWindowBase()
{
}

void hog::NitWindowBase::Canvas_SetOriginAndClipping( BPoint p, BRect r )
{
	Canvas().SetOriginAndClipping( p, r );
}

void hog::NitWindowBase::Canvas_FillRect( BRect r, rgb_color c )
{
	Canvas().FillRect( r, c );
}

void hog::NitWindowBase::Canvas_Update()
{
	Canvas().Update();
}


//static
hog::NitWindowBase * hog::NitWindowBase::Instantiate( BBitmap * offscreen_bitmap )
{
	return new NitpickerInternals( offscreen_bitmap );
}



//#pragma mark - Nitpicker win IMPL -


NitpickerInternals::NitpickerInternals( BBitmap * offscreen_bitmap )
:	nitpicker( be_app->Env(), "BWindow" )
	,geCanvas( be_app->Env(), nitpicker, offscreen_bitmap )
	,_handle()
{
	PDBG( "-- created Decorated-window, bare-window, and surface/canvas --" );
}


CanvasGenode &
	NitpickerInternals::Canvas()
{
	return geCanvas;
}

Input::Session_client &
	NitpickerInternals::Input()
{
	return nitpicker.input;
}


void NitpickerInternals::Create_Bare_Window()
{
	PDBG( "-- creating a 'view'  (T- used caps/mem:  ", be_app->Env().pd().used_caps().value, ", ", be_app->Env().pd().used_ram().value/1024, " KB)" );
	
///FIXME: migrate to 'Top_level_view winView' ?
/* and then just refer to its.id() accessor:
	nitpicker.enqueue<Command::Title>( winView.id(), title );
	.. and so on ..
*/
	nitpicker.view(
		_handle,
		{ }  // we will set the title/rect/front attributes separately
		);
	
	PDBG( "--  created a 'view'  (T+ used caps/mem:  ", be_app->Env().pd().used_caps().value, ", ", be_app->Env().pd().used_ram().value/1024, " KB)" );
}

void NitpickerInternals::Destroy_Bare_Window()
{
	PDBG( "DELETING 'view'  (T- used caps/mem: ", be_app->Env().pd().used_caps().value, ", ", be_app->Env().pd().used_ram().value/1024, " KB)" );
	
	nitpicker.destroy_view( _handle );
	
	PDBG( " DELETED 'view'  (T+ used caps/mem: ", be_app->Env().pd().used_caps().value, ", ", be_app->Env().pd().used_ram().value/1024, " KB)" );
}

void NitpickerInternals::Set_Title( const char * title )
{
	PDBG( "settitle: ", title );
	
	nitpicker.enqueue<Command::Title>( _handle, title );
	nitpicker.execute();
	
#ifdef HAS_MICRO_WINDOW_MANAGER
	geCanvas.SetTitle_DecorCS( title );
#endif
}

void NitpickerInternals::To_Front()
{
	PDBG( "to-front" );
	
	nitpicker.enqueue<Command::Front>( _handle );
	nitpicker.execute();
}

void NitpickerInternals::To_Back()
{
	PDBG( "to-Back" );
	
	nitpicker.enqueue<Command::Back>( _handle );
	nitpicker.execute();
}

void NitpickerInternals::Background()
{
	PDBG( "to-Background" );
	
/// (see matching 'FIXME' in Window.cpp): .. Background() .. neither of the below variants work, the window just hides/disappears, so call To_Back() for now...
	To_Back(); return;
	/**********/
	
#if 0
	// let's try just ::Background on its own
	nitpicker.enqueue<Command::Background>( _handle );
	nitpicker.execute();
#else
	// let's try the whole block from backdrop
	nitpicker.enqueue<Command::Background>( _handle );
	Gui::Rect rect = Gui::Rect::compound( Gui::Point(0,0), Gui::Point(800,600) );//_buffer->size());
	nitpicker.enqueue<Command::Geometry>( _handle, rect );
	nitpicker.enqueue<Command::Back>( _handle );
	nitpicker.execute();
#endif
}


void NitpickerInternals::reGeom( Gui::Rect inner_rect )
{
#ifdef HAS_MICRO_WINDOW_MANAGER
	const Gui::Rect outer_rect =
		geCanvas.DecoratedOuterRect_For_Inner( inner_rect );
#else
	const Gui::Rect outer_rect = inner_rect;
#endif
	
	if( debug )
		Genode::log( "  setGeom: outer_rect: ", outer_rect );
	
	// T0
	nitpicker.enqueue<Command::Geometry>(
		_handle,
		outer_rect
		);
	nitpicker.execute();
}

void NitpickerInternals::reBuffer( Gui::Rect inner_rect )
{
	if( debug )
		Genode::log( "  reBuffer: inner_rect: ", inner_rect );
	
	// T0
	geCanvas.Rebuffer_Inner( inner_rect );
}


#if defined( HAS_MICRO_WINDOW_MANAGER )

Gui::Connection &
	NitpickerInternals::ConnectionAndGeom()
{
	return nitpicker;
}

void NitpickerInternals::Set_ClientSide_Decorations(
			BRect inner_frame,
			BRect tab_frame,
			float border_thickness
			)
{
	if( debug )
	{
		Genode::log( "NitPriv.setDecorations with thickness ", border_thickness, " and rect: " );
		inner_frame.PrintToStream();
	}
	
	// T0
	geCanvas.SetInsets_DecorCS(
		tab_frame,
		border_thickness
		);
	
	Gui::Rect inner_rect
	{
		Gui::Point( inner_frame.left, inner_frame.top ),
		Gui::Area(  inner_frame.Width()+1, inner_frame.Height()+1)
	};
	
	// T+
	reBuffer( inner_rect );
	// do NOT call this:
	// reGeom(   inner_rect );
	// as that might reset the hide/show state.
}

#endif  // ~HAS_MICRO_WINDOW_MANAGER


void NitpickerInternals::SetClientGeometry( BRect be_inner_rect )
{
	// So, 3 things are kept in sync:
	// - Rebuffer() -- in BWindow
	// - nitpicker.Geom -- below
	// - canvas.decorations -- below
	
	// BRect -> Gui::Rect
	Gui::Rect inner_rect(
		Gui::Point( be_inner_rect.left, be_inner_rect.top ),
		Gui::Area( be_inner_rect.Width() +1., be_inner_rect.Height() +1. )
		);
	
	if( debug )
		Genode::log( "NitPriv.SetGeom-inner: ", inner_rect );
	PDBG( "-- geom x: ", inner_rect.x1(), " to ", inner_rect.x2(), "  y: ", inner_rect.y1(), " to ", inner_rect.y2() );
	
	if( false == inner_rect.valid() )
	{
		// *Hide* window/geometry, leave bitmap buffer intact:
		nitpicker.enqueue<Command::Geometry>(
			_handle,
			Gui::Rect( Gui::Point(), Gui::Area(0, 0) )
			);
		nitpicker.execute();
		
		//
		return;
		//
	}
	// Else:
	
	// T0
	reBuffer( inner_rect );
	reGeom(   inner_rect );
}



//#pragma mark - Microscopic "wm" -


#ifdef HAS_MICRO_WINDOW_MANAGER

hog::Wm::Wm( BWindow & win )
:	ourWin( win )
	,isDragging( false )
	,trackWithHot()
{
}


void hog::Wm::HandleEvent(
			const Input::Event & ev,
			const BPoint screen_where
			)
{
	using namespace Input;
	
	BPoint p;//+"inner_relative"
	{
		p = screen_where;
		p -= ourWin.Frame().LeftTop();
	}
	
	BPoint decor_relative;
	{
		decor_relative = screen_where;
		decor_relative -= ourWin.DecoratorFrame().LeftTop();
	}
	
	bool in_closer = false;
	bool in_draggable_decorations = false;
	{
		// <screen_where> is implicitely inside the client-side decorator frame, but...
		// - is it inside the "close button" part of the decorations ? or...
		// - is it inside the (decorated) inner frame (BWindow.Frame()) ?
		
		if( (B_TITLED_WINDOW_LOOK==ourWin.Look() || B_DOCUMENT_WINDOW_LOOK==ourWin.Look() || B_FLOATING_WINDOW_LOOK==ourWin.Look())
			&& 0==(ourWin.Flags() & B_NOT_CLOSABLE)
			&& decor_relative.x < 15
			&& decor_relative.y < 15 )///xxx if( ourWin.decor.closebutton.Contains(where) )
		{
			in_closer = true;
		}
		else if( false == ourWin.Frame().Contains(screen_where)  &&  0==(ourWin.Flags() & B_NOT_MOVABLE) )
			// not in the "inner" rect -> out in border -> allow dragging the window:
			in_draggable_decorations = true;
	}
	
	if( in_draggable_decorations && ev.key_press(BTN_RIGHT) )
		ourWin.SendBehind( NULL );
	
	if( ev.key_press(BTN_LEFT) )
	{
#if 1
		ourWin.Activate();
#else
		ourWin.ServerWindow()
			.enqueue<Gui::Connection::Command::To_front>(
				ourWin.nitPrivate->_handle,//ServerWindow().Nitview(),
				Gui::Session::View_handle()
				);
		ourWin.ServerWindow().execute();
#endif
		
		if( in_draggable_decorations )
		{
			if( debug >= 2 )
				Genode::log( "  wm: start tracking!" );
			
			// start dragging the window by its decoration
			isDragging = true;
			trackWithHot = p;
		}
		else if( in_closer )
		{
			ourWin.PostMessage( B_QUIT_REQUESTED );
		}
	}
	
	if( ev.key_release(BTN_LEFT) )
	{
		if( debug >= 2 )
			Genode::log( "  wm: stop tracking" );
		
		// stop tracking/moving the window, if we were
		isDragging = false;
	}
	
	ev.handle_absolute_motion(
		[&] ( int x, int y )
		{
			if( false == isDragging )
				// we're NOT moving the window, do not call the below MoveTo()
				return;
				//
			
			if( debug >= 3 )
				Genode::log( "  WM: win.MoveTo(", x, " ", y, ")" );///
			
#if 1
			// this variant must be executed in a "pthread" thread
			BPoint abs( x, y );
			ourWin.MoveTo( abs - trackWithHot );
#else
///ToDo: I can revert to this safe, plain-geometry, code, now that Haiku apps no longer use Genode decorations/WM ?
		----- this variant can be called in a "plain" Thread but fails to keep ourWin.Frame() in sync ----
		----- (that situation is similar to using a proper Genode WM, like SculptOS does) ----
			BPoint abs( x, y );
			BRect r = ourWin.Frame();
			//Genode::log( "win.frame ltrb: ", r.left, " ", r.top, " ", r.right, " ", r.bottom );
			r.OffsetTo( abs - trackWithHot );
			ourWin.ServerWindow()
				.enqueue<Gui::Connection::Command::Geometry>(
					ourWin.nitPrivate->_handle,
					Gui::Rect(
						Gui::Point(r.LeftTop().x, r.LeftTop().y),
						Gui::Point(r.RightBottom().x, r.RightBottom().y) )
					);
			ourWin.ServerWindow().execute();
			
			// make sure BWindow.MessageReceived( B_WINDOW_MOVED ) is called, to update BWindow.fFrame member
			BMessage m( B_WINDOW_MOVED );
			m.AddPoint( "where", ... );
			ourWin.SendMessage( m );
#endif
		}
	);
}

#else  // no-HAS_MICRO_WINDOW_MANAGER  (i.e. we're probably using a proper Genode WM)

///ToDo: BWindow.Frame() syncing, in Genode-WM case: who will be responsible for keeping it in sync ? We are not calling BWindow.MoveTo() here, in e.g. SculptOS, the Genode window manager is calling only Nitpicker/SetGeometry !
// Get inspiration from Sculpt/Menu_view, which has some code
// re. screen coordinates being converted with decorator help(?):
//		_position = Decorator::point_attribute(config);

/// -> a second bug (unrelated ? or maybe related to the Mandelbrot/About display bug ?) is the pop-up menu in Pulse : it fails to display, when using a proper Genode WM !
// (symptoms: maybe "something" gets open, since I can use the pop-up "in the blind" and open Pulse>Settings, with a bit of trying ; and the Settings window opens up.. but stays behind main win, cant bring it to front?! and cant move either of the two windows, they "RETURN back" to their place ?! -> activate full tracing of input_server... Or maybe it's a configuration problem (in <config> xml) ?

#endif  // ~no-HAS_MICRO_WINDOW_MANAGER



//#pragma mark - InputBridge -


hog::InputBridger * hog::create_in_bri( BWindow & win )
{
	return new hog::InputBridger( win );
}

void hog::destroy_in_bri( hog::InputBridger * ptr )
{
	delete ptr;
}


int hog::InputBridger::forwarding_to_pthread( void * bridger_uncast )
{
	// Forward Input::Events from the Genode signal handler, to the Haiku "world"
	// here where we can call PostMessage() to further convert/forward to BWindow.
	// That cannot all be done in the signal handler itself, because
	// BLooper.PostMessage() makes heavy use of e.g. find_thread(NULL), which
	// fails in a non-pthread thread and confuses the heck out of PostMessage.
	// Hence the two-steps/two-threads forwarding.
	
	hog::InputBridger * bridger = static_cast<hog::InputBridger*>( bridger_uncast );
	
//int debug = 1;
	for( ;; )
	{
		bridger->greenlightForwarder.block();
		
		if( bridger->tearDownThread )
			//
			break;
			//
		
		if( debug >= 6 )
			Genode::warning( "  forwarder: received a task to perform" );
		
		// Mode-Change task
		//
		if( bridger->newMode.width > 0 && bridger->newMode.height > 0 )
		{
			Genode::Mutex::Guard
				_( bridger->mutexFwd )
				;
			
			// Tx screen resolution information
			BMessage m( B_SCREEN_CHANGED );
			{
				const BRect r( 0., 0., bridger->newMode.width -1, bridger->newMode.height -1 );
				
				m.AddRect( "frame", r );
				m.AddInt32( "mode",
					B_RGBA32 ///maybe check that newMode.bytes_per_pixel() == 4 ?
					);
			}
			BMessenger( NULL, &bridger->ourWin ).SendMessage( &m );
			
			// Reset
			bridger->newMode = { -1, -1 };
		}
		
		// Input-Event tasks
		//
		for( ;; )
		{
			// sprinkle more tear-down checks for good measure...
/// are these checks a reliable fix ? Or am I just pushing the deadlock bug ever more into a (more difficult to reproduce) corner ?
///ToDo: if I ever get deadlocks again, especially if hard to reproduce, this code here should be prime suspect,
// and I should remove the "sprinkled" statements to make the deadlock easier (not harder) to reproduce, and find a proper fix
if( bridger->tearDownThread ) return 0;//break;
#if 0
			BMessage * m = NULL;
			{
				//..
				Genode::Mutex::Guard
					_( bridger->mutexFwd )
					;
				
				if( bridger->msgsToForward.empty() )
					break;
				
				m = bridger->msgsToForward.front();
				bridger->msgsToForward.pop();
			}
			
			// do the PostMessage /outside/ of the mutex 'hold', just in case:
			
		//	m->PrintToStream();
			bridger->ourWin.PostMessage( m );
			delete m  ; m = NULL;

#else
			Input::Event ev;
			{
				// hold the lock as little as possible since it's a point
				// of contention with the (time sensitive ?) Genode sigh
				
				// this check here seems more critical:
					//acquire this first, then check tear-down second:
				Genode::Mutex::Guard
					_( bridger->mutexFwd )
					;
// last tweak, move this below the above guard:
if( bridger->tearDownThread ) return 0;//break;
				if( bridger->eventsToForward.empty() )
					//
					break;  // go back to blockade.block()
				
if( bridger->tearDownThread ) return 0;//break;
				// pop <ev> from the events FIFO
				bridger->eventsToForward.dequeue
				(
					[&] (EvItem & e) {
						ev = e.event;  // 'operator=' seems to work fine
						delete &e;
					}
				);
			}
			
			/*******/
			
#	ifdef HAS_MICRO_WINDOW_MANAGER
			if( bridger->tearDownThread )
				//
				return 0;//break;
				//
			// Nitpicker : geometry (move/pull-to-front/push-back)
			//Genode::log( "          ..winman.handleevent.. <", bridger->ourWin.Title(), ">" );
			bridger->winManager.HandleEvent( ev, bridger->curScreenWhere );
			//Genode::log( "          ..winman.handleevent Done   <", bridger->ourWin.Title(), ">" );
#	endif
			
///ToDo: *very clunky code all around!* <curButtons> and <curScreenWhere> belong to InputBridger/thread but get modified in the pthread/thread, without locking! This only works sans corruption because the pthread happens to be the *only* one to read & write those variables! Should get rid of the "cur.." vars anyway, since be_app->MouseKbd() handles that state, and has proper MT safety
			// BWindow/BView : generate B_MOUSE_DOWN, B_MOUSE_MOVED, etc
if( bridger->tearDownThread ) return 0;//break;
			bridger->handleOneEvent( ev );
#endif
		}
		
		if( debug >= 6 )
			Genode::warning( "  Bridger: event processing done......" );
	}
	
	if( debug >= 1 )
		Genode::log( "  Bridger: Tearing Down Thread !" );
	
	return 0;
}



hog::InputBridger::InputBridger( BWindow & win )
:	ourWin( win )
	,latestClick( 0 )
	,curScreenWhere()
	,curButtons( 0 )
#if 0
	,threadId( -1 )
#else
	,threadId( -1 )
		// this creates a new Genode::Thread which repeatedly calls handleInput():
	,localEp( be_app->Env(), sizeof(addr_t)*2048, "input_bridge_handler", Genode::Affinity::Location() )
	,inputHandler( localEp, *this, &InputBridger::handleInput )
	,dispModeHandler( localEp, *this, &InputBridger::handleDisplayMode )
#endif
	,greenlightForwarder()
	,mutexFwd()
	,eventsToForward()
	,newMode()
	,tearDownThread( false )
#ifdef HAS_MICRO_WINDOW_MANAGER
	,winManager( win )
#endif  // ~HAS_MICRO_WINDOW_MANAGER
{
#if 0
	BString s;
	s << "input-server for window " << win.Title();
	
	threadId = spawn_thread( input_server_, s, 10, this );
	
	if( threadId >= 0 )
		resume_thread( threadId );
#endif
	// Collect nitpicker events
	//
	win.nitPrivate->Input().sigh( inputHandler );
	win.nitPrivate->ConnectionAndGeom().info_sigh( dispModeHandler );///.. putting this here since we have the infrastructure here, although it does not really belong here
	
	// Bridge "Genode" world to "libc/pthreads/Haiku" world:
	//
	status_t res = spawn_thread( forwarding_to_pthread, "forward-genode_events-to-haiku",10, this );
	threadId = res;
	if( res < B_OK )
		Genode::error("error: cannot Spawn 'forwarder' thread\n");
	else
		res = resume_thread( threadId );
	
	if( res < B_OK )
		Genode::error("error: cannot Spawn/Resume 'forwarder' thread\n");
}

hog::InputBridger::~InputBridger()
{
	tearDownThread = true;
	greenlightForwarder.wakeup();
	
	if( debug )
		Genode::log( "  bridger: join..." );
	
	if( threadId >= 0 )
		wait_for_thread( threadId, NULL );
	
	if( debug )
		Genode::log( "  bridger: join() successful without deadlocking" );
}


BMessage hog::InputBridger::mouseDown( int32 button )
{
	// Craft a BMessage like Haiku's input_server would, in the
	// form expected by BWindow & friends.
	
	BMessage m;
	
//	if( ourWin.Lock() )
	{
		// state T0 -- update state of our host BWindow
		curButtons |= button;
		//
		int32 clickcount = 1;
		{
			bigtime_t threshold = 499000;
			get_click_speed( &threshold );
			
			// T-
			bigtime_t delta = system_time();
			delta -= latestClick;
			if( delta < threshold )
				clickcount = 2;
			
			// T+
			latestClick = system_time();
		}
		
		m.what = B_MOUSE_DOWN;
		m.AddPoint( "screen_where", curScreenWhere );
		//m.AddPoint( "be:view_where", BPoint(z) );
		m.AddInt32( "buttons", curButtons );
		m.AddInt32( "clicks", clickcount );
		///later: any debugging to do yet ? If so, this PrintToStream() is useful
		// Helps with debug/coverage: in BFilePanel, may be useful to find out when the event loop is unlocked again ?
		//m.PrintToStream();
		
	//	Genode::log( "  ...lmb @ x=", curScreenWhere.x, " y=", curScreenWhere.y);
		
//		ourWin.Unlock();
	}
	
	// T+
	// update state of BApplication
	be_app->MouseKbd().Set_MouseButtons( curButtons );
	
	return m;
}

BMessage hog::InputBridger::mouseUp( int32 button )
{
	BMessage m;
	
//	if( ourWin.Lock() )
	{
		curButtons &= ~button;
		
		m.what = B_MOUSE_UP;
		m.AddPoint( "screen_where", curScreenWhere );
		//m.AddPoint( "be:view_where", BPoint(z) );
		m.AddInt32( "buttons", curButtons );
		
//		ourWin.Unlock();
	}
	
	// T+
	be_app->MouseKbd().Set_MouseButtons( curButtons );
	
	return m;
}


BMessage hog::InputBridger::keyDownOrUp(
		const Input::Keycode genode_key,
		const bool latch_down )
{
	using namespace Input;
	
	BMessage m;
	
	m.what = latch_down
		? B_KEY_DOWN
		: B_KEY_UP
		;
	
	// -- <raw_be_key>, <modifiers> --
	
	// T-  combined virtual-plus-physical modifiers:
	int32 virtualized_modifiers = be_app->MouseKbd().KbdModifiers();
	
	// T0 modifiers
	int32 changed_disambiguated_modifier = 0;
	//const int raw_be_key =
	//	hog::conv_input::HaikuKey_for_GeKey( genode_key );
	const int changedascii_or_changedextra =
		hog::conv_input::HaikuKey_for_GeKey( genode_key, changed_disambiguated_modifier );
	
	if( debug >= 2 )
		Genode::log( "  t- modifiers: ", virtualized_modifiers, " change: ", changed_disambiguated_modifier );
	
	// Processing -- there are two cases:
	// 1) a modifier (B_SHIFT_KEY etc) went down (or up) -- with the exception of B_FUNCTION_KEY which is not really a "modifier" and is processed in 2).
	// 2) a non-modifier key went down (or up)
	
	if( changed_disambiguated_modifier && (0 == changedascii_or_changedextra) )
	{
		// => call Set_KbdModifiers( B_LEFT_SHIFT_KEY ) ..etc:
		
		// -- <disambiguated_modifiers> --
		
		// T- modifiers
		//		(clear B_SHIFT_KEY|B_COMMAND_KEY|B_CONTROL_KEY|B_OPTION_KEY bits)
		
	//	int32 modifiers = be_app->MouseKbd().KbdModifiers();
		int32 disambiguated_modifiers = 0; //discriminated/differentiated/individualized_modifiers =
		{
			// T-
			disambiguated_modifiers =
				virtualized_modifiers
				&
				~( B_SHIFT_KEY|B_COMMAND_KEY|B_CONTROL_KEY|B_OPTION_KEY )  // those names are misleading as those are hemispheric (left/right), those constants should be named "B_COMBINED_SHIFT_STATE" or something ; anyway, we just preserve the physical locations (B_LEFT_SHIFT etc) here
				;
			
			// T0
			if( latch_down )
				disambiguated_modifiers |= changed_disambiguated_modifier;
			else
				disambiguated_modifiers &= ~changed_disambiguated_modifier;
		}
		
		// T+
		//		(set B_SHIFT_KEY|B_COMMAND_KEY|B_CONTROL_KEY|B_OPTION_KEY bits)
		
		virtualized_modifiers = disambiguated_modifiers;
		
		if( (disambiguated_modifiers & B_LEFT_SHIFT_KEY) || (disambiguated_modifiers & B_RIGHT_SHIFT_KEY) )
			virtualized_modifiers |= B_SHIFT_KEY;
		if( (disambiguated_modifiers & B_LEFT_COMMAND_KEY) || (disambiguated_modifiers & B_RIGHT_COMMAND_KEY) )
			virtualized_modifiers |= B_COMMAND_KEY;
		if( (disambiguated_modifiers & B_LEFT_CONTROL_KEY) || (disambiguated_modifiers & B_RIGHT_CONTROL_KEY) )
			virtualized_modifiers |= B_CONTROL_KEY;
		if( (disambiguated_modifiers & B_LEFT_OPTION_KEY) || (disambiguated_modifiers & B_RIGHT_OPTION_KEY) )
			virtualized_modifiers |= B_OPTION_KEY;
		
		// Apply
		
		be_app->MouseKbd().Set_KbdModifiers( virtualized_modifiers );
		
		if( debug >= 2 )
			Genode::log( "  t+ modifiers: ", virtualized_modifiers, " (from ", disambiguated_modifiers, ")" );
		
		// Preserve the bits instead of clearing/setting them ?
		//no! what if the left shift is being held down, and right shift goes up, we clear the SHIFT flag..
		// should test for presence of remaining (one of two) shift key, and only clear the SHIFT bit if *both* LEFT_SHIFT and RIGHT_SHIFT are up..
		// EITHER THAT, or ADD *SHIFT* on the fly before sending message, at the last minute !
	}
	else if( changedascii_or_changedextra )
	{
		// => call m.AddString( "bytes", "a" ) ..etc :
		
		// -- <haiku_key> --
		
		const int haiku_key =
			hog::conv_input::ModifiedKey_for_RawBeKey( changedascii_or_changedextra, virtualized_modifiers );
		
		if( debug >= 2 )
			Genode::log( "  key-down.. code: ", changedascii_or_changedextra, " becomes code: ", haiku_key );
		
		// 'mapped' key, e.g. a/b/c/..., B_TAB, B_ESCAPE, B_LEFT_ARROW...
		PDBG( "sending key: ", char(haiku_key) );
		char bytes[2] = { char(haiku_key), 0 };
		
		// Special case: shortcuts:
		// If the COMMAND key is depressed, this is a *shortcut* call, and BWindow will only understand if
		// in uppercase (e.g. Command-Q and not Command-q) so call toupper()
		// NOTE: seems BeOS/Hai call toupper() here, instead of at the last moment in BWindow::_HandleKeyDown() unfortunately, so we have to follow suit.
		if( virtualized_modifiers & B_COMMAND_KEY )
			bytes[0] = toupper( bytes[0] );
		
		// Special case(s): F1....F12:
		if( changed_disambiguated_modifier == B_FUNCTION_KEY )
		{
			// shuffle stuff around: B_F1_KEY will not be in <bytes> but instead in Int32("key"), and <bytes> must contain B_FUNCTION_KEY instead:
			bytes[0] = B_FUNCTION_KEY;
			m.AddInt32( "key", changedascii_or_changedextra );  // B_F1_KEY, B_PRINT_KEY, etc
		}
		
		// T+
		m.AddString( "bytes", bytes );
		
		///ToDo: AddInt32( "raw_char", B_ENTER )
		// when receiving ENTER or RETURN -- seems BWindow::_DetermineTarget() needs that, to route <Enter> to DefaultButton()
	}
	else    //if( raw_be_key <= 0 )
		// unmapped/unimplemented key
		return BMessage();//
		//
	
	// modifier(s) (B_SHIFT_KEY, B_COMMAND_KEY, B_CONTROL_KEY...)
	// => AddInt32() is done at T+ of handleOneEvent()
	
	return m;
}

void hog::InputBridger::handleInput()
{
	// USED BY
	// Genode::Signal_handler<InputBridger>
	/*
	I.e. this is executed in GENODE context, so no haiku/libc stuff,
	or I can get in trouble (printf() will hang up on "libc suspend",
	find_thread/pthread_self() won't work, and so on). This must be
	pure Genode code only.
	*/
	
	if( debug >= 5 )
		Genode::log( "..Inputbridger.handleInput()" );
	
	ourWin.nitPrivate->Input().for_each_event(
		[&] ( Input::Event const & ev )
		{
#if 0
			must have only pure Genode code here, no haiku code !
#	ifdef HAS_MICRO_WINDOW_MANAGER
			// Nitpicker : geometry (move/pull-to-front/push-back)
			winManager.HandleEvent( ev, curScreenWhere, curButtons );
#	endif
			
			// BWindow/BView : generate B_MOUSE_DOWN, B_MOUSE_MOVED, etc
			handleOneEvent( ev );
#else
			// delegate all the heavy lifting to the "pthread" thread:
			{
				Genode::Mutex::Guard _( mutexFwd );
				
				eventsToForward.enqueue( *new EvItem(ev) );  // copy ctor seems to work fine
			}
			greenlightForwarder.wakeup();
#endif
		}
	);
	
	if( debug >= 5 )
		Genode::log( "..~Inputbridger.handleInput() *Done*" );
}

void hog::InputBridger::handleDisplayMode()
{
	{
		Genode::Mutex::Guard _( mutexFwd );
		
		// use new 'panorama()' API as per ticket #5353
		auto const panorama = ourWin.nitPrivate->ConnectionAndGeom().panorama();
		auto const new_area = panorama.convert<Gui::Area>(
			[&] (Gui::Rect rect) { return rect.area; },
			[&] (Gui::Undefined) { return Gui::Area { 640, 480 }; }
			);
		newMode = BSize( new_area.w, new_area.h );
	}
	greenlightForwarder.wakeup();
	
	if( debug >= 3 )
		Genode::log( "handleDisplayMode got mode: ", newMode.width, " x ", newMode.height );
}



void hog::InputBridger::handleOneEvent(
			const Input::Event & ev
			)
{
	using namespace Input;
	
	if( debug >= 4 )
		Genode::log( " .. input_server sending to window: ", ourWin.Title() );
	
	if( debug >= 5 )
		Genode::log( "    -> ", ev );  // prints e.g. " -> ABS_MOTION +267+26"
	
	BMessage tx;
	
	ev.handle_press([&] (Keycode key, Codepoint codepoint/*glyph*/) {
		Genode::String<128> info(
			key_name(key), " (", codepoint.value, ")"
			);
		if( debug >= 2 )
			Genode::log( "  pressed: ", info );
		
		// handle e.g. key_press(BTN_LEFT)
		switch( key )
		{
			// mouse:
			case BTN_LEFT: tx = mouseDown( B_PRIMARY_MOUSE_BUTTON ); break;
			case BTN_RIGHT: tx = mouseDown( B_SECONDARY_MOUSE_BUTTON ); break;
			case BTN_MIDDLE: tx = mouseDown( B_TERTIARY_MOUSE_BUTTON ); break;
			
			default:
				// keyboard:
				tx = keyDownOrUp( key, true );  // key *down* case
		}
		
		//if ((codepoint/glyph.value & 0x7f) && !(glyph.value & 0x80)) {
			if( debug >= 2 )
				Genode::log( " -- glyph: ", codepoint.value );//glyph.value );
		//}
		
		// Code-points note:
		// We only get valid codepoints/glyph numbers if there is a full-fledged 'chargen' config for event_filter, otherwise glyph comes up as zero.
		
		//if( false == codepoint.valid() )
		//	Genode::error( "codepoint is always invalid.. what gives?" );
	});
	
	ev.handle_release([&] (Keycode key) {
		// handle e.g. key_release(BTN_LEFT)
		switch( key )
		{
			// mouse:
			case BTN_LEFT: tx = mouseUp( B_PRIMARY_MOUSE_BUTTON ); break;
			case BTN_RIGHT: tx = mouseUp( B_SECONDARY_MOUSE_BUTTON ); break;
			case BTN_MIDDLE: tx = mouseUp( B_TERTIARY_MOUSE_BUTTON ); break;
			
			default:
				// keyboard:
				tx = keyDownOrUp( key, false );  // key *up* case
		}
	});
	
	ev.handle_relative_motion([&] (int x, int y) {
		//if( debug >= 2 )
			Genode::error( "received ==relative== motion event (tablet device?), n/i ! : ", x, " ", y );
		// Saw this in Genode documents:
		// << The main difference between mouse and tablet devices is that the former produces relative events whereas the latter produces absolute motion events. >>
		// -> hmmmm.. maybe it's the opposite ? Since we always end up in handle_absolute_motion() below, and never in here...
	});
	
	ev.handle_absolute_motion(
		[&] ( int x, int y )
		{
			if( debug >= 3 )
				Genode::log( " -- mouse pos to x=", x, " y=", y );
			
//			if( ourWin.Lock() )
			{
				curScreenWhere = BPoint( x, y );
				
				tx.what = B_MOUSE_MOVED;
				tx.AddPoint( "screen_where", curScreenWhere );
				//tx.AddPoint( "be:view_where", BPoint(z) );
				tx.AddInt32( "buttons", curButtons );
				
//				ourWin.Unlock();
			}
			
			// T+
			// update state of BApplication
			be_app->MouseKbd().Set_MouseScreenCoords( curScreenWhere );
			
			/*
			++	AddInt32("_view_token", &token)
			(remed out handling of B_MOUSE_MOVED in BWindow::DispatchMessage(),
			so must add support both here in input_server AND down there!)
			*/
			
			/*
			BPoint w( x, y );
			win.ConvertFromScreen( &w );
			if( debug >= 2 )
				Genode::log( "        conv *abs* in window coords: ", w.x, " ", w.y );
			*/
		}
	);
	
	ev.handle_wheel([&] (int dx, int dy) {
		if( debug >= 2 )
			Genode::log( " -- mouse wheel dy: ", dy );
		
		tx.what = B_MOUSE_WHEEL_CHANGED;
	///+Lock()..
	//addFields():  "screen_where" serves a dual purpose: its presence is also a "flag" for BWindow to route the message correctly (as a substitute for app_server)
		tx.AddPoint( "screen_where", curScreenWhere );
		tx.AddFloat( "be:wheel_delta_x", dx );
		tx.AddFloat( "be:wheel_delta_y", - dy );
			// <dy> signage/semantics seems reversed in Genode and Haiku (tested with Mandelbrot)
	});
	
	
	//
	// T+
	//
	
	if( debug >= 3 )
		Genode::log( " ..T+ for tx.what = ", tx.what );
	
	if( tx.what )
	{
		// must be common to all (B_MOUSE_DOWN/UP, B_KEY_DOWN/UP, ... and probably also B_MOUSE_MOVED)
		tx.AddInt32( "modifiers", be_app->MouseKbd().KbdModifiers() );
		
		if( debug >= 2 && tx.what != B_MOUSE_MOVED )
			Genode::log( "  input event: ", WhatAsStr(tx.what).String() );
		
//can't call libc from a Genode ep/thread !
//		if( debug >= 3 )
//			tx.PrintToStream();
		
		//Genode::log( "input-server ------------->----------------\\");
#if 0
		{
			Genode::Mutex::Guard _( mutexFwd );
			
			msgsToForward.push( new BMessage(tx) );
		}
		greenlightForwarder.wakeup();
#else
		// this will set B_PREFERRED_TOKEN, which will ensure UsePreferredTarget()
		// returns true, which will make mouse & keyboard handling work in BWindow:
		BMessenger( NULL, &ourWin ).SendMessage( &tx );
			// Though Haiku's app_server (ServerWindow.cpp) uses this instead, equivalent? :
			//BMessenger::Private(fFocusMessenger).SetTo(fClientTeam, looperPort, B_PREFERRED_TOKEN);
		
		///ToDo: sometimes B_MOUSE_UP arrives late (or not at all) to a BFilePanel, which makes LongAndDragTrackingFilter NOT reset its state, which triggers a spurious "Drag" event ; maybe linked to "busy looper slowing down everything even VFS" problem I seem to have ?
#endif
		//Genode::log( "input-server ...........done.............../");
	}
}


#if 0
int hog::InputBridger::input_server_( void * data )
{
	InputBridger * bridge = static_cast<InputBridger*>( data );
	
	if( debug )
		Genode::log( "input_server for ", bridge->ourWin.Title(), " starting +++");
	
	bridge
		->eventLoop();
	
	if( debug )
		Genode::log( "input_server QUITting (normally on BWindow quitting)");
	
	return 0;
}


void hog::InputBridger::eventLoop()
{
	BWindow & win = ourWin;
	
	Nitpicker::Connection
		* nit = &win.ServerWindow();
	
	while( false == win.fTerminating )
	{
		snooze( 20000 );//timer.msleep(20);
		
		if( false == nit->input()->pending() )
			//
			continue;  // test for termination, snooze
		
//		nit
//			->input()
//				->for_each_event(
//...
	}
}
#endif

