package ctbrec.ui; import java.awt.AWTException; import java.awt.Desktop; import java.awt.Image; import java.awt.MenuItem; import java.awt.PopupMenu; import java.awt.SystemTray; import java.awt.Toolkit; import java.awt.TrayIcon; import java.awt.TrayIcon.MessageType; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.io.File; import java.io.IOException; import java.net.URI; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; import java.util.Map; import javax.swing.SwingUtilities; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import ctbrec.OS; import ctbrec.event.EventBusHolder; import ctbrec.io.StreamRedirector; import ctbrec.recorder.Recorder; import ctbrec.ui.controls.Dialogs; import javafx.application.Platform; import javafx.geometry.Insets; import javafx.scene.control.Alert; import javafx.scene.control.Label; import javafx.scene.control.TextField; import javafx.scene.layout.BorderPane; import javafx.stage.Stage; public class DesktopIntegration { private DesktopIntegration() {} private static final Logger LOG = LoggerFactory.getLogger(DesktopIntegration.class); private static SystemTray tray; private static TrayIcon trayIcon; private static Recorder recorder; private static Stage primaryStage; public static void open(String uri) { try { CamrecApplication.hostServices.showDocument(uri); return; } catch (Exception e) { LOG.debug("Couldn't open URL with host services {}", uri); } // opening with HostServices failed, now try Desktop try { Desktop.getDesktop().browse(new URI(uri)); return; } catch (Exception e) { LOG.debug("Couldn't open URL with Desktop {}", uri); } // try external helpers String[] externalHelpers = { "kde-open5", "kde-open", "gnome-open", "xdg-open" }; Runtime rt = Runtime.getRuntime(); for (String helper : externalHelpers) { try { rt.exec(helper + " " + uri); return; } catch (IOException e) { LOG.debug("Couldn't open URL with {} {}", helper, uri); } } // all attempts failed, show a dialog with URL at least Alert info = new AutosizeAlert(Alert.AlertType.ERROR); info.setTitle("Open URL"); info.setContentText("Couldn't open URL"); BorderPane pane = new BorderPane(); pane.setTop(new Label()); TextField urlField = new TextField(uri); urlField.setPadding(new Insets(10)); urlField.setEditable(false); pane.setCenter(urlField); info.getDialogPane().setExpandableContent(pane); info.getDialogPane().setExpanded(true); info.show(); } public static void open(File f) { try { Desktop.getDesktop().open(f); return; } catch (Exception e) { LOG.debug("Couldn't open file with Desktop {}", f); } // try external helpers String[] externalHelpers = { "kde-open5", "kde-open", "gnome-open", "xdg-open" }; Runtime rt = Runtime.getRuntime(); for (String helper : externalHelpers) { try { rt.exec(helper + " " + f.getAbsolutePath()); return; } catch (IOException e) { LOG.debug("Couldn't open file with {} {}", helper, f); } } // all attempts failed, show a dialog with path at least Alert info = new AutosizeAlert(Alert.AlertType.ERROR); info.setTitle("Open file"); info.setContentText("Couldn't open file"); BorderPane pane = new BorderPane(); pane.setTop(new Label()); TextField urlField = new TextField(f.toString()); urlField.setPadding(new Insets(10)); urlField.setEditable(false); pane.setCenter(urlField); info.getDialogPane().setExpandableContent(pane); info.getDialogPane().setExpanded(true); info.show(); } public static void notification(String title, String header, String msg) { if (OS.getOsType() == OS.TYPE.LINUX) { notifyLinux(title, header, msg); } else if (OS.getOsType() == OS.TYPE.WINDOWS) { notifyWindows(title, header, msg); } else if (OS.getOsType() == OS.TYPE.MAC) { notifyMac(title, header, msg); } else { // unknown system, try systemtray notification anyways notifySystemTray(title, header, msg); } } private static void notifyLinux(String title, String header, String msg) { try { Process p = Runtime.getRuntime().exec(new String[] { "notify-send", "-u", "normal", "-t", "5000", "-a", title, header, msg.replace("-", "\\\\-").replace("\\s", "\\\\ "), "--icon=dialog-information" }); new Thread(new StreamRedirector(p.getInputStream(), System.out)).start(); // NOSONAR new Thread(new StreamRedirector(p.getErrorStream(), System.err)).start(); // NOSONAR } catch (NullPointerException e) { // can happen at start, ignore } catch (IOException e1) { LOG.error("Notification failed", e1); } } private static void notifyWindows(String title, String header, String msg) { notifySystemTray(title, header, msg); } private static void notifyMac(String title, String header, String msg) { notifySystemTray(title, header, msg); } private static synchronized void notifySystemTray(String title, String header, String msg) { if (SystemTray.isSupported()) { createTrayIcon(primaryStage); trayIcon.displayMessage(header, msg, MessageType.INFO); } else { LOG.error("SystemTray notifications not supported by this OS"); } } public static void minimizeToTray(Stage primaryStage) { Platform.setImplicitExit(false); boolean supported = createTrayIcon(primaryStage); if (supported) { primaryStage.hide(); } } private static boolean createTrayIcon(Stage stage) { if (SystemTray.isSupported()) { if (tray == null) { String title = CamrecApplication.title; tray = SystemTray.getSystemTray(); Image image = Toolkit.getDefaultToolkit().createImage(DesktopIntegration.class.getResource("/icon64.png")); PopupMenu menu = createTrayContextMenu(stage); trayIcon = new TrayIcon(image, title, menu); trayIcon.setImageAutoSize(true); trayIcon.setToolTip(title); try { tray.add(trayIcon); } catch (AWTException e) { LOG.error("Couldn't add tray icon", e); } trayIcon.addMouseListener(new MouseAdapter() { @Override public void mousePressed(MouseEvent e) { if (SwingUtilities.isLeftMouseButton(e)) { toggleVisibility(stage); } } }); } return true; } else { LOG.error("SystemTray notifications not supported by this OS"); return false; } } private static PopupMenu createTrayContextMenu(Stage stage) { PopupMenu menu = new PopupMenu(); MenuItem show = new MenuItem("Show"); show.addActionListener(evt -> restoreStage(stage)); menu.add(show); menu.addSeparator(); MenuItem pauseRecording = new MenuItem("Pause recording"); pauseRecording.addActionListener(evt -> { try { recorder.pause(); } catch (InvalidKeyException | NoSuchAlgorithmException | IOException e) { Dialogs.showError(stage.getScene(), "Pausing recording", "Pausing of the recorder failed", e); } }); menu.add(pauseRecording); MenuItem resumeRecording = new MenuItem("Resume recording"); resumeRecording.addActionListener(evt -> { try { recorder.resume(); } catch (InvalidKeyException | NoSuchAlgorithmException | IOException e) { Dialogs.showError(stage.getScene(), "Resuming recording", "Resuming of the recorder failed", e); } }); menu.add(resumeRecording); menu.addSeparator(); MenuItem exit = new MenuItem("Exit"); exit.addActionListener(evt -> exit(stage)); menu.add(exit); return menu; } private static void toggleVisibility(Stage stage) { if (stage.isShowing()) { Platform.runLater(stage::hide); } else { restoreStage(stage); } } private static void restoreStage(Stage stage) { Platform.runLater(() -> { stage.setIconified(false); stage.show(); stage.toFront(); }); } private static void exit(Stage stage) { EventBusHolder.BUS.post(Map.of("event", "shutdown")); } public static void setRecorder(Recorder recorder) { DesktopIntegration.recorder = recorder; } public static void setPrimaryStage(Stage primaryStage) { DesktopIntegration.primaryStage = primaryStage; } }