1 /*******************************************************************************
2 * Copyright (c) 2000, 2018 IBM Corporation and others.
3 *
4 * This program and the accompanying materials
5 * are made available under the terms of the Eclipse Public License 2.0
6 * which accompanies this distribution, and is available at
7 * https://www.eclipse.org/legal/epl-2.0/
8 *
9 * SPDX-License-Identifier: EPL-2.0
10 *
11 * Contributors:
12 * IBM Corporation - initial API and implementation
13 *******************************************************************************/
14 package org.eclipse.swt.graphics;
15
16
17 import java.io.*;
18 import java.util.function.*;
19 import java.util.stream.*;
20
21 import org.eclipse.swt.*;
22 import org.eclipse.swt.internal.*;
23 import org.eclipse.swt.internal.cairo.*;
24 import org.eclipse.swt.internal.gtk.*;
25 import org.eclipse.swt.internal.gtk3.*;
26 import org.eclipse.swt.internal.gtk4.*;
27
28 /**
29 * This class is the abstract superclass of all device objects,
30 * such as the Display device and the Printer device. Devices
31 * can have a graphics context (GC) created for them, and they
32 * can be drawn on by sending messages to the associated GC.
33 *
34 * @see <a href="http://www.eclipse.org/swt/">Sample code and further information</a>
35 */
36 public abstract class Device implements Drawable {
37 /**
38 * @noreference This field is not intended to be referenced by clients.
39 * @since 3.105
40 */
41 protected static final int CHANGE_SCALEFACTOR = 1;
42 /* Settings callbacks */
43 long gsettingsProc;
44 Callback gsettingsCallback;
45 boolean isConnected = false;
46 long displaySettings; //gsettings Dictionary
47
48 /**
49 * the handle to the X Display
50 * (Warning: This field is platform dependent)
51 * <p>
52 * <b>IMPORTANT:</b> This field is <em>not</em> part of the SWT
53 * public API. It is marked protected only so that it can be shared
54 * within the packages provided by SWT. It is not available on all
55 * platforms and should never be accessed from application code.
56 * </p>
57 *
58 * @noreference This field is not intended to be referenced by clients.
59 */
60 protected long xDisplay;
61 long shellHandle;
62
63 /* Debugging */
64 public static boolean DEBUG;
65 boolean debug = DEBUG;
66 boolean tracking = DEBUG;
67 Error [] errors;
68 Object [] objects;
69 Object trackingLock;
70
71 /* Disposed flag */
72 boolean disposed;
73
74 /* Warning and Error Handlers */
75 long logProc;
76 Callback logCallback;
77 //NOT DONE - get list of valid names
78 String [] log_domains = {"", "GLib-GObject", "GLib", "GObject", "Pango", "ATK", "GdkPixbuf", "Gdk", "Gtk", "GnomeVFS", "GIO"};
79 int [] handler_ids = new int [log_domains.length];
80 int warningLevel;
81
82 /* X Warning and Error Handlers */
83 static Callback XErrorCallback, XIOErrorCallback;
84 static long XErrorProc, XIOErrorProc, XNullErrorProc, XNullIOErrorProc;
85 static Device[] Devices = new Device[4];
86
87 /*
88 * The following colors are listed in the Windows
89 * Programmer's Reference as the colors in the default
90 * palette.
91 */
92 Color COLOR_BLACK, COLOR_DARK_RED, COLOR_DARK_GREEN, COLOR_DARK_YELLOW, COLOR_DARK_BLUE;
93 Color COLOR_DARK_MAGENTA, COLOR_DARK_CYAN, COLOR_GRAY, COLOR_DARK_GRAY, COLOR_RED, COLOR_TRANSPARENT;
94 Color COLOR_GREEN, COLOR_YELLOW, COLOR_BLUE, COLOR_MAGENTA, COLOR_CYAN, COLOR_WHITE;
95
96 /* System Font */
97 Font systemFont;
98
99 /* Device dpi */
100 Point dpi;
101
102 long emptyTab;
103
104 /*
105 * TEMPORARY CODE. When a graphics object is
106 * created and the device parameter is null,
107 * the current Display is used. This presents
108 * a problem because SWT graphics does not
109 * reference classes in SWT widgets. The correct
110 * fix is to remove this feature. Unfortunately,
111 * too many application programs rely on this
112 * feature.
113 */
114 protected static Device CurrentDevice;
115 protected static Runnable DeviceFinder;
116 static {
117 try {
118 Class.forName ("org.eclipse.swt.widgets.Display");
119 } catch (ClassNotFoundException e) {}
120 }
121
122 /*
123 * TEMPORARY CODE.
124 */
getDevice()125 static synchronized Device getDevice () {
126 if (DeviceFinder != null) DeviceFinder.run();
127 Device device = CurrentDevice;
128 CurrentDevice = null;
129 return device;
130 }
131
132 /**
133 * Constructs a new instance of this class.
134 * <p>
135 * You must dispose the device when it is no longer required.
136 * </p>
137 *
138 * @see #create
139 * @see #init
140 *
141 * @since 3.1
142 */
Device()143 public Device() {
144 this(null);
145 }
146
147 /**
148 * Constructs a new instance of this class.
149 * <p>
150 * You must dispose the device when it is no longer required.
151 * </p>
152 *
153 * @param data the DeviceData which describes the receiver
154 *
155 * @see #create
156 * @see #init
157 * @see DeviceData
158 */
Device(DeviceData data)159 public Device(DeviceData data) {
160 synchronized (Device.class) {
161 if (data != null) {
162 debug = data.debug;
163 tracking = data.tracking;
164 }
165 if (tracking) {
166 startTracking();
167 }
168 create (data);
169 init ();
170 register (this);
171 }
172 }
173
174 /**
175 *
176 * @exception SWTException <ul>
177 * <li>ERROR_DEVICE_DISPOSED - if the receiver has been disposed</li>
178 * </ul>
179 * @since 3.115
180 */
isTracking()181 public boolean isTracking() {
182 checkDevice();
183 return tracking;
184 }
185
186 /**
187 * @exception SWTException <ul>
188 * <li>ERROR_DEVICE_DISPOSED - if the receiver has been disposed</li>
189 * </ul>
190 * @since 3.115
191 */
setTracking(boolean tracking)192 public void setTracking(boolean tracking) {
193 checkDevice();
194 if (tracking == this.tracking) {
195 return;
196 }
197 this.tracking = tracking;
198 if (tracking) {
199 startTracking();
200 } else {
201 stopTracking();
202 }
203 }
204
startTracking()205 private void startTracking() {
206 errors = new Error [128];
207 objects = new Object [128];
208 trackingLock = new Object ();
209 }
210
stopTracking()211 private void stopTracking() {
212 synchronized (trackingLock) {
213 objects = null;
214 errors = null;
215 trackingLock = null;
216 }
217 }
218
219 /**
220 * Throws an <code>SWTException</code> if the receiver can not
221 * be accessed by the caller. This may include both checks on
222 * the state of the receiver and more generally on the entire
223 * execution context. This method <em>should</em> be called by
224 * device implementors to enforce the standard SWT invariants.
225 * <p>
226 * Currently, it is an error to invoke any method (other than
227 * <code>isDisposed()</code> and <code>dispose()</code>) on a
228 * device that has had its <code>dispose()</code> method called.
229 * </p><p>
230 * In future releases of SWT, there may be more or fewer error
231 * checks and exceptions may be thrown for different reasons.
232 * </p>
233 *
234 * @exception SWTException <ul>
235 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
236 * </ul>
237 */
checkDevice()238 protected void checkDevice () {
239 if (disposed) SWT.error(SWT.ERROR_DEVICE_DISPOSED);
240 }
241
242 /**
243 * Creates the device in the operating system. If the device
244 * does not have a handle, this method may do nothing depending
245 * on the device.
246 * <p>
247 * This method is called before <code>init</code>.
248 * </p><p>
249 * Subclasses are supposed to reimplement this method and not
250 * call the <code>super</code> implementation.
251 * </p>
252 *
253 * @param data the DeviceData which describes the receiver
254 *
255 * @see #init
256 */
create(DeviceData data)257 protected void create (DeviceData data) {
258 }
259
260 /**
261 * Disposes of the operating system resources associated with
262 * the receiver. After this method has been invoked, the receiver
263 * will answer <code>true</code> when sent the message
264 * <code>isDisposed()</code>.
265 *
266 * @see #release
267 * @see #destroy
268 * @see #checkDevice
269 */
dispose()270 public void dispose () {
271 synchronized (Device.class) {
272 if (isDisposed()) return;
273 checkDevice ();
274 release ();
275 destroy ();
276 deregister (this);
277 xDisplay = 0;
278 disposed = true;
279 if (tracking) {
280 tracking = false;
281 stopTracking();
282 }
283 }
284 }
285
dispose_Object(Object object)286 void dispose_Object (Object object) {
287 synchronized (trackingLock) {
288 for (int i=0; i<objects.length; i++) {
289 if (objects [i] == object) {
290 objects [i] = null;
291 errors [i] = null;
292 return;
293 }
294 }
295 }
296 }
297
findDevice(long xDisplay)298 static synchronized Device findDevice (long xDisplay) {
299 for (int i=0; i<Devices.length; i++) {
300 Device device = Devices [i];
301 if (device != null && device.xDisplay == xDisplay) {
302 return device;
303 }
304 }
305 return null;
306 }
307
deregister(Device device)308 synchronized static void deregister (Device device) {
309 for (int i=0; i<Devices.length; i++) {
310 if (device == Devices [i]) Devices [i] = null;
311 }
312 }
313
314 /**
315 * Destroys the device in the operating system and releases
316 * the device's handle. If the device does not have a handle,
317 * this method may do nothing depending on the device.
318 * <p>
319 * This method is called after <code>release</code>.
320 * </p><p>
321 * Subclasses are supposed to reimplement this method and not
322 * call the <code>super</code> implementation.
323 * </p>
324 *
325 * @see #dispose
326 * @see #release
327 */
destroy()328 protected void destroy () {
329 }
330
331 /**
332 * Returns a rectangle describing the receiver's size and location.
333 *
334 * @return the bounding rectangle
335 *
336 * @exception SWTException <ul>
337 * <li>ERROR_DEVICE_DISPOSED - if the receiver has been disposed</li>
338 * </ul>
339 */
getBounds()340 public Rectangle getBounds () {
341 checkDevice ();
342 return DPIUtil.autoScaleDown (getBoundsInPixels ());
343 }
344
getBoundsInPixels()345 private Rectangle getBoundsInPixels () {
346 return new Rectangle(0, 0, 0, 0);
347 }
348
349 /**
350 * Returns a <code>DeviceData</code> based on the receiver.
351 * Modifications made to this <code>DeviceData</code> will not
352 * affect the receiver.
353 *
354 * @return a <code>DeviceData</code> containing the device's data and attributes
355 *
356 * @exception SWTException <ul>
357 * <li>ERROR_DEVICE_DISPOSED - if the receiver has been disposed</li>
358 * </ul>
359 *
360 * @see DeviceData
361 */
getDeviceData()362 public DeviceData getDeviceData () {
363 checkDevice();
364 DeviceData data = new DeviceData ();
365 data.debug = debug;
366 data.tracking = tracking;
367 if (tracking) {
368 synchronized (trackingLock) {
369 int count = 0, length = objects.length;
370 for (int i=0; i<length; i++) {
371 if (objects [i] != null) count++;
372 }
373 int index = 0;
374 data.objects = new Object [count];
375 data.errors = new Error [count];
376 for (int i=0; i<length; i++) {
377 if (objects [i] != null) {
378 data.objects [index] = objects [i];
379 data.errors [index] = errors [i];
380 index++;
381 }
382 }
383 }
384 } else {
385 data.objects = new Object [0];
386 data.errors = new Error [0];
387 }
388 return data;
389 }
390
391 /**
392 * Returns a rectangle which describes the area of the
393 * receiver which is capable of displaying data.
394 *
395 * @return the client area
396 *
397 * @exception SWTException <ul>
398 * <li>ERROR_DEVICE_DISPOSED - if the receiver has been disposed</li>
399 * </ul>
400 *
401 * @see #getBounds
402 */
getClientArea()403 public Rectangle getClientArea () {
404 return getBounds ();
405 }
406
407 /**
408 * Returns the bit depth of the screen, which is the number of
409 * bits it takes to represent the number of unique colors that
410 * the screen is currently capable of displaying. This number
411 * will typically be one of 1, 8, 15, 16, 24 or 32.
412 *
413 * @return the depth of the screen
414 *
415 * @exception SWTException <ul>
416 * <li>ERROR_DEVICE_DISPOSED - if the receiver has been disposed</li>
417 * </ul>
418 */
getDepth()419 public int getDepth () {
420 checkDevice ();
421 return 0;
422 }
423
424 /**
425 * Returns a point whose x coordinate is the logical horizontal
426 * dots per inch of the display, and whose y coordinate
427 * is the logical vertical dots per inch of the display.
428 *
429 * @return the horizontal and vertical DPI
430 *
431 * @exception SWTException <ul>
432 * <li>ERROR_DEVICE_DISPOSED - if the receiver has been disposed</li>
433 * </ul>
434 */
getDPI()435 public Point getDPI () {
436 checkDevice ();
437 return getScreenDPI();
438 }
439
440 /**
441 * Returns <code>FontData</code> objects which describe
442 * the fonts that match the given arguments. If the
443 * <code>faceName</code> is null, all fonts will be returned.
444 *
445 * @param faceName the name of the font to look for, or null
446 * @param scalable if true only scalable fonts are returned, otherwise only non-scalable fonts are returned.
447 * @return the matching font data
448 *
449 * @exception SWTException <ul>
450 * <li>ERROR_DEVICE_DISPOSED - if the receiver has been disposed</li>
451 * </ul>
452 */
getFontList(String faceName, boolean scalable)453 public FontData[] getFontList (String faceName, boolean scalable) {
454 checkDevice ();
455 if (!scalable) return new FontData[0];
456 long [] family = new long [1];
457 long [] face = new long [1];
458 long [] families = new long [1];
459 int[] n_families = new int[1];
460 long [] faces = new long [1];
461 int[] n_faces = new int[1];
462 long context;
463 if (GTK.GTK4) {
464 long fontMap = OS.pango_cairo_font_map_get_default ();
465 context = OS.pango_font_map_create_context (fontMap);
466 } else {
467 context = GDK.gdk_pango_context_get();
468 }
469 OS.pango_context_list_families(context, families, n_families);
470 int nFds = 0;
471 FontData[] fds = new FontData[faceName != null ? 4 : n_families[0]];
472 for (int i=0; i<n_families[0]; i++) {
473 C.memmove(family, families[0] + i * C.PTR_SIZEOF, C.PTR_SIZEOF);
474 boolean match = true;
475 if (faceName != null) {
476 long familyName = OS.pango_font_family_get_name(family[0]);
477 int length = C.strlen(familyName);
478 byte[] buffer = new byte[length];
479 C.memmove(buffer, familyName, length);
480 String name = new String(Converter.mbcsToWcs(buffer));
481 match = faceName.equalsIgnoreCase(name);
482 }
483 if (match) {
484 OS.pango_font_family_list_faces(family[0], faces, n_faces);
485 for (int j=0; j<n_faces[0]; j++) {
486 C.memmove(face, faces[0] + j * C.PTR_SIZEOF, C.PTR_SIZEOF);
487 long fontDesc = OS.pango_font_face_describe(face[0]);
488 Font font = Font.gtk_new(this, fontDesc);
489 FontData data = font.getFontData()[0];
490 if (nFds == fds.length) {
491 FontData[] newFds = new FontData[fds.length + n_families[0]];
492 System.arraycopy(fds, 0, newFds, 0, nFds);
493 fds = newFds;
494 }
495 fds[nFds++] = data;
496 OS.pango_font_description_free(fontDesc);
497 }
498 OS.g_free(faces[0]);
499 if (faceName != null) break;
500 }
501 }
502 OS.g_free(families[0]);
503 OS.g_object_unref(context);
504 if (nFds == fds.length) return fds;
505 FontData[] result = new FontData[nFds];
506 System.arraycopy(fds, 0, result, 0, nFds);
507 return result;
508 }
509
getScreenDPI()510 Point getScreenDPI () {
511 Point ptDPI;
512
513 if (GTK.GTK4) {
514 ptDPI = new Point (96, 96);
515 } else {
516 long screen = GDK.gdk_screen_get_default();
517 int dpi = (int) GDK.gdk_screen_get_resolution(screen);
518 ptDPI = dpi == -1 ? new Point (96, 96) : new Point (dpi, dpi);
519 }
520
521 return ptDPI;
522 }
523
524 /**
525 * Returns the matching standard color for the given
526 * constant, which should be one of the color constants
527 * specified in class <code>SWT</code>. Any value other
528 * than one of the SWT color constants which is passed
529 * in will result in the color black. This color should
530 * not be freed because it was allocated by the system,
531 * not the application.
532 *
533 * @param id the color constant
534 * @return the matching color
535 *
536 * @exception SWTException <ul>
537 * <li>ERROR_DEVICE_DISPOSED - if the receiver has been disposed</li>
538 * </ul>
539 *
540 * @see SWT
541 */
getSystemColor(int id)542 public Color getSystemColor (int id) {
543 checkDevice ();
544 switch (id) {
545 case SWT.COLOR_TRANSPARENT: return COLOR_TRANSPARENT;
546 case SWT.COLOR_BLACK: return COLOR_BLACK;
547 case SWT.COLOR_DARK_RED: return COLOR_DARK_RED;
548 case SWT.COLOR_DARK_GREEN: return COLOR_DARK_GREEN;
549 case SWT.COLOR_DARK_YELLOW: return COLOR_DARK_YELLOW;
550 case SWT.COLOR_DARK_BLUE: return COLOR_DARK_BLUE;
551 case SWT.COLOR_DARK_MAGENTA: return COLOR_DARK_MAGENTA;
552 case SWT.COLOR_DARK_CYAN: return COLOR_DARK_CYAN;
553 case SWT.COLOR_GRAY: return COLOR_GRAY;
554 case SWT.COLOR_DARK_GRAY: return COLOR_DARK_GRAY;
555 case SWT.COLOR_RED: return COLOR_RED;
556 case SWT.COLOR_GREEN: return COLOR_GREEN;
557 case SWT.COLOR_YELLOW: return COLOR_YELLOW;
558 case SWT.COLOR_BLUE: return COLOR_BLUE;
559 case SWT.COLOR_MAGENTA: return COLOR_MAGENTA;
560 case SWT.COLOR_CYAN: return COLOR_CYAN;
561 case SWT.COLOR_WHITE: return COLOR_WHITE;
562 }
563 return COLOR_BLACK;
564 }
565
566 /**
567 * Returns a reasonable font for applications to use.
568 * On some platforms, this will match the "default font"
569 * or "system font" if such can be found. This font
570 * should not be freed because it was allocated by the
571 * system, not the application.
572 * <p>
573 * Typically, applications which want the default look
574 * should simply not set the font on the widgets they
575 * create. Widgets are always created with the correct
576 * default font for the class of user-interface component
577 * they represent.
578 * </p>
579 *
580 * @return a font
581 *
582 * @exception SWTException <ul>
583 * <li>ERROR_DEVICE_DISPOSED - if the receiver has been disposed</li>
584 * </ul>
585 */
getSystemFont()586 public Font getSystemFont () {
587 checkDevice ();
588 return systemFont;
589 }
590
591 /**
592 * Returns <code>true</code> if the underlying window system prints out
593 * warning messages on the console, and <code>setWarnings</code>
594 * had previously been called with <code>true</code>.
595 *
596 * @return <code>true</code>if warnings are being handled, and <code>false</code> otherwise
597 *
598 * @exception SWTException <ul>
599 * <li>ERROR_DEVICE_DISPOSED - if the receiver has been disposed</li>
600 * </ul>
601 */
getWarnings()602 public boolean getWarnings () {
603 checkDevice ();
604 return warningLevel == 0;
605 }
606
607 /**
608 * Initializes any internal resources needed by the
609 * device.
610 * <p>
611 * This method is called after <code>create</code>.
612 * </p><p>
613 * If subclasses reimplement this method, they must
614 * call the <code>super</code> implementation.
615 * </p>
616 *
617 * @see #create
618 */
init()619 protected void init () {
620 if (debug) {
621 if (xDisplay != 0) {
622 /* Create the warning and error callbacks */
623 Class<?> clazz = getClass ();
624 synchronized (clazz) {
625 int index = 0;
626 while (index < Devices.length) {
627 if (Devices [index] != null) break;
628 index++;
629 }
630 if (index == Devices.length) {
631 XErrorCallback = new Callback (clazz, "XErrorProc", 2);
632 XNullErrorProc = XErrorCallback.getAddress ();
633 XIOErrorCallback = new Callback (clazz, "XIOErrorProc", 1);
634 XNullIOErrorProc = XIOErrorCallback.getAddress ();
635 XErrorProc = OS.XSetErrorHandler (XNullErrorProc);
636 XIOErrorProc = OS.XSetIOErrorHandler (XNullIOErrorProc);
637 }
638 }
639 if (debug) OS.XSynchronize (xDisplay, true);
640 }
641 }
642
643 /* Create GTK warnings and error callbacks */
644 if (xDisplay != 0) {
645 logCallback = new Callback (this, "logProc", 4);
646 logProc = logCallback.getAddress ();
647
648 /* Set GTK warning and error handlers */
649 if (debug) {
650 int flags = OS.G_LOG_LEVEL_MASK | OS.G_LOG_FLAG_FATAL | OS.G_LOG_FLAG_RECURSION;
651 for (int i=0; i<log_domains.length; i++) {
652 byte [] log_domain = Converter.wcsToMbcs (log_domains [i], true);
653 handler_ids [i] = OS.g_log_set_handler (log_domain, flags, logProc, 0);
654 }
655 }
656 }
657
658 /* Create the standard colors */
659 COLOR_TRANSPARENT = new Color (0xFF, 0xFF,0xFF,0);
660 COLOR_BLACK = new Color (0, 0,0);
661 COLOR_DARK_RED = new Color (0x80, 0,0);
662 COLOR_DARK_GREEN = new Color (0, 0x80,0);
663 COLOR_DARK_YELLOW = new Color (0x80, 0x80,0);
664 COLOR_DARK_BLUE = new Color (0, 0,0x80);
665 COLOR_DARK_MAGENTA = new Color (0x80, 0,0x80);
666 COLOR_DARK_CYAN = new Color (0, 0x80,0x80);
667 COLOR_GRAY = new Color (0xC0, 0xC0,0xC0);
668 COLOR_DARK_GRAY = new Color (0x80, 0x80,0x80);
669 COLOR_RED = new Color (0xFF, 0,0);
670 COLOR_GREEN = new Color (0, 0xFF,0);
671 COLOR_YELLOW = new Color (0xFF, 0xFF,0);
672 COLOR_BLUE = new Color (0, 0,0xFF);
673 COLOR_MAGENTA = new Color (0xFF, 0,0xFF);
674 COLOR_CYAN = new Color (0, 0xFF,0xFF);
675 COLOR_WHITE = new Color (0xFF, 0xFF,0xFF);
676
677 emptyTab = OS.pango_tab_array_new(1, false);
678 if (emptyTab == 0) SWT.error(SWT.ERROR_NO_HANDLES);
679 OS.pango_tab_array_set_tab(emptyTab, 0, OS.PANGO_TAB_LEFT, 1);
680
681 if (GTK.GTK4) {
682 shellHandle = GTK4.gtk_window_new();
683 } else {
684 shellHandle = GTK3.gtk_window_new (GTK.GTK_WINDOW_TOPLEVEL);
685 }
686 if (shellHandle == 0) SWT.error(SWT.ERROR_NO_HANDLES);
687 GTK.gtk_widget_realize(shellHandle);
688
689 this.dpi = getDPI();
690 DPIUtil.setDeviceZoom (getDeviceZoom ());
691
692 if (GTK.GTK_VERSION >= OS.VERSION(3, 22, 0)) {
693 double sx[] = new double[1];
694 double sy[] = new double[1];
695 long gdkResource;
696 long surface;
697 if (GTK.GTK4) {
698 surface = Cairo.cairo_image_surface_create(Cairo.CAIRO_FORMAT_RGB24, 10, 10);
699 } else {
700 gdkResource = GDK.gdk_get_default_root_window();
701 surface = GDK.gdk_window_create_similar_surface(gdkResource, Cairo.CAIRO_CONTENT_COLOR, 10, 10);
702 }
703 Cairo.cairo_surface_get_device_scale(surface, sx, sy);
704 DPIUtil.setUseCairoAutoScale((sx[0]*100) == DPIUtil.getDeviceZoom() || OS.isGNOME);
705 }
706
707 /* Initialize the system font slot */
708 long [] defaultFontArray = new long [1];
709 long defaultFont = 0;
710 long context = GTK.gtk_widget_get_style_context (shellHandle);
711 if ("ppc64le".equals(System.getProperty("os.arch"))) {
712 defaultFont = GTK.gtk_style_context_get_font (context, GTK.GTK_STATE_FLAG_NORMAL);
713 } else {
714 if (GTK.GTK4) {
715 long[] fontPtr = new long[1];
716 long settings = GTK.gtk_settings_get_default ();
717 OS.g_object_get (settings, GTK.gtk_style_property_font, fontPtr, 0);
718 if (fontPtr[0] != 0) {
719 int length = C.strlen(fontPtr[0]);
720 if (length != 0) {
721 byte[] fontString = new byte [length + 1];
722 C.memmove(fontString, fontPtr[0], length);
723 OS.g_free(fontPtr[0]);
724 defaultFont = OS.pango_font_description_from_string(fontString);
725 }
726 }
727 } else {
728 GTK.gtk_style_context_save(context);
729 GTK.gtk_style_context_set_state(context, GTK.GTK_STATE_FLAG_NORMAL);
730 GTK.gtk_style_context_get(context, GTK.GTK_STATE_FLAG_NORMAL, GTK.gtk_style_property_font, defaultFontArray, 0);
731 GTK.gtk_style_context_restore(context);
732 defaultFont = defaultFontArray [0];
733 }
734 }
735 defaultFont = OS.pango_font_description_copy (defaultFont);
736 Point dpi = getDPI(), screenDPI = getScreenDPI();
737 if (dpi.y != screenDPI.y) {
738 int size = OS.pango_font_description_get_size(defaultFont);
739 OS.pango_font_description_set_size(defaultFont, size * dpi.y / screenDPI.y);
740 }
741 systemFont = Font.gtk_new (this, defaultFont);
742
743 //overrideThemeValues();
744 }
745
746 /**
747 * For functionality & improved looks, we override some CSS theme values with custom values.
748 *
749 * Note about theme load mechanism:
750 * - This method is reached early at start of SWT initialization.
751 * Later, platform.ui will call OS.setDarkThemePreferred(true), which tells Gtk to use dark theme.
752 * This has the implication that the system theme can be 'Adwaita' (light), but later be 'darkened'
753 * by platform.ui. This means that there should not be any color-specific overrides in Adwaita theming
754 * because 'Adwaita' is used for both light and dark theme.
755 *
756 * Note about light/dark system theme:
757 * - If the System theme is Adwaita (light), eclipse can be forced to be dark with setDarkThemePreferred(true).
758 * But if the System theme is Adwaita-dark, eclipse cannot be made 'light'.
759 *
760 * Note that much of eclipse 'dark theme' is done by platform.ui's CSS engine, not by SWT.
761 */
overrideThemeValues()762 private void overrideThemeValues () {
763 long provider = GTK.gtk_css_provider_new();
764
765 BiFunction <String, Boolean, String> load = (path, isResource) -> {
766 try {
767 BufferedReader buffer;
768 if (isResource) {
769 buffer = new BufferedReader(new InputStreamReader(Device.class.getResourceAsStream(path)));
770 } else {
771 buffer = new BufferedReader(new FileReader(new File(path)));
772 }
773 return buffer.lines().collect(Collectors.joining("\n"));
774 } catch (IOException e) {
775 System.err.println("SWT Warning: Failed to load " + (isResource ? "resource: " : "file: ") + path);
776 return "";
777 }
778 };
779
780 StringBuilder combinedCSS = new StringBuilder();
781
782 if (!GTK.GTK4) {
783 // Load functional CSS fixes. Such as keyboard functionality for some widgets.
784 combinedCSS.append(load.apply("/org/eclipse/swt/internal/gtk/swt_functional_gtk_3_20.css", true));
785 }
786
787 // By default, load CSS theme fixes to overcome things such as excessive padding that breaks SWT otherwise.
788 // Initially designed for Adwaita light/dark theme, but after investigation other themes (like Ubuntu's Ambiance + dark) seem to benefit from this also.
789 // However, a few themes break with these fixes, so we allow them to be turned off by user and allow them to load their own fixes manually instead.
790 // To turn on this flag, add the following vm argument: -Dorg.eclipse.swt.internal.gtk.noThemingFixes
791 // Note:
792 // - Display.create() may override the theme name. See Display.create() ... OS.getThemeName(..).
793 // - These fixes should not contain any color information, otherwise it might break a light/dark variant of the theme.
794 // Color fixes should be put either into the theme itself or via swt user api.
795 if (System.getProperty("org.eclipse.swt.internal.gtk.noThemingFixes") == null) {
796 combinedCSS.append(load.apply("/org/eclipse/swt/internal/gtk/swt_theming_fixes_gtk_3_20.css", true));
797 if (GTK.GTK_VERSION >= OS.VERSION(3, 24, 5)) {
798 combinedCSS.append(load.apply("/org/eclipse/swt/internal/gtk/swt_theming_fixes_gtk_3_24_5.css", true));
799 }
800 }
801
802 // Load CSS from user-defined CSS file.
803 String additionalCSSPath = System.getProperty("org.eclipse.swt.internal.gtk.cssFile");
804 if (additionalCSSPath != null){
805 // Warning:
806 // - gtk css syntax changed in 3.20. If you load custom css, it could break things depending on gtk version on system.
807 // - Also, a lot of custom css/themes are buggy and may result in additional console warnings.
808 combinedCSS.append(load.apply(additionalCSSPath, false));
809 }
810
811 if (GTK.GTK4) {
812 long display = GDK.gdk_display_get_default();
813 if (display == 0 || provider == 0) {
814 System.err.println("SWT Warning: Override of theme values failed. Reason: could not acquire display or provider.");
815 return;
816 }
817 GTK.gtk_style_context_add_provider_for_display (display, provider, GTK.GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
818 } else {
819 long screen = GDK.gdk_screen_get_default();
820 if (screen == 0 || provider == 0) {
821 System.err.println("SWT Warning: Override of theme values failed. Reason: could not acquire screen or provider.");
822 return;
823 }
824 GTK.gtk_style_context_add_provider_for_screen (screen, provider, GTK.GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
825 }
826 if (GTK.GTK4) {
827 GTK4.gtk_css_provider_load_from_data (provider, Converter.wcsToMbcs (combinedCSS.toString(), true), -1);
828 } else {
829 GTK3.gtk_css_provider_load_from_data (provider, Converter.wcsToMbcs (combinedCSS.toString(), true), -1, null);
830 }
831 }
832
833 /**
834 * Invokes platform specific functionality to allocate a new GC handle.
835 * <p>
836 * <b>IMPORTANT:</b> This method is <em>not</em> part of the public
837 * API for <code>Device</code>. It is marked public only so that it
838 * can be shared within the packages provided by SWT. It is not
839 * available on all platforms, and should never be called from
840 * application code.
841 * </p>
842 *
843 * @param data the platform specific GC data
844 * @return the platform specific GC handle
845 *
846 * @noreference This method is not intended to be referenced by clients.
847 */
848 @Override
internal_new_GC(GCData data)849 public abstract long internal_new_GC (GCData data);
850
851 /**
852 * Invokes platform specific functionality to dispose a GC handle.
853 * <p>
854 * <b>IMPORTANT:</b> This method is <em>not</em> part of the public
855 * API for <code>Device</code>. It is marked public only so that it
856 * can be shared within the packages provided by SWT. It is not
857 * available on all platforms, and should never be called from
858 * application code.
859 * </p>
860 *
861 * @param hDC the platform specific GC handle
862 * @param data the platform specific GC data
863 *
864 * @noreference This method is not intended to be referenced by clients.
865 */
866 @Override
internal_dispose_GC(long hDC, GCData data)867 public abstract void internal_dispose_GC (long hDC, GCData data);
868
869 /**
870 * Returns <code>true</code> if the device has been disposed,
871 * and <code>false</code> otherwise.
872 * <p>
873 * This method gets the dispose state for the device.
874 * When a device has been disposed, it is an error to
875 * invoke any other method using the device.
876 *
877 * @return <code>true</code> when the device is disposed and <code>false</code> otherwise
878 */
isDisposed()879 public boolean isDisposed () {
880 synchronized (Device.class) {
881 return disposed;
882 }
883 }
884
885 /**
886 * Loads the font specified by a file. The font will be
887 * present in the list of fonts available to the application.
888 *
889 * @param path the font file path
890 * @return whether the font was successfully loaded
891 *
892 * @exception SWTException <ul>
893 * <li>ERROR_NULL_ARGUMENT - if path is null</li>
894 * <li>ERROR_DEVICE_DISPOSED - if the receiver has been disposed</li>
895 * </ul>
896 *
897 * @see Font
898 *
899 * @since 3.3
900 */
loadFont(String path)901 public boolean loadFont (String path) {
902 checkDevice();
903 if (path == null) SWT.error (SWT.ERROR_NULL_ARGUMENT);
904 byte [] buffer = Converter.wcsToMbcs (path, true);
905 return OS.FcConfigAppFontAddFile (0, buffer);
906 }
907
logProc(long log_domain, long log_level, long message, long user_data)908 long logProc (long log_domain, long log_level, long message, long user_data) {
909 if (DEBUG) {
910 new Error ().printStackTrace ();
911 }
912 if (warningLevel == 0) {
913 if (DEBUG || debug) {
914 new Error ().printStackTrace ();
915 }
916 OS.g_log_default_handler (log_domain, (int)log_level, message, 0);
917 }
918 return 0;
919 }
920
new_Object(Object object)921 void new_Object (Object object) {
922 synchronized (trackingLock) {
923 for (int i=0; i<objects.length; i++) {
924 if (objects [i] == null) {
925 objects [i] = object;
926 errors [i] = new Error ();
927 return;
928 }
929 }
930 Object [] newObjects = new Object [objects.length + 128];
931 System.arraycopy (objects, 0, newObjects, 0, objects.length);
932 newObjects [objects.length] = object;
933 objects = newObjects;
934 Error [] newErrors = new Error [errors.length + 128];
935 System.arraycopy (errors, 0, newErrors, 0, errors.length);
936 newErrors [errors.length] = new Error ();
937 errors = newErrors;
938 }
939 }
940
register(Device device)941 static synchronized void register (Device device) {
942 for (int i=0; i<Devices.length; i++) {
943 if (Devices [i] == null) {
944 Devices [i] = device;
945 return;
946 }
947 }
948 Device [] newDevices = new Device [Devices.length + 4];
949 System.arraycopy (Devices, 0, newDevices, 0, Devices.length);
950 newDevices [Devices.length] = device;
951 Devices = newDevices;
952 }
953
954 /**
955 * Releases any internal resources back to the operating
956 * system and clears all fields except the device handle.
957 * <p>
958 * When a device is destroyed, resources that were acquired
959 * on behalf of the programmer need to be returned to the
960 * operating system. For example, if the device allocated a
961 * font to be used as the system font, this font would be
962 * freed in <code>release</code>. Also,to assist the garbage
963 * collector and minimize the amount of memory that is not
964 * reclaimed when the programmer keeps a reference to a
965 * disposed device, all fields except the handle are zero'd.
966 * The handle is needed by <code>destroy</code>.
967 * </p>
968 * This method is called before <code>destroy</code>.
969 * <p>
970 * If subclasses reimplement this method, they must
971 * call the <code>super</code> implementation.
972 * </p>
973 *
974 * @see #dispose
975 * @see #destroy
976 */
release()977 protected void release () {
978 if (shellHandle != 0) {
979 if (GTK.GTK4) {
980 GTK4.gtk_window_destroy(shellHandle);
981 } else {
982 GTK3.gtk_widget_destroy(shellHandle);
983 }
984 }
985 shellHandle = 0;
986
987 /* Dispose the default font */
988 if (systemFont != null) systemFont.dispose ();
989 systemFont = null;
990
991 COLOR_BLACK = COLOR_DARK_RED = COLOR_DARK_GREEN = COLOR_DARK_YELLOW = COLOR_DARK_BLUE =
992 COLOR_DARK_MAGENTA = COLOR_DARK_CYAN = COLOR_GRAY = COLOR_DARK_GRAY = COLOR_RED =
993 COLOR_GREEN = COLOR_YELLOW = COLOR_BLUE = COLOR_MAGENTA = COLOR_CYAN = COLOR_WHITE = null;
994
995 if (emptyTab != 0) OS.pango_tab_array_free(emptyTab);
996 emptyTab = 0;
997
998 /* Free the GTK error and warning handler */
999 if (xDisplay != 0) {
1000 for (int i=0; i<handler_ids.length; i++) {
1001 if (handler_ids [i] != 0) {
1002 byte [] log_domain = Converter.wcsToMbcs (log_domains [i], true);
1003 OS.g_log_remove_handler (log_domain, handler_ids [i]);
1004 handler_ids [i] = 0;
1005 }
1006 }
1007 logCallback.dispose (); logCallback = null;
1008 handler_ids = null; log_domains = null;
1009 logProc = 0;
1010 }
1011 /* Dispose the settings callback */
1012 if (gsettingsCallback != null) {
1013 gsettingsCallback.dispose();
1014 gsettingsCallback = null;
1015 }
1016 gsettingsProc = 0;
1017
1018
1019 }
1020
1021 /**
1022 * If the underlying window system supports printing warning messages
1023 * to the console, setting warnings to <code>false</code> prevents these
1024 * messages from being printed. If the argument is <code>true</code> then
1025 * message printing is not blocked.
1026 *
1027 * @param warnings <code>true</code>if warnings should be printed, and <code>false</code> otherwise
1028 *
1029 * @exception SWTException <ul>
1030 * <li>ERROR_DEVICE_DISPOSED - if the receiver has been disposed</li>
1031 * </ul>
1032 */
setWarnings(boolean warnings)1033 public void setWarnings (boolean warnings) {
1034 checkDevice ();
1035 if (warnings) {
1036 if (--warningLevel == 0) {
1037 if (debug) return;
1038 if (logProc != 0) {
1039 for (int i=0; i<handler_ids.length; i++) {
1040 if (handler_ids [i] != 0) {
1041 byte [] log_domain = Converter.wcsToMbcs (log_domains [i], true);
1042 OS.g_log_remove_handler (log_domain, handler_ids [i]);
1043 handler_ids [i] = 0;
1044 }
1045 }
1046 }
1047 }
1048 } else {
1049 if (warningLevel++ == 0) {
1050 if (debug) return;
1051 if (logProc != 0) {
1052 int flags = OS.G_LOG_LEVEL_MASK | OS.G_LOG_FLAG_FATAL | OS.G_LOG_FLAG_RECURSION;
1053 for (int i=0; i<log_domains.length; i++) {
1054 byte [] log_domain = Converter.wcsToMbcs (log_domains [i], true);
1055 handler_ids [i] = OS.g_log_set_handler (log_domain, flags, logProc, 0);
1056 }
1057 }
1058 }
1059 }
1060 }
1061
XErrorProc(long xDisplay, long xErrorEvent)1062 static long XErrorProc (long xDisplay, long xErrorEvent) {
1063 Device device = findDevice (xDisplay);
1064 if (device != null) {
1065 if (device.warningLevel == 0) {
1066 if (DEBUG || device.debug) {
1067 new SWTError ().printStackTrace ();
1068 }
1069 OS.Call (XErrorProc, xDisplay, xErrorEvent);
1070 }
1071 } else {
1072 if (DEBUG) new SWTError ().printStackTrace ();
1073 OS.Call (XErrorProc, xDisplay, xErrorEvent);
1074 }
1075 return 0;
1076 }
1077
XIOErrorProc(long xDisplay)1078 static long XIOErrorProc (long xDisplay) {
1079 Device device = findDevice (xDisplay);
1080 if (device != null) {
1081 if (DEBUG || device.debug) {
1082 new SWTError ().printStackTrace ();
1083 }
1084 } else {
1085 if (DEBUG) new SWTError ().printStackTrace ();
1086 }
1087 OS.Call (XIOErrorProc, xDisplay, 0);
1088 return 0;
1089 }
1090
1091 /**
1092 * Gets the scaling factor from the device and calculates the zoom level.
1093 * @return zoom in percentage
1094 *
1095 * @noreference This method is not intended to be referenced by clients.
1096 * @nooverride This method is not intended to be re-implemented or extended by clients.
1097 * @since 3.105
1098 */
getDeviceZoom()1099 protected int getDeviceZoom() {
1100 /*
1101 * We can hard-code 96 as gdk_screen_get_resolution will always return -1
1102 * if gdk_screen_set_resolution has not been called.
1103 */
1104 int dpi = 96;
1105 if (GTK.GTK_VERSION >= OS.VERSION(3, 22, 0)) {
1106 long display = GDK.gdk_display_get_default();
1107 long monitor;
1108
1109 if (GTK.GTK4) {
1110 long surface = GTK4.gtk_native_get_surface(GTK4.gtk_widget_get_native(shellHandle));
1111 monitor = GDK.gdk_display_get_monitor_at_surface(display, surface);
1112 } else {
1113 monitor = GDK.gdk_display_get_monitor_at_point(display, 0, 0);
1114 }
1115
1116 int scale = GDK.gdk_monitor_get_scale_factor(monitor);
1117 dpi = dpi * scale;
1118 } else {
1119 long screen = GDK.gdk_screen_get_default();
1120 dpi = (int) GDK.gdk_screen_get_resolution (screen);
1121 if (dpi <= 0) dpi = 96; // gdk_screen_get_resolution returns -1 in case of error
1122 int monitor_num = GDK.gdk_screen_get_monitor_at_point (screen, 0, 0);
1123 int scale = GDK.gdk_screen_get_monitor_scale_factor (screen, monitor_num);
1124 dpi = dpi * scale;
1125 }
1126
1127 return DPIUtil.mapDPIToZoom (dpi);
1128 }
1129
1130 }
1131