<commit_before>const rfb = require('rfb2');
const cv = require('opencv');
const util = require('util');
const keysym = require('keysym');
const EventEmitter = require('events').EventEmitter;
const Template = require('./template.js')

function Screen(opts) {
  EventEmitter.call(this);
  this._rfbConn = rfb.createConnection(opts);
  this._screenBuffer = null;
  this._mouseState = {
    buttons: 0, // all buttons up
    x: 0, // TODO: pass initial position from opts? or is there a way to see it from rfb?
    y: 0,
  };
  this._mouseMoveSpeed = 1600; // pix/sec
  this._mouseClickSpeed = 300;
  this._mouseDoubleClickSpeed = 50;
  this._keyboardTypeSpeed = 20;
  this._rfbConn.on('connect', () => {
    const r = this._rfbConn;
    this._screenBuffer = new cv.Matrix(r.height, r.width, cv.Constants.CV_8UC4);
    this.emit('connect')
    r.requestUpdate(true, 0, 0, r.width, r.height);
    r.on('rect', this._handleRawUpdate.bind(this))
  })
  this._rfbConn.on('error', e => this.emit('error', e))
}
util.inherits(Screen, EventEmitter);

Screen.prototype._handleRawUpdate = function(rect) {
  const r = this._rfbConn;
  r.requestUpdate(true, 0, 0, r.width, r.height);
  const update = new cv.Matrix(rect.height, rect.width, cv.Constants.CV_8UC4);
  update.put(rect.buffer)
  update.copyTo(this._screenBuffer, rect.x, rect.y);
  this.emit('screen-update', rect);
}

Screen.prototype._connected = async function() {
  if (this._screenBuffer) {
    return Promise.resolve()
  }
  return new Promise((accept, reject) => {
    this.on('connect', accept)
  })
}

Screen.prototype.createTemplateFromFile = async function(path) {
  return new Promise((accept, reject) => {
    const template = new cv.Matrix();
    cv.readImage(path, (err, img) => {
      if (err) {
        return reject(err);
      }
      img.convertTo(template, cv.Constants.CV_8UC4);
      accept(new Template({matrix: template}))
    })
  })
}

function clamp(val, max) {
  if (val < 0) {
    return 0;
  }
  if (val > max) {
    return max;
  }
  return val
}

Screen.prototype.waitVisible = async function(template, maxWait=0) {
  await this._connected();
  let counter = 0;
  return new Promise((accept, reject) => {
    let timeout = null
    const findMatch = (rect) => {
      /*
      if (!rect) {
        return
      }

      const left  = clamp(rect.x - template.width(), this._screenBuffer.width())
      const top   = clamp(rect.y - template.height(), this._screenBuffer.height())
      console.log('roi(left, top, rect.width + 2*template.width(), rect.height + 2*template.height())', left, top, rect.width + 2*template.width(), rect.height + 2*template.height())
      const roi = rect
        ? this._screenBuffer.roi(left, top, rect.width + 2*template.width(), rect.height + 2*template.height())
        : this._screenBuffer

      */

      counter++;

      const rgb = m => {
        const r = new cv.Matrix();
        r.merge(m.split().slice(0, 3));
        return r;
      }

      rgb(this._screenBuffer).save(`screen-${counter}.png`)

      const res = rgb(this._screenBuffer).matchTemplateByMatrix(rgb(template._mat));
      const m = res.templateMatches(template.similarity(), 1, 1)
      if (m[0] && (m[0].probability >= template.similarity())) {
        this.removeListener('screen-update', findMatch)
        if (timeout) {
          clearTimeout(timeout);
        }
        const match = m[0] ? {
          x: m[0].x + template.width() / 2,  // TODO: use template offset
          y: m[0].y + template.height() / 2, // TODO: use template offset
          probability: m[0].probability
        } : m[0]
        accept(match)
      }
    }
    const cancel = () => {
      this.removeListener('screen-update', findMatch)
      reject()
    }
    if (maxWait > 0) {
      timeout = setTimeout(cancel, maxWait)
    }
    findMatch();
    this.on('screen-update', findMatch);
  });
}

Screen.prototype.waitVanish = async function(template) {
  await this._connected()
  // ...
}

Screen.prototype.exists = async function(template) {
  await this._connected()
  // ...
}

function sleep(ms) {<commit_msg>optimization: only check update+-template region in waitVisible<commit_after>const rfb = require('rfb2');
const cv = require('opencv');
const util = require('util');
const keysym = require('keysym');
const EventEmitter = require('events').EventEmitter;
const Template = require('./template.js')

const rgb = m => {
  const r = new cv.Matrix();
  r.merge(m.split().slice(0, 3));
  return r;
}

function Screen(opts) {
  EventEmitter.call(this);
  this._rfbConn = rfb.createConnection(opts);
  this._screenBuffer = null;
  this._mouseState = {
    buttons: 0, // all buttons up
    x: 0, // TODO: pass initial position from opts? or is there a way to see it from rfb?
    y: 0,
  };
  this._mouseMoveSpeed = 1000; // pix/sec
  this._mouseClickSpeed = 300;
  this._mouseDoubleClickSpeed = 50;
  this._keyboardTypeSpeed = 15;
  this._rfbConn.on('connect', () => {
    const r = this._rfbConn;
    this._screenBuffer = new cv.Matrix(r.height, r.width, cv.Constants.CV_8UC3);
    this.emit('connect')
    r.requestUpdate(true, 0, 0, r.width, r.height);
    r.on('rect', this._handleRawUpdate.bind(this))
  })
  this._rfbConn.on('error', e => this.emit('error', e))
}
util.inherits(Screen, EventEmitter);

Screen.prototype._handleRawUpdate = function(rect) {
  const r = this._rfbConn;
  r.requestUpdate(true, 0, 0, r.width, r.height);
  const update = new cv.Matrix(rect.height, rect.width, cv.Constants.CV_8UC4);
  update.put(rect.buffer)
  rgb(update).copyTo(this._screenBuffer, rect.x, rect.y);
  this.emit('screen-update', rect);
}

Screen.prototype._connected = async function() {
  if (this._screenBuffer) {
    return Promise.resolve()
  }
  return new Promise((accept, reject) => {
    this.on('connect', accept)
  })
}

Screen.prototype.sleep = async function(ms) {
  return new Promise((accept, reject) => {
    setTimeout(accept, ms);
  })
}

Screen.prototype.createTemplateFromFile = async function(path) {
  return new Promise((accept, reject) => {
    const template = new cv.Matrix();
    cv.readImage(path, (err, img) => {
      if (err) {
        return reject(err);
      }
      img.convertTo(template, cv.Constants.CV_8UC4);
      const templateNoAlpha = rgb(template)
      accept(new Template({matrix: templateNoAlpha}))
    })
  })
}

function clamp(val, max) {
  if (val < 0) {
    return 0;
  }
  if (val > max) {
    return max;
  }
  return val
}

let counter = 0;

Screen.prototype.waitVisible = async function(template, maxWait=0) {
  await this._connected();
  return new Promise((accept, reject) => {
    let timeout = null

    const findMatch = (rect) => {

      let region = this._screenBuffer
      region.save(`screen-${counter}-full.png`)

      if (rect) {
        console.log('findMatch', rect.x, rect.y, rect.width, rect.height)
        try {
          let left  = rect.x - template.width()
          if (left < 0) {
            left = 0
          }
          let right = rect.x + rect.width + template.width()
          if (right > region.width()) {
            right = region.width()
          }
          let top  = rect.y - template.height()
          if (top < 0) {
            top = 0
          }
          let bottom = rect.y + rect.height + template.height()
          if (bottom > region.height()) {
            bottom = region.height()
          }

          //console.log('Rect:', rect.x, rect.y, rect.width, rect.height, 'Roi:', left, right, top, bottom, right - left, bottom - top, 'region:', region.width(), region.height())
          console.log('roi: ', left, top, right - left, bottom - top)
          region = region.roi(left, top, right - left, bottom - top)
          console.log(region)

        } catch(e) {
          console.log(e)
        }
      }

      counter++;

      region.save(`screen-${counter}.png`)
      template._mat.save(`screen-${counter}-template.png`)

      const origin = region.locateROI();
      const res = region.matchTemplateByMatrix(template._mat);
      const m = res.templateMatches(template.similarity(), 1, 1)
      if (m[0]) {
        console.log('GOT MATCH!!!!', counter, m)
        this.removeListener('screen-update', findMatch)
        if (timeout) {
          clearTimeout(timeout);
        }
        const match = m[0] ? {
          x: origin[2] + m[0].x + template.width() / 2,  // TODO: use template offset
          y: origin[3] + m[0].y + template.height() / 2, // TODO: use template offset
          probability: m[0].probability
        } : m[0]
        accept(match)
      }
    }
    const cancel = () => {
      console.log('TIMEOUT')
      this.removeListener('screen-update', findMatch)
      reject()
    }
    if (maxWait > 0) {
      timeout = setTimeout(cancel, maxWait)
    }
    findMatch();
    this.on('screen-update', findMatch);
  });
}

Screen.prototype.waitVanish = async function(template) {
  await this._connected()
  // ...
}

Screen.prototype.exists = async function(template) {
  await this._connected()
  // ...
}

function sleep(ms) {
{'fraction_non_alphanumeric': 0.15573227302849568, 'fraction_numerical': 0.010713496796995802, 'mean_word_length': 2.7248046071575485, 'pattern_counts': {'":': 0, '<': 7, '<?xml version=': 0, '>': 29, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4174025', 'n_tokens_mistral': 2982, 'n_tokens_neox': 2907, 'n_words': 654}