File: Screen.java

package info (click to toggle)
sikulix 1.1.0-2
  • links: PTS, VCS
  • area: main
  • in suites: stretch
  • size: 5,884 kB
  • ctags: 9,486
  • sloc: java: 44,019; cpp: 3,215; python: 2,101; xml: 1,783; sh: 171; ruby: 154; makefile: 60
file content (672 lines) | stat: -rw-r--r-- 18,410 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
/*
 * Copyright 2010-2014, Sikuli.org, Sikulix.com
 * Released under the MIT License.
 *
 * modified RaiMan
 */
package org.sikuli.script;

import org.sikuli.util.ScreenHighlighter;
import org.sikuli.util.OverlayCapturePrompt;
import org.sikuli.util.EventSubject;
import org.sikuli.util.EventObserver;
import org.sikuli.basics.Settings;
import org.sikuli.basics.Debug;
import java.awt.AWTException;
import java.awt.Rectangle;
import java.awt.Robot;
import java.util.Date;

/**
 * A screen represents a physical monitor with its coordinates and size according to the global
 * point system: the screen areas are grouped around a point (0,0) like in a cartesian system (the
 * top left corner and the points contained in the screen area might have negative x and/or y values)
 * <br >The screens are arranged in an array (index = id) and each screen is always the same object
 * (not possible to create new objects).
 * <br>A screen inherits from class Region, so it can be used as such in all aspects. If you need
 * the region of the screen more than once, you have to create new ones based on the screen.
 * <br>The so called primary screen is the one with top left (0,0) and has id 0.
 */
public class Screen extends Region implements EventObserver, IScreen {

  static RunTime runTime = RunTime.get();

  private static String me = "Screen: ";
  private static int lvl = 3;
  private static Region fakeRegion;
  private static void log(int level, String message, Object... args) {
    Debug.logx(level, me + message, args);
  }

  private static IRobot globalRobot = null;
  protected static Screen[] screens = null;
  protected static int primaryScreen = -1;
  private static int waitForScreenshot = 300;
  protected IRobot robot = null;
  protected int curID = -1;
  protected int oldID = 0;
  protected int monitor = -1;
  protected boolean waitPrompt;
  protected OverlayCapturePrompt prompt;
  private final static String promptMsg = "Select a region on the screen";
  private ScreenImage lastScreenImage = null;

  //<editor-fold defaultstate="collapsed" desc="Initialization">

  static {
    System.loadLibrary("VisionProxy");
    initScreens(false);
  }
  private long lastCaptureTime = -1;

//  private static void initScreens() {
//    initScreens(false);
//  }

  public int getcurrentID() {
    return curID;
  }

  private static void initScreens(boolean reset) {
    if (screens != null && !reset) {
      return;
    }
    log(lvl+1, "initScreens: entry");
    primaryScreen = 0;
    globalRobot = getMouseRobot();
    screens = new Screen[runTime.nMonitors];
    screens[0] = new Screen(0, runTime.mainMonitor);
    screens[0].initScreen();
    int nMonitor = 0;
    for (int i = 1; i < screens.length; i++) {
      if (nMonitor == runTime.mainMonitor) {
        nMonitor++;
      }
      screens[i] = new Screen(i, nMonitor);
      screens[i].initScreen();
      nMonitor++;
    }
    Mouse.init();
    Keys.init();
    if (getNumberScreens() > 1) {
      log(lvl, "initScreens: multi monitor mouse check");
      Location lnow = Mouse.at();
      float mmd = Settings.MoveMouseDelay;
      Settings.MoveMouseDelay = 0f;
      Location lc = null, lcn = null;
      for (Screen s : screens) {
        lc = s.getCenter();
        Mouse.move(lc);
        lcn = Mouse.at();
        if (!lc.equals(lcn)) {
          log(lvl, "*** multimonitor click check: %s center: (%d, %d) --- NOT OK:  (%d, %d)",
                  s.toStringShort(), lc.x, lc.y, lcn.x, lcn.y);
        } else {
          log(lvl, "*** checking: %s center: (%d, %d) --- OK", s.toStringShort(), lc.x, lc.y);
        }
      }
      Mouse.move(lnow);
      Settings.MoveMouseDelay = mmd;
    }
  }

  protected static IRobot getMouseRobot() {
    try {
      if (globalRobot == null) {
        globalRobot = new RobotDesktop();
      }
    } catch (AWTException e) {
      Debug.error("Can't initialize global Robot for Mouse: " + e.getMessage());
      Sikulix.terminate(999);
    }
    return globalRobot;
  }
  
  protected static Region getFakeRegion() {
    if (fakeRegion == null) {
      fakeRegion = new Region(0,0,5,5);
    }
    return fakeRegion;
  }
  
  /**
   * create a Screen (ScreenUnion) object as a united region of all available monitors
   * @return ScreenUnion
   */
//TODO: check wether this can be a Screen object, to be tested: Region methods
  public static ScreenUnion all() {
    return new ScreenUnion();
  }

  // hack to get an additional internal constructor for the initialization
  private Screen(int id, boolean init) {
    super();
    curID = id;
  }

  // hack to get an additional internal constructor for the initialization
  private Screen(int id, int monitor) {
    super();
    curID = id;
    this.monitor = monitor;
  }
  
  public static Screen as(int id) {
    if (id < 0 || id >= runTime.nMonitors) {
      Debug.error("Screen(%d) not in valid range 0 to %d - using primary %d",
							id, runTime.nMonitors - 1, primaryScreen);
			return screens[0];
    } else {
			return screens[id];
		}
  }

  /**
   * The screen object with the given id
   *
   * @param id valid screen number
   */
  public Screen(int id) {
    super();
    if (id < 0 || id >= runTime.nMonitors) {
      Debug.error("Screen(%d) not in valid range 0 to %d - using primary %d",
							id, runTime.nMonitors - 1, primaryScreen);
			curID = primaryScreen;
    } else {
			curID = id;
		}
    monitor = screens[id].monitor;
    initScreen();
  }

	/**
	 * INTERNAL USE
	 * collect all physical screens to one big region<br>
	 * TODO: under evaluation, wether it really makes sense
	 * @param isScreenUnion true/false
	 */
	public Screen(boolean isScreenUnion) {
    super(isScreenUnion);
  }

	/**
	 * INTERNAL USE
	 * collect all physical screens to one big region<br>
	 * This is under evaluation, wether it really makes sense
	 */
  public void setAsScreenUnion() {
    oldID = curID;
    curID = -1;
  }

	/**
	 * INTERNAL USE
	 * reset from being a screen union to the screen used before
	 */
  public void setAsScreen() {
    curID = oldID;
  }

  /**
   * Is the screen object having the top left corner as (0,0). If such a screen does not exist it is
   * the screen with id 0.
   */
  public Screen() {
    super();
    curID = primaryScreen;
    initScreen();
  }

  /**
	 * <br>TODO: remove this method if it is not needed
	 * @param scr
   */
  public void initScreen(Screen scr) {
    updateSelf();
  }

  private void initScreen() {
    Rectangle bounds = getBounds();
    x = (int) bounds.getX();
    y = (int) bounds.getY();
    w = (int) bounds.getWidth();
    h = (int) bounds.getHeight();
//    try {
//      robot = new RobotDesktop(this);
//      robot.setAutoDelay(10);
//    } catch (AWTException e) {
//      Debug.error("Can't initialize Java Robot on Screen " + curID + ": " + e.getMessage());
//      robot = null;
//    }
    robot = globalRobot;
  }

  /**
   * {@inheritDoc}
	 * @return Screen
   */
  @Override
  public Screen getScreen() {
    return this;
  }

  /**
   * Should not be used - throws UnsupportedOperationException
	 * @param s Screen
	 * @return should not return
   */
  @Override
  protected Region setScreen(IScreen s) {
    throw new UnsupportedOperationException("The setScreen() method cannot be called from a Screen object.");
  }

  /**
   * show the current monitor setup
   */
  public static void showMonitors() {
//    initScreens();
    Debug.logp("*** monitor configuration [ %s Screen(s)] ***", Screen.getNumberScreens());
    Debug.logp("*** Primary is Screen %d", primaryScreen);
    for (int i = 0; i < runTime.nMonitors; i++) {
      Debug.logp("Screen %d: %s", i, Screen.getScreen(i).toStringShort());
    }
    Debug.logp("*** end monitor configuration ***");
  }

  /**
   * re-initialize the monitor setup (e.g. when it was changed while running)
   */
  public static void resetMonitors() {
    Debug.error("*** BE AWARE: experimental - might not work ***");
    Debug.error("Re-evaluation of the monitor setup has been requested");
    Debug.error("... Current Region/Screen objects might not be valid any longer");
    Debug.error("... Use existing Region/Screen objects only if you know what you are doing!");
    initScreens(true);
    Debug.logp("*** new monitor configuration [ %s Screen(s)] ***", Screen.getNumberScreens());
    Debug.logp("*** Primary is Screen %d", primaryScreen);
    for (int i = 0; i < runTime.nMonitors; i++) {
      Debug.logp("Screen %d: %s", i, Screen.getScreen(i).toStringShort());
    }
    Debug.error("*** end new monitor configuration ***");
  }

  //</editor-fold>

  //<editor-fold defaultstate="collapsed" desc="getters setters">
  protected boolean useFullscreen() {
    return false;
  }

  private static int getValidID(int id) {
    if (id < 0 || id >= runTime.nMonitors) {
      Debug.error("Screen: invalid screen id %d - using primary screen", id);
      return primaryScreen;
    }
    return id;
  }

  private static int getValidMonitor(int id) {
    if (id < 0 || id >= runTime.nMonitors) {
      Debug.error("Screen: invalid screen id %d - using primary screen", id);
      return runTime.mainMonitor;
    }
    return screens[id].monitor;
  }

  /**
   *
   * @return number of available screens
   */
  public static int getNumberScreens() {
    return runTime.nMonitors;
  }

  /**
   *
   * @return the id of the screen at (0,0), if not exists 0
   */
  public static int getPrimaryId() {
    return primaryScreen;
  }

  /**
   *
   * @return the screen at (0,0), if not exists the one with id 0
   */
  public static Screen getPrimaryScreen() {
    return screens[primaryScreen];
  }

  /**
   *
   * @param id of the screen
   * @return the screen with given id, the primary screen if id is invalid
   */
  public static Screen getScreen(int id) {
    return screens[getValidID(id)];
  }

  /**
	 *
	 * @return the screen's rectangle
	 */
  @Override
  public Rectangle getBounds() {
    return new Rectangle(runTime.getMonitor(monitor));
  }

  /**
   *
   * @param id of the screen
   * @return the physical coordinate/size <br>as AWT.Rectangle to avoid mix up with getROI
   */
  public static Rectangle getBounds(int id) {
    return new Rectangle(runTime.getMonitor(getValidMonitor(id)));
  }

  /**
   * each screen has exactly one robot (internally used for screen capturing)
   * <br>available as a convenience for those who know what they are doing. Should not be needed
   * normally.
   *
   * @param id of the screen
   * @return the AWT.Robot of the given screen, if id invalid the primary screen
   */
  public static IRobot getRobot(int id) {
    return getScreen(id).getRobot();
  }

  /**
   *
	 * @return the id
   */
  @Override
  public int getID() {
    return curID;
  }

  /**
   * INTERNAL USE: to be compatible with ScreenUnion
   * @param x value
   * @param y value
   * @return id of the screen
   */
  @Override
  public int getIdFromPoint(int x, int y) {
    return curID;
  }

  /**
   * Gets the Robot of this Screen.
   *
   * @return The Robot for this Screen
   */
  @Override
  public IRobot getRobot() {
    return robot;
  }

  /**
   * creates a region on the current screen with the given coordinate/size. The coordinate is
   * translated to the current screen from its relative position on the screen it would have been
   * created normally.
   *
   * @param loc Location
   * @param width value
   * @param height value
   * @return the new region
   */
  public Region newRegion(Location loc, int width, int height) {
    return Region.create(loc.copyTo(this), width, height);
  }

  @Override
  public ScreenImage getLastScreenImageFromScreen() {
    return lastScreenImage;
  }
  /**
   * creates a location on the current screen with the given point. The coordinate is translated to
   * the current screen from its relative position on the screen it would have been created
   * normally.
   *
   * @param loc Location
   * @return the new location
   */
  public Location newLocation(Location loc) {
    return (new Location(loc)).copyTo(this);
  }

  //</editor-fold>

  //<editor-fold defaultstate="collapsed" desc="Capture - SelectRegion">
  /**
   * create a ScreenImage with the physical bounds of this screen
   *
   * @return the image
   */
  @Override
  public ScreenImage capture() {
    return capture(getRect());
  }

  /**
   * create a ScreenImage with given coordinates on this screen.
   *
   * @param x x-coordinate of the region to be captured
   * @param y y-coordinate of the region to be captured
   * @param w width of the region to be captured
   * @param h height of the region to be captured
   * @return the image of the region
   */
  @Override
  public ScreenImage capture(int x, int y, int w, int h) {
    Rectangle rect = newRegion(new Location(x, y), w, h).getRect();
    return capture(rect);
  }

  public ScreenImage captureforHighlight(int x, int y, int w, int h) {
    return robot.captureScreen(new Rectangle(x, y, w, h));
  }

  /**
   * create a ScreenImage with given rectangle on this screen.
   *
   * @param rect The Rectangle to be captured
   * @return the image of the region
   */
  @Override
  public ScreenImage capture(Rectangle rect) {
    lastCaptureTime = new Date().getTime();
    ScreenImage simg = robot.captureScreen(rect);
    if (Settings.FindProfiling) {
      Debug.logp("[FindProfiling] Screen.capture [%d x %d]: %d msec", 
              rect.width, rect.height, new Date().getTime() - lastCaptureTime);
    }
    lastScreenImage = simg;
    if (Debug.getDebugLevel() > lvl) {
      simg.saveLastScreenImage(runTime.fSikulixStore);
    }
    return simg;
  }
  
  /**
   * create a ScreenImage with given region on this screen
   *
   * @param reg The Region to be captured
   * @return the image of the region
   */
  @Override
  public ScreenImage capture(Region reg) {
    return capture(reg.getRect());
  }

  /**
   * interactive capture with predefined message: lets the user capture a screen image using the
   * mouse to draw the rectangle
   *
   * @return the image
   */
  public ScreenImage userCapture() {
    return userCapture("");
  }

  public static void startPrompt(String message, EventObserver obs) {
    String msg = message.isEmpty() ? promptMsg : message;
    for (int is = 0; is < Screen.getNumberScreens(); is++) {
      Screen.getScreen(is).prompt = new OverlayCapturePrompt(Screen.getScreen(is), obs);
      Screen.getScreen(is).prompt.prompt(msg);
    }
  }
  
  public static void closePrompt() {
    for (int is = 0; is < Screen.getNumberScreens(); is++) {
      Screen.getScreen(is).prompt.close();
    }
  }
  
  /**
   * interactive capture with given message: lets the user capture a screen image using the mouse to
   * draw the rectangle
   *
	 * @param message text
   * @return the image
   */
  @Override
  public ScreenImage userCapture(final String message) {
    waitPrompt = true;
    Thread th = new Thread() {
      @Override
      public void run() {
        String msg = message.isEmpty() ? promptMsg : message;
        for (int is = 0; is < Screen.getNumberScreens(); is++) {
          Screen.getScreen(is).prompt = new OverlayCapturePrompt(Screen.getScreen(is), Screen.this);
          Screen.getScreen(is).prompt.prompt(msg);
        }
      }
    };
    th.start();
    boolean hasShot = true;
    try {
      int count = 0;
      while (waitPrompt) {
        Thread.sleep(100);
        if (count++ > waitForScreenshot) {
          hasShot = false;
          break;
        }
      }
    } catch (InterruptedException e) {
      hasShot = false;
    }
    ScreenImage simg = null;
    if (hasShot) {
      for (int is = 0; is < Screen.getNumberScreens(); is++) {
        if (simg == null) {
          simg = Screen.getScreen(is).prompt.getSelection();
          if (simg != null) {
            Screen.getScreen(is).lastScreenImage = simg;
          }
        }
        Screen.getScreen(is).prompt.close();
      }
    }
    return simg;
  }
  
  public String saveCapture(String name) {
    return saveCapture(name, null);
  }
  
  public String saveCapture(String name, Region reg) {
    ScreenImage img;
    if (reg == null) {
      img = userCapture("Capture for image " + name);
    } else {
      img = capture(reg);
    }
    if (img == null) {
      return null;
    } else {
      return img.saveInBundle(name);
    }
  }

  /**
   * interactive region create with predefined message: lets the user draw the rectangle using the
   * mouse
   *
   * @return the region
   */
  public Region selectRegion() {
    return selectRegion("Select a region on the screen");
  }

  /**
   * interactive region create with given message: lets the user draw the rectangle using the mouse
   *
	 * @param message text
   * @return the region
   */
  public Region selectRegion(final String message) {
    ScreenImage sim = userCapture(message);
    if (sim == null) {
      return null;
    }
    Rectangle r = sim.getROI();
    return Region.create((int) r.getX(), (int) r.getY(),
            (int) r.getWidth(), (int) r.getHeight());
  }

  /**
   * Internal use only
   *
   * @param s EventSubject
   */
  @Override
  public void update(EventSubject s) {
    waitPrompt = false;
  }
  //</editor-fold>

  //<editor-fold defaultstate="collapsed" desc="Visual effects">
  @Override
  public void showTarget(Location loc) {
    showTarget(loc, Settings.SlowMotionDelay);
  }

  protected void showTarget(Location loc, double secs) {
    if (Settings.isShowActions()) {
      ScreenHighlighter overlay = new ScreenHighlighter(this, null);
      overlay.showTarget(loc, (float) secs);
    }
  }
  //</editor-fold>

  @Override
  public String toString() {
    Rectangle r = getBounds();
    return String.format("S(%d)[%d,%d %dx%d] E:%s, T:%.1f",
            curID, (int) r.getX(), (int) r.getY(),
            (int) r.getWidth(), (int) r.getHeight(),
            getThrowException() ? "Y" : "N", getAutoWaitTimeout());
  }

  /**
   * only a short version of toString()
   *
   * @return like S(0) [0,0, 1440x900]
   */
  @Override
  public String toStringShort() {
    Rectangle r = getBounds();
    return String.format("S(%d)[%d,%d %dx%d]",
            curID, (int) r.getX(), (int) r.getY(),
            (int) r.getWidth(), (int) r.getHeight());
  }

	@Override
	public String toJSON() {
    Rectangle r = getBounds();
		return String.format("[\"S\", %d, %d, %d, %d, %d]", r.x, r.y, r.width, r.height, curID);
	}
}