
// SPDX-License-Identifier: CC-BY-NC-SA-4.0
//
// Copyright (C) 2026 Bit by Bit Signal Processing LLC (https://bxbsp.com)
//
// This work is placed under the "Creative Commons Attribution
// NonCommercial ShareAlike 4.0 International" license, known
// by the shortened acronym "CC-BY-NC-SA-4.0".
//
// This work is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
//
// A CC-BY-NC-SA-4.0 license allows you to use this work for
// noncommercial purposes so long as attribution is made to the
// original author.  Modified versions of this work may be distributed,
// but only under the same license.  For further details, see the
// Creative Commons License "CC-BY-NC-SA-4.0".
//
// You should have received a copy of the CC-BY-NC-SA-4.0 license
// along with this work. If not, see
// <https://creativecommons.org/licenses/by-nc-sa/4.0/>.
//

#include "local_display.hh"
#include "touchscreen_calibration.hh"

#include <float.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#define virtual virtualm
#include <libdrm/drm.h>
#undef virtual

#include <libdrm/drm_mode.h>

#include "displays.hh"
#include "color.hh"
#include "global_beep.hh"
#include "hotspot.hh"

#ifdef ZYNQMP_DISPLAYPORT_STATUS
#include "DisplayPortStatus.hh"
#endif

volatile bool enable_display = true;

//void create_display_panels();
//#define run_at_start(function) class at_start { public: at_start(void (*func)()) { func(); }; } run(function)
//run_at_start(create_display_panels);


volatile bool local_display::lock_dri_fd          = false;
int           local_display::dri_fd               = 0;
uint32_t      local_display::crtc_id              = 0;
uint32_t      local_display::connector_id         = 0;
volatile bool local_display::is_drm_master        = false;


#define memclear(s)  memset(&s, 0, sizeof(s))
#define VOIDP2U64(x) ((uint64_t)(void*)(x))


void local_display::layout()
{
  int xmargin = 0;
  int ymargin = 0;

  clear(bgcolor);

  //printf("In local_display::layout();\n");
  
  layout_calculator->do_layout(0, 0,
			       width, height,
			       xmargin, ymargin,
			       *subwindows,
			       target_aspect_x,
			       target_aspect_y,
			       force_aspect);
  
  layout_dirty = false;
  dirty = true;
}



void local_display_panel::clear_rect(color c, point corner1, point corner2)
{
  if(corner1.x<0)             corner1.x = 0;
  if(corner1.x>=width)        corner1.x = width-1;
  if(corner1.y<0)             corner1.y = 0;
  if(corner1.y>=height)       corner1.y = height-1;
  
  if(corner2.x<0)             corner2.x = 0;
  if(corner2.x>=width)        corner2.x = width-1;
  if(corner2.y<0)             corner2.y = 0;
  if(corner2.y>=height)       corner2.y = height-1;

  if(corner1.x>corner2.x)
    {
      int t = corner1.x;
      corner1.x = corner2.x;
      corner2.x = t;
    }
  
  if(corner1.y>corner2.y)
    {
      int t = corner1.y;
      corner1.y = corner2.y;
      corner2.y = t;
    }

  for(int i=corner1.x; i<=corner2.x; i++)
    for(int j=corner1.y; j<=corner2.y; j++)
      {
	draw_point_no_boundscheck(point(i,j), c);
      }    
}


local_display_panel::local_display_panel(int w, int h, int pitch, uint16_t* buff, int buffsize, uint32_t dumb_handle, uint32_t fb_id, int dri_fd) : panel(w, h, pitch)
{
  this->buff         = buff;
  this->buffsize     = buffsize;
  this->fb_id        = fb_id;
  this->dumb_handle  = dumb_handle;
  this->dri_fd       = dri_fd;
}


void local_display_panel::draw_point_no_boundscheck(point p, color c)
{
#ifdef INVERT_X
  int x_fixed = width-1 - p.x;
#else
  int x_fixed = p.x;
#endif

#ifdef INVERT_Y
  int y_fixed = height-1 - p.y;
#else
  int y_fixed = p.y;
#endif
  
  int location = y_fixed * pitch + x_fixed;

  buff[location] = c;
}

color local_display_panel::get_point_no_boundscheck(point p)
{
#ifdef INVERT_X
  int x_fixed = width-1 - p.x;
#else
  int x_fixed = p.x;
#endif

#ifdef INVERT_Y
  int y_fixed = height-1 - p.y;
#else
  int y_fixed = p.y;
#endif
  
  int location = y_fixed * pitch + x_fixed;

  return color(buff[location]);
}


local_display_panel* local_display::create_local_display_panel(int dri_fd, int width, int height)
{
  int error;
  
  //------------------------------------------------------------------------------
  //Create a dumb buffer
  //------------------------------------------------------------------------------
  drm_mode_create_dumb create_dumb;
  memclear(create_dumb);
  create_dumb.width   = width;
  create_dumb.height  = height;
  create_dumb.bpp     = 16; //32;
  create_dumb.flags   = 0;
  create_dumb.pitch   = 0;
  create_dumb.size    = 0;
  create_dumb.handle  = 0;
  error = ioctl(dri_fd, DRM_IOCTL_MODE_CREATE_DUMB, &create_dumb);
  
  if(error) { printf("Error %d creating dumb buffer (errno=%d \"%s\").\n", error, errno, strerror(errno)); return 0; }

  if((int)create_dumb.pitch != (int)width)
    {
      printf("************  Width is %ld, Pitch is %ld\n", (long)width, (long)create_dumb.pitch);
    }

  
  drm_mode_fb_cmd      cmd_dumb;
  memclear(cmd_dumb);
  cmd_dumb.width      = create_dumb.width;
  cmd_dumb.height     = create_dumb.height;
  cmd_dumb.bpp        = create_dumb.bpp;
  cmd_dumb.pitch      = create_dumb.pitch;
  cmd_dumb.depth      = 16; //24;
  cmd_dumb.handle     = create_dumb.handle;
  error = ioctl(dri_fd,DRM_IOCTL_MODE_ADDFB,&cmd_dumb);

  
  if(error) { printf("Error adding framebuffer.\n"); exit(20); }
  
  drm_mode_map_dumb    map_dumb;
  memclear(map_dumb);
  map_dumb.handle     = create_dumb.handle;
  error = ioctl(dri_fd,DRM_IOCTL_MODE_MAP_DUMB, &map_dumb);
  
  if(error) { printf("Error mapping dumb framebuffer.\n"); exit(20); }
  
  
  uint16_t* fb_base = (uint16_t*) mmap(0, create_dumb.size, PROT_READ | PROT_WRITE, MAP_SHARED, dri_fd, map_dumb.offset);

  int pixel_pitch = create_dumb.pitch / (create_dumb.bpp/8);  // want pitch in pixels, not in bytes
  
  return new local_display_panel(create_dumb.width, create_dumb.height, pixel_pitch, fb_base, create_dumb.size, create_dumb.handle, cmd_dumb.fb_id, dri_fd);
}


local_display_panel::~local_display_panel()
{
  //printf("Destroying local display panel of size %dx%d\n", width, height);

  munmap(buff, buffsize);
  drm_mode_destroy_dumb  destroy_dumb;

  destroy_dumb.handle = dumb_handle;

  int error = ioctl(dri_fd, DRM_IOCTL_MODE_DESTROY_DUMB, &destroy_dumb);
  
  if(error) { printf("Error %d destroying dumb buffer (errno=%d \"%s\").\n", error, errno, strerror(errno)); exit(20); }

}


void local_display::copy_static_to_drawing()
{
  beginDrawToStatic();
  //printf("\n\n\n\ncopy_static_to_drawing of %p, enter layout_dirty=%d, dirty=%d\n", this, layout_dirty, dirty);

  if(touchscreen_calibration_in_progress)
    {
      if(layout_dirty)
	touchscreen_calibration_window->layout();
      layout_dirty = false;
      dirty        = false;
    }
  else
    {
      if(layout_dirty)
	{
	  bool size_changed = width!=static_panel->width || height!=static_panel->height || pitch != static_panel->pitch;
	  
	  if(size_changed)
	    {
	      delete static_panel;  
	      static_panel = new backing_panel(width, height, pitch);
	    }
	  
	  static_panel->clear(bgcolor);
	  
	  layout();
	  layout_dirty = false;
	  dirty        = true;
	}
    }
  
  //printf("copy_static_to_drawing layout done layout_dirty=%d, dirty=%d\n", layout_dirty, dirty);
  if(dirty)
    {
      draw_dirty();
      dirty = false;
    }
  
  
  //printf("copy_static_to_drawing exit layout_dirty=%d, dirty=%d\n\n\n\n", layout_dirty, dirty);
  
  memcpy(drawing_panel->buff, static_panel->buff, pitch*height*sizeof(static_panel->buff[0]));

  endDrawToStatic();
}

bool local_display::draw()
{
  if(!is_drm_master)
    return true;

  copy_static_to_drawing();

  if(touchscreen_calibration_in_progress)
    {
      touchscreen_calibration_window->draw_dynamic();
    }
  else
    {
      draw_dynamic();
      draw_cursor(drawing_panel);
    }

  
  page_flip();

  //usleep(20000);
  return true;
}


void local_display::draw_ephemeral_alert(const char* text, int text_height, color c, color bg)
{
  //printf("Calling draw_ephemeral_alert with test \"%s\" on local display.\n", text);
  
  set_text_size(text_height);
  
  int twidth  = calculate_text_width(text);
  int theight = calculate_text_height(text);

  int corner1_x = width/2  - twidth  - 60;
  int corner1_y = height/2 - theight - 60;
  int corner2_x = width/2  + twidth  + 60;
  int corner2_y = height/2 + theight + 60;

  active_panel->clear_rect( bg, point(corner1_x, corner1_y), point(corner2_x, corner2_y));


  active_panel->draw_line_horizontal(point(corner1_x, corner1_y), point(corner2_x, corner1_y), c);
  active_panel->draw_line_horizontal(point(corner1_x, corner2_y), point(corner2_x, corner2_y), c);
  active_panel->draw_line_vertical  (point(corner1_x, corner1_y), point(corner1_x, corner2_y), c);
  active_panel->draw_line_vertical  (point(corner2_x, corner1_y), point(corner2_x, corner2_y), c);

  active_panel->draw_text( text, c, width/2, height/2, DRAW_TEXT_X_CENTER|DRAW_TEXT_Y_CENTER);

  //drawing_panel->clear_rect( bg, point(corner1_x, corner1_y), point(corner2_x, corner2_y));
  //drawing_panel->draw_text( text, c, width/2, height/2, DRAW_TEXT_X_CENTER|DRAW_TEXT_Y_CENTER);

  //page_flip();
}

void local_display::page_flip()
{  
  local_display_panel* p = drawing_panel;
  int error = 0;
  int err;
  
#ifdef NOTDEF
  struct drm_mode_crtc_page_flip flip;

  memclear(flip);
  flip.fb_id     = p->fb_id;
  flip.crtc_id   = crtc_id;
  flip.user_data = 0;
  flip.flags     = 0;

  error = ioctl(dri_fd, DRM_IOCTL_MODE_PAGE_FLIP, &flip);
  err = errno;
#endif

  {
    drm_wait_vblank drmwv;
    
    memclear(drmwv);
    
    drmwv.request.type = _DRM_VBLANK_RELATIVE;
    drmwv.request.sequence = 0;
    drmwv.request.signal = 0;


    //sleep(100);
    
    error = ioctl(dri_fd, DRM_IOCTL_WAIT_VBLANK, &drmwv);
    err = errno;
  
    //printf("vblank wait error=%d, errno=%d (%s)\n", error, err, strerror(err));
  }

  {
    struct drm_mode_crtc_page_flip_target flip;

    memclear(flip);
    flip.fb_id     = p->fb_id;
    flip.crtc_id   = crtc_id;
    flip.user_data = 0;
    flip.flags     = 0;
    flip.sequence  = 0;
    
    error = ioctl(dri_fd, DRM_IOCTL_MODE_PAGE_FLIP, &flip);
    err = errno;
  
    //printf("flip error=%d, errno=%d (%s)\n", error, err, strerror(err));
  }
	  	  
  if(!error)
    {
      //
      // The soon-to-be-active panel is the drawing panel.  So call it the active one.
      // Things drawn to it will appear once it becomes active.  Don't draw at all to
      // the former active one, since anything drawn may briefly appear and then
      // disappear when the page flip happens.  So hide it away as the flip_ending_panel.
      // Since no page flip is pending (that would have given an EBUSY from the ioctl),
      // the old flip_pending_panel is completely free.  Make it the new drawing panel.
      //
      drawing_panel = flip_pending_panel;
      flip_pending_panel = active_panel;
      active_panel = p;
    }
  else if(error && err==EBUSY)
    {
      // Already processing a page flip; can't do it.  In this case, abort the page flip.
      // The active panel stays in place, as does the flip_pending panel.  These mustn't
      // be messed with, since the flip is in progress.  Thus also leave the drawing
      // panel in place.  Let it be redrawn.  
    }
  else
    {
      printf("ERROR: FLIP ioctl returns %d:  errno=%d (%s)\n", error, err, strerror(err));
    }
}



bool connector_link_status_ok(int dri_fd, int connector_id, bool verbose)
{
  bool status_ok = false;

  drm_mode_get_connector conn;

  memclear(conn);
  conn.connector_id = connector_id;
  ioctl(dri_fd, DRM_IOCTL_MODE_GETCONNECTOR, &conn);
      
  //Check if the connector is OK to use (connected to something)
  if (conn.count_encoders<1 || conn.count_modes<1 || !conn.encoder_id || !conn.connection)
    {
      if(verbose)
	printf("Connector is DISCONNECTED.\n");
      return status_ok;
    }

  if(verbose)
    printf("Connector is CONNECTED.\n");
  
  drm_mode_obj_get_properties obj_get_props;

  memclear(obj_get_props);
  obj_get_props.obj_type = DRM_MODE_OBJECT_CONNECTOR;
  obj_get_props.obj_id   = connector_id;

  ioctl(dri_fd, DRM_IOCTL_MODE_OBJ_GETPROPERTIES, &obj_get_props);
  
  if(verbose)
    printf("Connector has %d properties.\n", obj_get_props.count_props);
  
  bool got_it = false;
  
  if(verbose)
    printf("Connector property 2 (DPMS) is:  ");
  
  if(obj_get_props.count_props)
    {
      uint32_t* obj_props       = new uint32_t[obj_get_props.count_props];
      uint64_t* obj_prop_values = new uint64_t[obj_get_props.count_props];
      
      obj_get_props.props_ptr       = VOIDP2U64(obj_props);
      obj_get_props.prop_values_ptr = VOIDP2U64(obj_prop_values);
      
      int error = ioctl(dri_fd, DRM_IOCTL_MODE_OBJ_GETPROPERTIES, &obj_get_props);

      if(error)
	{
	  printf("Error getting connector properties.\n");
	  delete [] obj_props;
	  delete [] obj_prop_values;
	  return status_ok;
	}
      
      for(int p=0; p<(int)obj_get_props.count_props; p++)
	{
	  if(obj_props[p] == 2)
	    {
	      if(verbose)
		{
		  printf("%s", ( obj_prop_values[p]==0 ? "ON\n"       :
				 obj_prop_values[p]==1 ? "STANDBY\n"  :
				 obj_prop_values[p]==2 ? "SUSPEND\n"  :
				 /**/                    "OFF\n"      ));
		}
	      got_it = true;
	      status_ok = obj_prop_values[p]==0;
	    }
	}
      
      delete [] obj_props;
      delete [] obj_prop_values;
    }
  
  if(!got_it)
    {
      if(verbose)
	printf("UNKNOWN (assume OFF)\n");
    }

  return status_ok;
}


void local_display::check_for_new_displays(displays* dpys, int num_panes_per_display)
{
#ifdef ZYNQMP_DISPLAYPORT_STATUS
  class DisplayPortStatus dps(false);
#endif
  int error;
  
  if(!enable_display)
    {
      printf("Local display is disabled via program arguments.\n");
      return;
    }
  
  //printf("In check_for_new_displays.  lock_dri_fd=%d, is_drm_master=%d, dri_fd=%d\n",
  //	 lock_dri_fd, is_drm_master, dri_fd);

  if(lock_dri_fd)
    return;

  lock_dri_fd = true;
  
  // Only one local local_display is supported.  If it's already open, don't need to check for another.
  // However, do make sure its status is still OK
  if(dri_fd)
    {
#ifdef ZYNQMP_DISPLAYPORT_STATUS
      bool status_ok = dps.is_OK(false);
#else
      bool status_ok = true;
#endif
      
      bool link_status_ok = connector_link_status_ok(dri_fd, connector_id, true);
      if(link_status_ok && status_ok)
	{
	  //printf("Checked DisplayPort status, and it's still OK.\n");
	  lock_dri_fd = false;
	  return;
	}

      printf("DisplayPort status was bad, or lost focus.  Closing it out.\n");

      //bool status_ok = dps.is_OK(true);
      //bool link_status_ok = connector_link_status_ok(dri_fd, connector_id, true);

      local_display* ld = dpys->local_displays[0];

      dpys->num_local_displays = 0;
      dpys->parent = 0;
      ld->remove_no_delete(dpys);
      dpys->check_for_display_changes();
      
      // Wait to make sure that other thread isn't doing anything with the display,
      // before removing it.
      usleep(50000);

      if(ld->active_panel)       delete ld->active_panel;
      if(ld->drawing_panel)      delete ld->drawing_panel;
      if(ld->flip_pending_panel) delete ld->flip_pending_panel;

      delete ld;
      
      close(dri_fd);
      dri_fd = 0;
      is_drm_master = false;
    }

  printf("checking for new local displays.\n");
  
  //------------------------------------------------------------------------------
  //Open the DRI device
  //------------------------------------------------------------------------------
  
  dri_fd  = open("/dev/dri/card0", O_RDWR | O_CLOEXEC);

  // If we can't open the card, we can't add any local_displays on it.
  if(dri_fd<=0)
    {
      printf("Error:  Can't open video card0. (possibly temporary)\n");
      printf("dir_fd=%d, errno=%d (\"%s\")\n", dri_fd, errno, strerror(errno));
      dri_fd = 0;
      //lock_dri_fd = false;
      //return;
    }

  if(dri_fd)
    printf("opened device card0.\n");

  if(!dri_fd)
    {
      dri_fd  = open("/dev/dri/card1", O_RDWR | O_CLOEXEC);
	
      // If we can't open the card, we can't add any local_displays on it.
      if(dri_fd<=0)
	{
	  printf("Error:  Can't open video card1. (possibly temporary)\n");
	  printf("dir_fd=%d, errno=%d (\"%s\")\n", dri_fd, errno, strerror(errno));
	  dri_fd = 0;
	  lock_dri_fd = false;
	  return;
	}
      
      printf("opened device card1.\n");
    }
  
  //------------------------------------------------------------------------------
  //Kernel Mode Setting (KMS)
  //------------------------------------------------------------------------------

  // Become the "master" of the DRI device
  error = ioctl(dri_fd, DRM_IOCTL_SET_MASTER, 0);

  if(error)
    {
      printf("Error becoming DRM master; probably need to run with sudo to become root.\n");
      printf("errno=%d (\"%s\")\n", errno, strerror(errno));
      close(dri_fd);
      dri_fd = 0;
      lock_dri_fd = false;
      return;
    }

  printf("became master.\n");

  is_drm_master = true;
  
  // Get resource counts
  drm_mode_card_res res;
  memclear(res);
  error = ioctl(dri_fd, DRM_IOCTL_MODE_GETRESOURCES, &res);

  if(error)
    {
      printf("Error getting DRM resource counts 1.\n");
      close(dri_fd);
      dri_fd = 0;
      is_drm_master = false;
      lock_dri_fd = false;
      return;
    }

  printf("got resource counts.\n");
  
  //printf("fb: %d, crtc: %d, conn: %d, enc: %d\n",res.count_fbs,res.count_crtcs,res.count_connectors,res.count_encoders);

  // Get resource IDs
  uint32_t* res_fb_ids         =  new uint32_t[ res.count_fbs        ];
  uint32_t* res_crtc_ids       =  new uint32_t[ res.count_crtcs      ];
  uint32_t* res_connector_ids  =  new uint32_t[ res.count_connectors ];
  uint32_t* res_encoder_ids    =  new uint32_t[ res.count_encoders   ];

  res.fb_id_ptr         = VOIDP2U64( res_fb_ids        );
  res.crtc_id_ptr       = VOIDP2U64( res_crtc_ids      );
  res.connector_id_ptr  = VOIDP2U64( res_connector_ids );
  res.encoder_id_ptr    = VOIDP2U64( res_encoder_ids   );

  error = ioctl(dri_fd, DRM_IOCTL_MODE_GETRESOURCES, &res);

  if(error)
    {
      printf("Error getting DRM resource counts 2.\n");
      delete [] res_fb_ids;
      delete [] res_crtc_ids;
      delete [] res_connector_ids;
      delete [] res_encoder_ids;
      close(dri_fd);
      dri_fd = 0;
      is_drm_master = false;
      lock_dri_fd = false;
      return;
    }

  printf("got detailed resource info.  Have %d connectors.\n", res.count_connectors);
  

    //Loop though all available connectors
  for(unsigned int i=0; i<res.count_connectors; i++)
    {
      //get connector resource counts

      drm_mode_get_connector conn;

      printf("getting connector info. %d\n", i);
      
      memclear(conn);
      conn.connector_id = res_connector_ids[i];
      error = ioctl(dri_fd, DRM_IOCTL_MODE_GETCONNECTOR, &conn);

      if(error)
	{
	  printf("Error getting connector info 1.\n");
	  delete [] res_fb_ids;
	  delete [] res_crtc_ids;
	  delete [] res_connector_ids;
	  delete [] res_encoder_ids;
	  close(dri_fd);
	  dri_fd = 0;
	  is_drm_master = false;
	  lock_dri_fd = false;
	  return;
	}

      printf("checking connectors.\n");
      
      //Check if the connector is OK to use (connected to something)
      if (conn.count_encoders<1 || conn.count_modes<1 || !conn.encoder_id || !conn.connection)
        {
          printf("Connector with ID %d not connected.\n", conn.connector_id);

	  if(i==res.count_connectors-1)
	    {
	      printf("Found no valid connectors.\n");
	      delete [] res_fb_ids;
	      delete [] res_crtc_ids;
	      delete [] res_connector_ids;
	      delete [] res_encoder_ids;
	      close(dri_fd);
	      dri_fd = 0;
	      is_drm_master = false;
	      lock_dri_fd = false;
	      return;
	    }
	  
	  continue;
        }
      
      //printf("Got connector with ID %d.\n", conn.connector_id);

      //get connector resources
      drm_mode_modeinfo*  conn_modes           = new drm_mode_modeinfo [conn.count_modes];
      uint32_t*	          conn_props           = new uint32_t          [conn.count_props];
      uint64_t*	          conn_prop_values     = new uint64_t          [conn.count_props];
      uint32_t*	          conn_encoders        = new uint32_t          [conn.count_encoders];
      
      conn.modes_ptr       = VOIDP2U64(conn_modes);
      conn.props_ptr       = VOIDP2U64(conn_props);
      conn.prop_values_ptr = VOIDP2U64(conn_prop_values);
      conn.encoders_ptr    = VOIDP2U64(conn_encoders);
      error = ioctl(dri_fd, DRM_IOCTL_MODE_GETCONNECTOR, &conn);
      
      if(error)
	{
	  printf("Error getting connector info 2.\n");
	  delete [] conn_modes;
	  delete [] conn_props;
	  delete [] conn_prop_values;
	  delete [] conn_encoders;
	  delete [] res_fb_ids;
	  delete [] res_crtc_ids;
	  delete [] res_connector_ids;
	  delete [] res_encoder_ids;
	  close(dri_fd);
	  dri_fd = 0;
	  is_drm_master = false;
	  lock_dri_fd = false;
	  return;
	}

      connector_id = conn.connector_id;
      
      connector_link_status_ok(dri_fd, connector_id, true);

      
      for(int i=0; i<(int)conn.count_modes; i++)
	{
	  printf("Connector mode %d has resolution %dx%d\n", i, conn_modes[i].hdisplay, conn_modes[i].vdisplay);
	}

      int width  = 1920;
      int height = 1080;

      local_display_panel* active        = 0;
      local_display_panel* drawing       = 0;
      local_display_panel* flip_pending  = 0;

      drm_mode_crtc crtc;


      //
      // Check the current status.  If we're already connected, don't change the configuration.
      //
      {
#ifdef ZYNQMP_DISPLAYPORT_STATUS
        bool status_ok = dps.is_OK(true);
#else
        bool status_ok = true;
#endif
	
	bool link_status_ok = connector_link_status_ok(dri_fd, connector_id, true);
	
	
	// End if we have an OK status, and it's the target mode, or a better mode, or
	// we settle for any mode that is at least 1920 in width.
	if(link_status_ok && status_ok)
	  {
	    drm_mode_get_encoder enc;
	    memclear(enc);
	    enc.encoder_id=conn.encoder_id;
	    error = ioctl(dri_fd, DRM_IOCTL_MODE_GETENCODER, &enc);
	    
	    if(error)
	      {
		printf("Error getting encoder info.\n");
		delete [] conn_modes;
		delete [] conn_props;
		delete [] conn_prop_values;
		delete [] conn_encoders;
		delete [] res_fb_ids;
		delete [] res_crtc_ids;
		delete [] res_connector_ids;
		delete [] res_encoder_ids;
		close(dri_fd);
		dri_fd = 0;
		is_drm_master = false;
		lock_dri_fd = false;
		return;
	      }
	      
	    memclear(crtc);
	    crtc.crtc_id=enc.crtc_id;
	    error = ioctl(dri_fd, DRM_IOCTL_MODE_GETCRTC, &crtc);

	    if(error)
	      {
		printf("Error getting crtc\n");
		delete [] conn_modes;
		delete [] conn_props;
		delete [] conn_prop_values;
		delete [] conn_encoders;
		delete [] res_fb_ids;
		delete [] res_crtc_ids;
		delete [] res_connector_ids;
		delete [] res_encoder_ids;
		close(dri_fd);
		dri_fd = 0;
		is_drm_master = false;
		lock_dri_fd = false;
		return;
	      }
	    
	    width  = crtc.mode.hdisplay;
	    height = crtc.mode.vdisplay;

	    //
	    // In newer kernels, can't use the existing configuration.  Need to define my own,
	    // or ioctls like flip fail.  Probably a permission issue with using an existing
	    // configuration.
	    //
#ifdef NOTDEF
	    if(width>1200)
	      {
		printf("Local DisplayPort is already configured at resolution %dx%d.  Don't change it.\n", width, height);

		if(active)       delete active;
		if(drawing)      delete drawing;
		if(flip_pending) delete flip_pending;
		active        = local_display::create_local_display_panel(dri_fd, width, height);
		drawing       = local_display::create_local_display_panel(dri_fd, width, height);
		flip_pending  = local_display::create_local_display_panel(dri_fd, width, height);
		
		goto dp_config_complete;
	      }
	    else
	      {
		printf("Local DisplayPort is already configured at resolution %dx%d.  This is too low.  Attempting to reconfigure it.\n", width, height);
	      }
#endif
	    
	  }
	else
	  {
	    printf("Local DisplayPort isn't configured.  Configuring it.\n");
	  }
      }

      
      //
      // Code to search through modes.
      // 
      // This code has a target mode and a dummy mode.  It tries to switch to the target mode, and
      // detects if this was successful.  If so, it exits.  If not, it switches to the dummy mode
      // and then back to the target mode.  This causes a link retrain on the target mode.  If
      // successful, it exits.  If not successful, it tries again, running through all dummy modes
      // twice.  It uses all modes not the target mode as dummy modes.  (Although preferred modes
      // are of lower number, so if the dummy mode is less than the target mode it will accept a
      // lock to it.)  If this doesn't work, it increments the target mode.  It continues until
      // all target modes are exhausted.
      //

      for(int target_mode=0; target_mode<(int)conn.count_modes; target_mode++)
	for(int dummy_mode=0; dummy_mode<(int)conn.count_modes; dummy_mode++)
	  for(int mode_sel=0; mode_sel<=1; mode_sel++)
	    {
	      //if(dummy_mode==target_mode)
	      //continue;

	      //
	      // Noticed one time when it claimed to lock on a hi-res display but the
	      // lock was fake.  For now, assume it was the resolution and disallow
	      // anything above 1920
	      //
	      //if(conn_modes[target_mode].hdisplay>1920 || conn_modes[dummy_mode].hdisplay>1920)
	      //continue;
	      
	      int mode = (mode_sel&1) ? target_mode : dummy_mode ;
	      
	      width  = conn_modes[mode].hdisplay;
	      height = conn_modes[mode].vdisplay;
	      
	      printf("Trying mode %d with resolution %dx%d as %s\n", mode, width, height, (mode_sel&1) ? "dummy" : "target");
	      
	      if(active)       delete active;
	      if(drawing)      delete drawing;
	      if(flip_pending) delete flip_pending;
	      active = drawing = flip_pending = 0;
	      
	      active        = local_display::create_local_display_panel(dri_fd, width, height);
	      if(!active)
		continue;
	      
	      drawing       = local_display::create_local_display_panel(dri_fd, width, height);
	      if(!drawing)
		continue;
	      
	      flip_pending  = local_display::create_local_display_panel(dri_fd, width, height);
	      if(!flip_pending)
		continue;

	      
	      //------------------------------------------------------------------------------
	      // Kernel Mode Setting (KMS)
	      //------------------------------------------------------------------------------
	      
	      drm_mode_get_encoder enc;
	      memclear(enc);
	      enc.encoder_id=conn.encoder_id;
	      error = ioctl(dri_fd, DRM_IOCTL_MODE_GETENCODER, &enc);
	      
	      if(error)
		{
		  printf("Error getting encoder info.\n");
		  delete [] conn_modes;
		  delete [] conn_props;
		  delete [] conn_prop_values;
		  delete [] conn_encoders;
		  delete [] res_fb_ids;
		  delete [] res_crtc_ids;
		  delete [] res_connector_ids;
		  delete [] res_encoder_ids;
		  close(dri_fd);
		  dri_fd = 0;
		  is_drm_master = false;
		  lock_dri_fd = false;
		  return;
		}
	      
	      memclear(crtc);
	      crtc.crtc_id=enc.crtc_id;
	      error = ioctl(dri_fd, DRM_IOCTL_MODE_GETCRTC, &crtc);
	      
	      if(error) { printf("Error getting crtc info.\n"); exit(20); }
	      
	      crtc.fb_id              = active->fb_id;
	      crtc.set_connectors_ptr = VOIDP2U64(&conn.connector_id);
	      crtc.count_connectors   = 1;
	      crtc.mode               = conn_modes[mode];
	      crtc.mode_valid         = 1;
	      error = ioctl(dri_fd, DRM_IOCTL_MODE_SETCRTC, &crtc);
	      
	      if(error)
		{
		  printf("Error setting crtc.\n");
		  close(dri_fd);
		  dri_fd = 0;
		  is_drm_master = false;
		  lock_dri_fd = false;
		  delete [] res_fb_ids;
		  delete [] res_crtc_ids;
		  delete [] res_connector_ids;
		  delete [] res_encoder_ids;
		  return;
		}


	      // Kernel mode setting is asynchronous.  Make sure it has time to finish before we check if it succeeded.
	      usleep(10000);
	      
#ifdef ZYNQMP_DISPLAYPORT_STATUS
              bool status_ok = dps.is_OK(true);
#else
              bool status_ok = true;
#endif

	      bool link_status_ok = connector_link_status_ok(dri_fd, connector_id, true);


	      printf("DPS status is %s, link status is %s.\n", status_ok ? "OK" : "BAD", link_status_ok ? "OK" : "BAD");
	      
	      // End if we have an OK status, and it's the target mode, or a better mode, or
	      // we settle for any mode that is at least 1920 in width.
	      if(link_status_ok && status_ok && mode <= target_mode )
		{
		  printf("Succeeded in configuring local DisplayPort to resolution %dx%d.\n", width, height);
		  goto dp_config_complete;
		}
	    }

      printf("Failed to configure DisplayPort for local display.\n");

      close(dri_fd);
      dri_fd = 0;
      is_drm_master = false;
      lock_dri_fd = false;
      delete [] res_fb_ids;
      delete [] res_crtc_ids;
      delete [] res_connector_ids;
      delete [] res_encoder_ids;
      return;

      
    dp_config_complete:
      
      //if(active)
	//{
	  //float designed_height = 1080.0;
	  //float designed_width  = 1920.0;
	  //float ratio_x = active->width / designed_width;
	  //float ratio_y = active->height / designed_height;
	  
	  //int draw_width  = 900;
	  //int draw_height = 900;
	  //int offset_x    = (designed_width-draw_width)/2;
	  //int offset_y    = (designed_height-draw_height)/2;
      
	  //printf("width is %d, height is %d\n", active_panel->width, active_panel->height);
      
	  //global_draw_svg_from_data(active, BxB_svg, BxB_svg_length, offset_x * ratio_x, offset_y * ratio_y, draw_width * ratio_x, draw_height * ratio_y);
	  
	  
	  //sleep(3);
	  //}

      
      	  
      delete [] conn_modes;
      delete [] conn_props;
      delete [] conn_prop_values;
      delete [] conn_encoders;

      printf("Settled on local display size %dx%d\n", width, height);
      

      dpys->local_displays[dpys->num_local_displays] = new local_display(width, height, active, drawing, flip_pending, crtc.crtc_id, dri_fd, num_panes_per_display);
      
      //printf("Added active/inactive framebuffer panel %d for size %dx%d at base pointers %p %p %p\n", num_displays, width, height, active->buff, drawing->buff, flip_pending->buff);

      dpys->num_local_displays++;
      break;
    }
  
  
  delete [] res_fb_ids;
  delete [] res_crtc_ids;
  delete [] res_connector_ids;
  delete [] res_encoder_ids;

  lock_dri_fd = false;
}



//
// Setting the parent to null is OK, because no reference to the parent should every come out
// of a local_display. All the functions that reference a parent are overridden.
//
local_display::local_display(int w, int h, local_display_panel* active, local_display_panel* drawing, local_display_panel* flip_pending, uint32_t crtc_id, int dri_fd, int num_panes)
  : display(w, h, num_panes)
{
  touchscreen_calibrated = true;
  touchscreen_calibration_in_progress = false;
  touchscreen_x_offset = 0.0;
  touchscreen_y_offset = 0.0;
  touchscreen_x_gain   = 1.0;
  touchscreen_y_gain   = 1.0;

  active_panel       = active;
  drawing_panel      = drawing;
  flip_pending_panel = flip_pending;
  this->crtc_id      = crtc_id;
  this->dri_fd       = dri_fd;
  cursor_x           = 0;
  cursor_y           = 0;
  cursor_is_on       = false;

  pitch = active->pitch;
  
  draw_to_static     = 0;

  static_panel = new backing_panel(width, height, pitch);

  gettimeofday(&last_event_time, 0);

  // Delay allocating the input event handler, due to a practical consideration.  The local_display may have just turned on.  If so,
  // it also serves as a USB hub, so the input devices may not yet have gotten initialized.  So delay looking for input
  // devices for a bit, by putting off creation of the input event handler.
  ieh = 0;
}


local_display::~local_display()
{
  if(ieh)
    delete ieh;
  delete static_panel;
}


void local_display::draw_cursor(local_display_panel* p)
{
  if(cursor_is_on)
    {
      p->draw_line(point(cursor_x, cursor_y), point(cursor_x+11, cursor_y+11), WHITE);
      p->draw_line(point(cursor_x, cursor_y), point(cursor_x+6, cursor_y+2), WHITE);
      p->draw_line(point(cursor_x, cursor_y), point(cursor_x+2, cursor_y+6), WHITE);

      p->draw_line(point(cursor_x+1, cursor_y), point(cursor_x+12, cursor_y+11), WHITE);
      p->draw_line(point(cursor_x, cursor_y+1), point(cursor_x+11, cursor_y+12), WHITE);

      //p->draw_line(point(cursor_x, cursor_y), point(cursor_x+6, cursor_y+2), WHITE);
      //p->draw_line(point(cursor_x, cursor_y), point(cursor_x+2, cursor_y+6), WHITE);

      //drawing_panel->draw_point(point(cursor_x+1, cursor_y), GREEN);
      //drawing_panel->draw_point(point(cursor_x, cursor_y+1), GREEN);
      //drawing_panel->draw_point(point(cursor_x+2, cursor_y), GREEN);
      //drawing_panel->draw_point(point(cursor_x+3, cursor_y), GREEN);
      //drawing_panel->draw_point(point(cursor_x, cursor_y+2), GREEN);
      //drawing_panel->draw_point(point(cursor_x, cursor_y+3), GREEN);
      //drawing_panel->draw_point(point(cursor_x+1, cursor_y+2), GREEN);
      //drawing_panel->draw_point(point(cursor_x+2, cursor_y+1), GREEN);
    }
}





void local_display::get_and_process_events()
{
  if(!ieh)
    ieh = new input_event_handler(width, height, this);

  my_event me = ieh->check_for_event();

  //print_event(me);
  
  while(me.type != EVENT_NONE)
    {
      gettimeofday(&last_event_time, 0);
      //print_event(me);

      if(me.type==EVENT_TOUCH && (!me.source_mouse) && touchscreen_calibration_in_progress)
	{
	  bool handled = touchscreen_calibration_window->handle_event(me);
		  
	  if(!handled)
	    entry_rejected_beep();
	}
      else if(me.type==EVENT_TOUCH && (!me.source_mouse) && !touchscreen_calibrated && !touchscreen_calibration_in_progress)
	{
	  touchscreen_calibration_in_progress = true;
	  touchscreen_calibration_window = new touchscreen_calibration(this);
	  add(touchscreen_calibration_window);
	}
      else if(me.type==EVENT_TOUCH && (!me.source_mouse) && me.num_touches>=6 && !touchscreen_calibration_in_progress)
	{
	  touchscreen_calibration_in_progress = true;
	  touchscreen_calibration_window = new touchscreen_calibration(this);
	  add(touchscreen_calibration_window);
	}
      else if(me.type == EVENT_KEY_HOLD && me.count>10)
	{
	  if(me.key_code==KEY_ESC)
	    {
	      printf("ESC key pressed, exiting.\n");
	      if(dri_fd)
		{
		  int error = ioctl(dri_fd, DRM_IOCTL_DROP_MASTER, 0);
		  
		  if(error)
		    printf("Error dropping DRM master.  errno=%d (\"%s\")\n", errno, strerror(errno));
		  else
		    printf("Dropped DRM master.\n");

		  is_drm_master = false;
		  close(dri_fd);
		  dri_fd = 0;
		}
	      	      
	      exit(0);
	    }
	  
	  //printf("Key press:  keycode %d, ascii %d ('%c')\n", me.key_code, me.key_ascii, me.key_ascii);

	  handle_event(me);
	}
      else
	{
	  if(me.source_mouse)
	    {
	      gettimeofday(&last_mouse_event_time, 0);
	      cursor_x = me.c[0].x;
	      cursor_y = me.c[0].y;
	      cursor_is_on = true;
	      draw_cursor(active_panel);
	    }
	  else
	    {
	      cursor_is_on = false;
	      if(touchscreen_calibrated &&
		 (me.type==EVENT_TOUCH || me.type==EVENT_HOLD || me.type==EVENT_MOVE || me.type==EVENT_RELEASE) )
		{
		  // Apply the touchscreen calibration
		  for(int i=0; i<me.num_touches; i++)
		    {
		      me.c[i].x = me.c[i].x * touchscreen_x_gain + touchscreen_x_offset;
		      me.c[i].y = me.c[i].y * touchscreen_y_gain + touchscreen_y_offset;
		    }
		}
	    }
	
	  if(me.type!=EVENT_POINTER_MOVE)
	    {
	      if(trash_events_flag)
		{
		  //printf("Event trashed.\n");
		  if(me.type==EVENT_RELEASE)
		    {
		      //printf("trash_events released.\n");
		      trash_events_flag = false;
		    }
		}
	      else
		{
		  bool handled = handle_event(me);
		  
		  if(!handled && me.type==EVENT_TOUCH)
		    {
		      draw_hotspots = true;
		      gettimeofday(&unhandled_button_press_event_time, 0);
		      entry_rejected_beep();
		    }
		}
	    }
	}
      
      me = ieh->check_for_event();
    }


  //
  // Blank cursor if no recent movement
  //
  if(cursor_is_on)
    {
      struct timeval t;
      gettimeofday(&t, 0);

      long long t0_us = last_mouse_event_time.tv_sec*1000000ll + last_mouse_event_time.tv_usec;
      long long t1_us = t.tv_sec*1000000ll + t.tv_usec;

      long long elapsed_us = t1_us - t0_us;

      //printf("Blank cursor elapsed time is %lld\n", elapsed_us);
      
      if(elapsed_us > 3000000ll)
	cursor_is_on = false;
    }


  if(draw_hotspots)
    {
      struct timeval t;
      gettimeofday(&t, 0);

      long long t0_us = unhandled_button_press_event_time.tv_sec*1000000ll + unhandled_button_press_event_time.tv_usec;
      long long t1_us = t.tv_sec*1000000ll + t.tv_usec;
      
      long long elapsed_us = t1_us - t0_us;

      //printf("t0_us=%lld, t0_us=%lld, elapsed_us=%lld\n", t0_us, t1_us, elapsed_us);

      //printf("Draw hotspots elapsed time is %lld\n", elapsed_us);
      
      if(elapsed_us > 1000000ll)
	draw_hotspots = false;
    }

}



void local_display_panel::draw_text(const char* text, color c, int x, int y, int flags)
{
  global_draw_text(text, c, this, x, y, flags);
}

void local_display_panel::draw_text(const wchar_t* text, color c, int x, int y, int flags)
{
  global_draw_text(text, c, this, x, y, flags);
}

void local_display_panel::draw_text(const char16_t* text, color c, int x, int y, int flags)
{
  global_draw_text(text, c, this, x, y, flags);
}

void local_display_panel::draw_text(const char32_t* text, color c, int x, int y, int flags)
{
  global_draw_text(text, c, this, x, y, flags);
}


void local_display_panel::draw_multicolored_text(const char* text, int start_color, dt_colors& c, int x, int y, int flags)
{
  global_draw_multicolored_text(text, c, start_color, this, x, y, flags);
}

void local_display_panel::draw_multicolored_text(const wchar_t* text, int start_color, dt_colors& c, int x, int y, int flags)
{
  global_draw_multicolored_text(text, c, start_color, this, x, y, flags);
}

void local_display_panel::draw_multicolored_text(const char16_t* text, int start_color, dt_colors& c, int x, int y, int flags)
{
  global_draw_multicolored_text(text, c, start_color, this, x, y, flags);
}

void local_display_panel::draw_multicolored_text(const char32_t* text, int start_color, dt_colors& c, int x, int y, int flags)
{
  global_draw_multicolored_text(text, c, start_color, this, x, y, flags);
}


int  local_display_panel::calculate_text_width(const char* text)
{
  return global_calculate_text_width(text);
}

int  local_display_panel::calculate_text_width(const wchar_t* text)
{
  return global_calculate_text_width(text);
}

int  local_display_panel::calculate_text_width(const char16_t* text)
{
  return global_calculate_text_width(text);
}

int  local_display_panel::calculate_text_width(const char32_t* text)
{
  return global_calculate_text_width(text);
}




int  local_display_panel::calculate_text_height(const char* text)
{
  return global_calculate_text_height(text);
}

int  local_display_panel::calculate_text_height(const wchar_t* text)
{
  return global_calculate_text_height(text);
}

int  local_display_panel::calculate_text_height(const char16_t* text)
{
  return global_calculate_text_height(text);
}

int  local_display_panel::calculate_text_height(const char32_t* text)
{
  return global_calculate_text_height(text);
}


void local_display_panel::set_text_size(int height_pixels)
{
  global_set_text_size(height_pixels);
}


void local_display_panel::draw_svg_from_data(uint8_t* svg_data, int svg_data_length, int offset_x, int offset_y, int draw_width, int draw_height)
{
  return global_draw_svg_from_data(this, svg_data, svg_data_length, offset_x, offset_y, draw_width, draw_height);

}


void local_display_panel::draw_line_known_background(pointf p0, pointf p1, color c, color bg)
{
  global_draw_line_known_background(this, p0, p1, c, bg);
}


void local_display_panel::draw_line(pointf p0, pointf p1, color c)
{
  global_draw_line(this, p0, p1, c);
}


void local_display_panel::draw_line_vertical(point p0, point p1, color c)
{
  global_draw_line_vertical(this, p0, p1, c);
}


void local_display_panel::draw_line_horizontal(point p0, point p1, color c)
{
  global_draw_line_horizontal(this, p0, p1, c);
}


void eval_min_max(float x, float x_start, float x_step, int num_points, data_point* y_data, float& ymin, float& ymax)
{
  float findex = (x-x_start)/x_step;
  int index_minus = (int)floor( findex );
  int index_plus  = (int)ceil ( findex );

  if(index_minus>=num_points || index_plus>=num_points || index_minus<0 || index_plus<0)
    return;
  
  float frac = findex - index_minus;
  
  float y_min_minus = y_data[index_minus].min_y;
  float y_min_plus  = y_data[index_plus].min_y;

  float y_min = frac * (y_min_plus - y_min_minus) + y_min_minus;

  float y_max_minus = y_data[index_minus].max_y;
  float y_max_plus  = y_data[index_plus].max_y;

  float y_max = frac * (y_max_plus - y_max_minus) + y_max_minus;

  if(y_min<ymin)
    ymin = y_min;

  if(y_max>ymax)
    ymax = y_max;
}

void local_display::draw_graph_data(uint32_t     color,
				    int          num_points,
				    float        x_start,
				    float        x_step,
				    float        x_at_left,
				    float        x_at_right,
				    float        y_at_bottom,
				    float        y_at_top,
				    float        x_offset,
				    float        y_offset,
				    float        width,
				    float        height,
				    data_point*  y_data)
{
  //printf("\nin local_display::draw_graph_data()\n");
  
  int screen_x_start = (int)floor(x_offset);
  int screen_x_end   = (int)ceil(x_offset + width);

  int screen_y_top      = (int)floor(y_offset);
  int screen_y_bottom   = (int)ceil(y_offset + height);

  float x_end = x_start + x_step * (num_points-1);

  //printf("x_offset=%f, width=%f, screen_x_start=%d, screen_x_end=%d, x_end=%f\n", x_offset, width, screen_x_start, screen_x_end, x_end);
  
  for(int sx=screen_x_start; sx<screen_x_end; sx++)
    {
      float ymin = FLT_MAX;
      float ymax = -FLT_MAX;

      //printf("sx=%d\n", sx);
      
      float x      = (sx - x_offset) * (x_at_right - x_at_left) / width + x_at_left;
      float x_next = x + (x_at_right - x_at_left) / width;

      //printf("x=%f, x_next=%f\n", x, x_next);
      
      if(x_next<x_start)
	continue;

      if(x>x_end)
	break;

      // Now need to find the maximum and minimum y for x in the range of x to x_next.
      // Do this by finding max and min y at the start, and at the end, and at each
      // integral data point between.

      eval_min_max(     x, x_start, x_step, num_points, y_data, ymin, ymax);
      eval_min_max(x_next, x_start, x_step, num_points, y_data, ymin, ymax);

      // Find integral data points between.
      int index_min = (int)ceil((      x-x_start) / x_step);
      int index_max = (int)floor((x_next-x_start) / x_step);

      for(int i=index_min; i<=index_max; i++)
	{
	  float x_between = x_start + x_step * i;
	  eval_min_max(x_between, x_start, x_step, num_points, y_data, ymin, ymax);
	}	  

      if(ymin==FLT_MAX || ymax==-FLT_MIN)
	continue;

      int screen_y_min = (ymin - y_at_top) * height / (y_at_bottom - y_at_top) + screen_y_top;
      int screen_y_max = (ymax - y_at_top) * height / (y_at_bottom - y_at_top) + screen_y_top;

      //printf("ymin=%f, ymax=%f, y_at_top=%f, y_at_bottom=%f, screen_y_min=%d, screen_y_max=%d, screen_y_top=%d, screen_y_bottom=%d\n",
      //     ymin, ymax, y_at_top, y_at_bottom, screen_y_min, screen_y_max, screen_y_top, screen_y_bottom);
      
      if(screen_y_min<screen_y_top || screen_y_max>screen_y_bottom)
	continue;

      if(screen_y_min>screen_y_bottom)
	screen_y_min = screen_y_bottom;

      if(screen_y_max<screen_y_top)
	screen_y_max = screen_y_top;

      //printf("Drawing line at x=%d from y=%d to y=%d\n", sx, screen_y_min, screen_y_max);
      draw_line_vertical( point(sx, screen_y_min), point(sx, screen_y_max), color);      
    }
  
}
