View Javadoc
1   /*
2    * DavMail POP/IMAP/SMTP/CalDav/LDAP Exchange Gateway
3    * Copyright (C) 2009  Mickael Guessant
4    *
5    * This program is free software; you can redistribute it and/or
6    * modify it under the terms of the GNU General Public License
7    * as published by the Free Software Foundation; either version 2
8    * of the License, or (at your option) any later version.
9    *
10   * This program is distributed in the hope that it will be useful,
11   * but WITHOUT ANY WARRANTY; without even the implied warranty of
12   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13   * GNU General Public License for more details.
14   *
15   * You should have received a copy of the GNU General Public License
16   * along with this program; if not, write to the Free Software
17   * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
18   */
19  package davmail.ui.tray;
20  
21  import davmail.BundleMessage;
22  import davmail.Settings;
23  import davmail.exchange.NetworkDownException;
24  import org.apache.log4j.Level;
25  import org.apache.log4j.Logger;
26  
27  import javax.imageio.ImageIO;
28  import java.awt.*;
29  import java.awt.color.ColorSpace;
30  import java.awt.image.BufferedImage;
31  import java.awt.image.ColorConvertOp;
32  import java.io.IOException;
33  import java.net.URL;
34  
35  
36  /**
37   * Tray icon handler
38   */
39  public final class DavGatewayTray {
40      private static final Logger LOGGER = Logger.getLogger("davmail");
41      private static final long ICON_SWITCH_MINIMUM_DELAY = 250;
42      private static long lastIconSwitch;
43  
44      private DavGatewayTray() {
45      }
46  
47      static DavGatewayTrayInterface davGatewayTray;
48  
49      /**
50       * Return AWT Image icon for frame title.
51       *
52       * @return frame icon
53       */
54      public static java.util.List<Image> getFrameIcons() {
55          java.util.List<Image> icons = null;
56          if (davGatewayTray != null) {
57              icons = davGatewayTray.getFrameIcons();
58          }
59          return icons;
60      }
61  
62      /**
63       * Switch tray icon between active and standby icon.
64       */
65      public static void switchIcon() {
66          if (davGatewayTray != null && !Settings.getBooleanProperty("davmail.disableTrayActivitySwitch")) {
67              if (System.currentTimeMillis() - lastIconSwitch > ICON_SWITCH_MINIMUM_DELAY) {
68                  davGatewayTray.switchIcon();
69                  lastIconSwitch = System.currentTimeMillis();
70              }
71          }
72      }
73  
74      /**
75       * Set tray icon to inactive (network down)
76       */
77      public static void resetIcon() {
78          if (davGatewayTray != null && isActive()) {
79              davGatewayTray.resetIcon();
80          }
81      }
82  
83      /**
84       * Check if current tray status is inactive (network down).
85       *
86       * @return true if inactive
87       */
88      public static boolean isActive() {
89          return davGatewayTray == null || davGatewayTray.isActive();
90      }
91  
92      /**
93       * Log and display balloon message according to log level.
94       *
95       * @param message text message
96       * @param level   log level
97       */
98      private static void displayMessage(BundleMessage message, Level level) {
99          LOGGER.log(level, message.formatLog());
100         if (davGatewayTray != null && !Settings.getBooleanProperty("davmail.disableGuiNotifications")) {
101             davGatewayTray.displayMessage(message.format(), level);
102         }
103     }
104 
105     /**
106      * Log and display balloon message and exception according to log level.
107      *
108      * @param message text message
109      * @param e       exception
110      * @param level   log level
111      */
112     private static void displayMessage(BundleMessage message, Exception e, Level level) {
113         if (e instanceof NetworkDownException) {
114             LOGGER.log(level, BundleMessage.getExceptionLogMessage(message, e));
115         } else {
116             LOGGER.log(level, BundleMessage.getExceptionLogMessage(message, e), e);
117         }
118         if (davGatewayTray != null && !Settings.getBooleanProperty("davmail.disableGuiNotifications")
119                 && (!(e instanceof NetworkDownException))) {
120             davGatewayTray.displayMessage(BundleMessage.getExceptionMessage(message, e), level);
121         }
122         if (davGatewayTray != null && e instanceof NetworkDownException) {
123             davGatewayTray.inactiveIcon();
124         }
125     }
126 
127     /**
128      * Log message at level DEBUG.
129      *
130      * @param message bundle message
131      */
132     public static void debug(BundleMessage message) {
133         displayMessage(message, Level.DEBUG);
134     }
135 
136     /**
137      * Log message at level INFO.
138      *
139      * @param message bundle message
140      */
141     public static void info(BundleMessage message) {
142         displayMessage(message, Level.INFO);
143     }
144 
145     /**
146      * Log message at level WARN.
147      *
148      * @param message bundle message
149      */
150     public static void warn(BundleMessage message) {
151         displayMessage(message, Level.WARN);
152     }
153 
154     /**
155      * Log exception at level WARN.
156      *
157      * @param e exception
158      */
159     public static void warn(Exception e) {
160         displayMessage(null, e, Level.WARN);
161     }
162 
163     /**
164      * Log message at level ERROR.
165      *
166      * @param message bundle message
167      */
168     public static void error(BundleMessage message) {
169         displayMessage(message, Level.ERROR);
170     }
171 
172     /**
173      * Log exception at level WARN for NetworkDownException,
174      * ERROR for other exceptions.
175      *
176      * @param e exception
177      */
178     public static void log(Exception e) {
179         // only warn on network down
180         if (e instanceof NetworkDownException) {
181             warn(e);
182         } else {
183             error(e);
184         }
185     }
186 
187     /**
188      * Log exception at level ERROR.
189      *
190      * @param e exception
191      */
192     public static void error(Exception e) {
193         displayMessage(null, e, Level.ERROR);
194     }
195 
196     /**
197      * Log message and exception at level DEBUG.
198      *
199      * @param message bundle message
200      * @param e       exception
201      */
202     public static void debug(BundleMessage message, Exception e) {
203         displayMessage(message, e, Level.DEBUG);
204     }
205 
206     /**
207      * Log message and exception at level WARN.
208      *
209      * @param message bundle message
210      * @param e       exception
211      */
212     public static void warn(BundleMessage message, Exception e) {
213         displayMessage(message, e, Level.WARN);
214     }
215 
216     /**
217      * Log message and exception at level ERROR.
218      *
219      * @param message bundle message
220      * @param e       exception
221      */
222     public static void error(BundleMessage message, Exception e) {
223         displayMessage(message, e, Level.ERROR);
224     }
225 
226     /**
227      * Create tray icon and register frame listeners.
228      */
229     public static void init() {
230         String currentDesktop = System.getenv("XDG_CURRENT_DESKTOP");
231         String javaVersion = System.getProperty("java.version");
232         String arch = System.getProperty("sun.arch.data.model");
233         LOGGER.debug("OS Name: " + System.getProperty("os.name") +
234                 " Java version: " + javaVersion + ((arch != null) ? ' ' + arch : "") +
235                 " System tray " + (SystemTray.isSupported() ? "" : "not ") + "supported " +
236                 ((currentDesktop == null) ? "" : "Current Desktop: " + currentDesktop)
237         );
238 
239         if (Settings.isLinux()) {
240             // enable anti aliasing on linux
241             System.setProperty("awt.useSystemAAFontSettings", "on");
242             System.setProperty("swing.aatext", "true");
243         }
244 
245         if (!Settings.getBooleanProperty("davmail.server")) {
246             // disable tray by default on linux
247             if (Settings.getBooleanProperty("davmail.enableTray", !Settings.isLinux())) {
248                 if ("Unity".equals(currentDesktop)) {
249                     LOGGER.info("Detected Unity desktop, please follow instructions at " +
250                             "https://davmail.sourceforge.net/linuxsetup.html to restore normal systray " +
251                             "or run DavMail in server mode");
252                 } else if (currentDesktop != null && currentDesktop.contains("GNOME")) {
253                     LOGGER.info("Detected Gnome desktop, please follow instructions at " +
254                             "https://davmail.sourceforge.net/linuxsetup.html or " +
255                             "https://extensions.gnome.org/extension/1503/tray-icons/ " +
256                             "to restore normal systray or run DavMail in server mode");
257                 }
258                 // first try to load SWT before with Java AWT
259                 boolean isSWTAvailable = Settings.isSWTAvailable();
260                 if (isSWTAvailable) {
261                     try {
262                         // SWT available, create tray
263                         davGatewayTray = new SwtGatewayTray();
264                         davGatewayTray.init();
265                     } catch (Throwable e) {
266                         DavGatewayTray.info(new BundleMessage("LOG_SWT_NOT_AVAILABLE"));
267                         davGatewayTray = null;
268                     }
269                 }
270                 // try java6 tray support, except on Linux
271                 if (davGatewayTray == null /*&& !isLinux()*/) {
272                     try {
273                         if (SystemTray.isSupported()) {
274                             if (isOSX()) {
275                                 davGatewayTray = new OSXAwtGatewayTray();
276                             } else {
277                                 davGatewayTray = new AwtGatewayTray();
278                             }
279                             davGatewayTray.init();
280                         }
281                     } catch (NoClassDefFoundError e) {
282                         DavGatewayTray.info(new BundleMessage("LOG_SYSTEM_TRAY_NOT_AVAILABLE"));
283                     }
284                 }
285             }
286 
287             if (davGatewayTray == null) {
288                 if (isOSX()) {
289                     // MacOS
290                     davGatewayTray = new OSXFrameGatewayTray();
291                 } else {
292                     davGatewayTray = new FrameGatewayTray();
293                 }
294                 davGatewayTray.init();
295             }
296         }
297     }
298 
299     /**
300      * Test if running on OSX
301      *
302      * @return true on Mac OS X
303      */
304     public static boolean isOSX() {
305         return System.getProperty("os.name").toLowerCase().startsWith("mac os x");
306     }
307 
308     /**
309      * Test if running on Windows
310      *
311      * @return true on Windows
312      */
313     public static boolean isWindows() {
314         return System.getProperty("os.name").toLowerCase().startsWith("windows");
315     }
316 
317     /**
318      * Test if running on Linux
319      *
320      * @return true on Linux
321      */
322     public static boolean isLinux() {
323         return System.getProperty("os.name").toLowerCase().startsWith("linux");
324     }
325 
326     /**
327      * Load image with current class loader.
328      *
329      * @param fileName image resource file name
330      * @return image
331      */
332     public static BufferedImage loadImage(String fileName) {
333         BufferedImage result = null;
334         try {
335             ClassLoader classloader = DavGatewayTray.class.getClassLoader();
336             URL imageUrl = classloader.getResource(fileName);
337             if (imageUrl == null) {
338                 throw new IOException("Missing resource: " + fileName);
339             }
340             result = ImageIO.read(imageUrl);
341         } catch (IOException e) {
342             DavGatewayTray.warn(new BundleMessage("LOG_UNABLE_TO_LOAD_IMAGE"), e);
343         }
344         return result;
345     }
346 
347     public static BufferedImage convertGrayscale(BufferedImage colorImage) {
348         BufferedImage grayImage = new BufferedImage(colorImage.getWidth(), colorImage.getHeight(), BufferedImage.TYPE_INT_ARGB);
349 
350         ColorConvertOp op = new ColorConvertOp(ColorSpace.getInstance(ColorSpace.CS_GRAY), null);
351         op.filter(colorImage, grayImage);
352 
353         return grayImage;
354     }
355 
356     public static BufferedImage scaleImage(BufferedImage sourceImage, int targetSize) {
357         Image scaledImage = sourceImage.getScaledInstance(targetSize, targetSize, Image.SCALE_AREA_AVERAGING);
358 
359         BufferedImage targetImage = new BufferedImage(targetSize, targetSize, BufferedImage.TYPE_INT_ARGB);
360         targetImage.getGraphics().drawImage(scaledImage, 0, 0, null);
361         return targetImage;
362     }
363 
364     public static BufferedImage adjustTrayIcon(BufferedImage image) {
365         if (Settings.getBooleanProperty("davmail.trayGrayscale", false)) {
366             image = convertGrayscale(image);
367         }
368         Color backgroundColor = null;
369         String backgroundColorString = Settings.getProperty("davmail.trayBackgroundColor");
370 
371         String xdgCurrentDesktop = System.getenv("XDG_CURRENT_DESKTOP");
372 
373         boolean isKDE = "KDE".equals(xdgCurrentDesktop);
374         boolean isXFCE = "XFCE".equals(xdgCurrentDesktop);
375         boolean isUnity = "Unity".equals(xdgCurrentDesktop);
376         boolean isCinnamon = "X-Cinnamon".equals(xdgCurrentDesktop);
377         boolean isGnome = xdgCurrentDesktop != null && xdgCurrentDesktop.contains("GNOME");
378 
379         if (backgroundColorString == null || backgroundColorString.isEmpty()) {
380             // define color for default theme
381             if (isKDE) {
382                 backgroundColorString = "#DDF6E8";
383             }
384             if (isUnity) {
385                 backgroundColorString = "#4D4B45";
386             }
387             if (isXFCE) {
388                 backgroundColorString = "#E8E8E7";
389             }
390             if (isCinnamon) {
391                 backgroundColorString = "#2E2E2E";
392             }
393             if (isGnome) {
394                 backgroundColorString = "#000000";
395             }
396         }
397 
398         int imageType = BufferedImage.TYPE_INT_ARGB;
399         if (backgroundColorString != null && backgroundColorString.length() == 7
400                 && backgroundColorString.startsWith("#")) {
401             int red = Integer.parseInt(backgroundColorString.substring(1, 3), 16);
402             int green = Integer.parseInt(backgroundColorString.substring(3, 5), 16);
403             int blue = Integer.parseInt(backgroundColorString.substring(5, 7), 16);
404             backgroundColor = new Color(red, green, blue);
405             imageType = BufferedImage.TYPE_INT_RGB;
406         }
407 
408         if (backgroundColor != null || isKDE || isUnity || isXFCE || isGnome) {
409             int width = image.getWidth();
410             int height = image.getHeight();
411             int x = 0;
412             int y = 0;
413             if (isKDE || isXFCE) {
414                 width = 22;
415                 height = 22;
416                 x = 3;
417                 y = 3;
418             } else if (isUnity) {
419                 width = 22;
420                 height = 24;
421                 x = 4;
422                 y = 4;
423             } else if (isCinnamon || isGnome) {
424                 width = 24;
425                 height = 24;
426                 x = 4;
427                 y = 4;
428             }
429             BufferedImage bufferedImage = new BufferedImage(width, height, imageType);
430             Graphics2D graphics = bufferedImage.createGraphics();
431             graphics.setColor(backgroundColor);
432             graphics.fillRect(0, 0, width, height);
433             graphics.drawImage(image, x, y, null);
434             graphics.dispose();
435             return bufferedImage;
436         } else {
437             return image;
438         }
439     }
440 
441 
442     /**
443      * Dispose application tray icon
444      */
445     public static void dispose() {
446         if (davGatewayTray != null) {
447             davGatewayTray.dispose();
448         }
449     }
450 
451 }