Add setting to show the number of active recordings in the tray icon
This commit is contained in:
parent
737d1bbb55
commit
83cfee6568
|
@ -1,3 +1,7 @@
|
||||||
|
4.7.5
|
||||||
|
========================
|
||||||
|
* Add setting to show the number of active recordings in the tray
|
||||||
|
|
||||||
4.7.4
|
4.7.4
|
||||||
========================
|
========================
|
||||||
* Fixed AmateurTV recordings
|
* Fixed AmateurTV recordings
|
||||||
|
|
|
@ -1,37 +1,9 @@
|
||||||
package ctbrec.ui;
|
package ctbrec.ui;
|
||||||
|
|
||||||
import static ctbrec.event.Event.Type.*;
|
|
||||||
|
|
||||||
import java.awt.SplashScreen;
|
|
||||||
import java.io.BufferedReader;
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.FileOutputStream;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.io.InputStreamReader;
|
|
||||||
import java.lang.reflect.Type;
|
|
||||||
import java.nio.charset.StandardCharsets;
|
|
||||||
import java.security.InvalidKeyException;
|
|
||||||
import java.security.NoSuchAlgorithmException;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Objects;
|
|
||||||
import java.util.Optional;
|
|
||||||
import java.util.concurrent.Executors;
|
|
||||||
import java.util.concurrent.ScheduledExecutorService;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
|
|
||||||
import ctbrec.sites.secretfriends.SecretFriends;
|
|
||||||
import ctbrec.sites.cherrytv.CherryTv;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import com.google.common.eventbus.Subscribe;
|
import com.google.common.eventbus.Subscribe;
|
||||||
import com.squareup.moshi.JsonAdapter;
|
import com.squareup.moshi.JsonAdapter;
|
||||||
import com.squareup.moshi.Moshi;
|
import com.squareup.moshi.Moshi;
|
||||||
import com.squareup.moshi.Types;
|
import com.squareup.moshi.Types;
|
||||||
|
|
||||||
import ctbrec.Config;
|
import ctbrec.Config;
|
||||||
import ctbrec.Model;
|
import ctbrec.Model;
|
||||||
import ctbrec.StringUtil;
|
import ctbrec.StringUtil;
|
||||||
|
@ -54,11 +26,13 @@ import ctbrec.sites.bonga.BongaCams;
|
||||||
import ctbrec.sites.cam4.Cam4;
|
import ctbrec.sites.cam4.Cam4;
|
||||||
import ctbrec.sites.camsoda.Camsoda;
|
import ctbrec.sites.camsoda.Camsoda;
|
||||||
import ctbrec.sites.chaturbate.Chaturbate;
|
import ctbrec.sites.chaturbate.Chaturbate;
|
||||||
|
import ctbrec.sites.cherrytv.CherryTv;
|
||||||
import ctbrec.sites.fc2live.Fc2Live;
|
import ctbrec.sites.fc2live.Fc2Live;
|
||||||
import ctbrec.sites.flirt4free.Flirt4Free;
|
import ctbrec.sites.flirt4free.Flirt4Free;
|
||||||
import ctbrec.sites.jasmin.LiveJasmin;
|
import ctbrec.sites.jasmin.LiveJasmin;
|
||||||
import ctbrec.sites.manyvids.MVLive;
|
import ctbrec.sites.manyvids.MVLive;
|
||||||
import ctbrec.sites.mfc.MyFreeCams;
|
import ctbrec.sites.mfc.MyFreeCams;
|
||||||
|
import ctbrec.sites.secretfriends.SecretFriends;
|
||||||
import ctbrec.sites.showup.Showup;
|
import ctbrec.sites.showup.Showup;
|
||||||
import ctbrec.sites.streamate.Streamate;
|
import ctbrec.sites.streamate.Streamate;
|
||||||
import ctbrec.sites.stripchat.Stripchat;
|
import ctbrec.sites.stripchat.Stripchat;
|
||||||
|
@ -66,13 +40,7 @@ import ctbrec.sites.xlovecam.XloveCam;
|
||||||
import ctbrec.ui.controls.Dialogs;
|
import ctbrec.ui.controls.Dialogs;
|
||||||
import ctbrec.ui.news.NewsTab;
|
import ctbrec.ui.news.NewsTab;
|
||||||
import ctbrec.ui.settings.SettingsTab;
|
import ctbrec.ui.settings.SettingsTab;
|
||||||
import ctbrec.ui.tabs.DonateTabFx;
|
import ctbrec.ui.tabs.*;
|
||||||
import ctbrec.ui.tabs.HelpTab;
|
|
||||||
import ctbrec.ui.tabs.RecentlyWatchedTab;
|
|
||||||
import ctbrec.ui.tabs.RecordingsTab;
|
|
||||||
import ctbrec.ui.tabs.SiteTab;
|
|
||||||
import ctbrec.ui.tabs.TabSelectionListener;
|
|
||||||
import ctbrec.ui.tabs.UpdateTab;
|
|
||||||
import ctbrec.ui.tabs.logging.LoggingTab;
|
import ctbrec.ui.tabs.logging.LoggingTab;
|
||||||
import ctbrec.ui.tabs.recorded.RecordedTab;
|
import ctbrec.ui.tabs.recorded.RecordedTab;
|
||||||
import javafx.application.Application;
|
import javafx.application.Application;
|
||||||
|
@ -81,11 +49,8 @@ import javafx.application.Platform;
|
||||||
import javafx.beans.value.ObservableValue;
|
import javafx.beans.value.ObservableValue;
|
||||||
import javafx.geometry.Insets;
|
import javafx.geometry.Insets;
|
||||||
import javafx.scene.Scene;
|
import javafx.scene.Scene;
|
||||||
import javafx.scene.control.Alert;
|
|
||||||
import javafx.scene.control.ButtonType;
|
|
||||||
import javafx.scene.control.Label;
|
import javafx.scene.control.Label;
|
||||||
import javafx.scene.control.Tab;
|
import javafx.scene.control.*;
|
||||||
import javafx.scene.control.TabPane;
|
|
||||||
import javafx.scene.control.TabPane.TabDragPolicy;
|
import javafx.scene.control.TabPane.TabDragPolicy;
|
||||||
import javafx.scene.image.Image;
|
import javafx.scene.image.Image;
|
||||||
import javafx.scene.layout.BorderPane;
|
import javafx.scene.layout.BorderPane;
|
||||||
|
@ -94,6 +59,22 @@ import javafx.scene.paint.Color;
|
||||||
import javafx.stage.Stage;
|
import javafx.stage.Stage;
|
||||||
import javafx.stage.WindowEvent;
|
import javafx.stage.WindowEvent;
|
||||||
import okhttp3.Request;
|
import okhttp3.Request;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import java.awt.*;
|
||||||
|
import java.io.*;
|
||||||
|
import java.lang.reflect.Type;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.security.InvalidKeyException;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
import java.util.concurrent.ScheduledExecutorService;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
import static ctbrec.event.Event.Type.*;
|
||||||
|
|
||||||
public class CamrecApplication extends Application {
|
public class CamrecApplication extends Application {
|
||||||
|
|
||||||
|
@ -273,7 +254,7 @@ public class CamrecApplication extends Application {
|
||||||
primaryStage.getScene().getStylesheets().add("/ctbrec/ui/tabs/ThumbCell.css");
|
primaryStage.getScene().getStylesheets().add("/ctbrec/ui/tabs/ThumbCell.css");
|
||||||
primaryStage.getScene().widthProperty().addListener((observable, oldVal, newVal) -> Config.getInstance().getSettings().windowWidth = newVal.intValue());
|
primaryStage.getScene().widthProperty().addListener((observable, oldVal, newVal) -> Config.getInstance().getSettings().windowWidth = newVal.intValue());
|
||||||
primaryStage.getScene().heightProperty()
|
primaryStage.getScene().heightProperty()
|
||||||
.addListener((observable, oldVal, newVal) -> Config.getInstance().getSettings().windowHeight = newVal.intValue());
|
.addListener((observable, oldVal, newVal) -> Config.getInstance().getSettings().windowHeight = newVal.intValue());
|
||||||
primaryStage.setMaximized(Config.getInstance().getSettings().windowMaximized);
|
primaryStage.setMaximized(Config.getInstance().getSettings().windowMaximized);
|
||||||
primaryStage.maximizedProperty().addListener((observable, oldVal, newVal) -> Config.getInstance().getSettings().windowMaximized = newVal);
|
primaryStage.maximizedProperty().addListener((observable, oldVal, newVal) -> Config.getInstance().getSettings().windowMaximized = newVal);
|
||||||
Player.setScene(primaryStage.getScene());
|
Player.setScene(primaryStage.getScene());
|
||||||
|
@ -328,8 +309,8 @@ public class CamrecApplication extends Application {
|
||||||
|
|
||||||
private void suspendTabUpdates() {
|
private void suspendTabUpdates() {
|
||||||
tabPane.getTabs().stream()
|
tabPane.getTabs().stream()
|
||||||
.filter(TabSelectionListener.class::isInstance)
|
.filter(TabSelectionListener.class::isInstance)
|
||||||
.forEach(t -> ((TabSelectionListener)t).deselected());
|
.forEach(t -> ((TabSelectionListener) t).deselected());
|
||||||
}
|
}
|
||||||
|
|
||||||
private javafx.event.EventHandler<WindowEvent> createShutdownHandler() {
|
private javafx.event.EventHandler<WindowEvent> createShutdownHandler() {
|
||||||
|
@ -433,6 +414,7 @@ public class CamrecApplication extends Application {
|
||||||
int modelCount = recorder.getModelCount();
|
int modelCount = recorder.getModelCount();
|
||||||
List<Model> currentlyRecording = recorder.getCurrentlyRecording();
|
List<Model> currentlyRecording = recorder.getCurrentlyRecording();
|
||||||
activeRecordings = currentlyRecording.size();
|
activeRecordings = currentlyRecording.size();
|
||||||
|
DesktopIntegration.updateTrayIcon(activeRecordings);
|
||||||
String windowTitle = getActiveRecordings(activeRecordings, modelCount) + title;
|
String windowTitle = getActiveRecordings(activeRecordings, modelCount) + title;
|
||||||
Platform.runLater(() -> primaryStage.setTitle(windowTitle));
|
Platform.runLater(() -> primaryStage.setTitle(windowTitle));
|
||||||
updateStatus();
|
updateStatus();
|
||||||
|
|
|
@ -1,33 +1,8 @@
|
||||||
package ctbrec.ui;
|
package ctbrec.ui;
|
||||||
|
|
||||||
import java.awt.AWTException;
|
|
||||||
import java.awt.Desktop;
|
|
||||||
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.Config;
|
|
||||||
import ctbrec.OS;
|
import ctbrec.OS;
|
||||||
import ctbrec.event.EventBusHolder;
|
|
||||||
import ctbrec.io.StreamRedirector;
|
import ctbrec.io.StreamRedirector;
|
||||||
import ctbrec.recorder.Recorder;
|
import ctbrec.recorder.Recorder;
|
||||||
import ctbrec.ui.controls.Dialogs;
|
|
||||||
import javafx.application.Platform;
|
import javafx.application.Platform;
|
||||||
import javafx.geometry.Insets;
|
import javafx.geometry.Insets;
|
||||||
import javafx.scene.control.Alert;
|
import javafx.scene.control.Alert;
|
||||||
|
@ -35,17 +10,25 @@ import javafx.scene.control.Label;
|
||||||
import javafx.scene.control.TextField;
|
import javafx.scene.control.TextField;
|
||||||
import javafx.scene.layout.BorderPane;
|
import javafx.scene.layout.BorderPane;
|
||||||
import javafx.stage.Stage;
|
import javafx.stage.Stage;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import java.awt.*;
|
||||||
|
import java.awt.TrayIcon.MessageType;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.URI;
|
||||||
|
|
||||||
public class DesktopIntegration {
|
public class DesktopIntegration {
|
||||||
|
|
||||||
private DesktopIntegration() {}
|
private DesktopIntegration() {
|
||||||
|
}
|
||||||
|
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(DesktopIntegration.class);
|
private static final Logger LOG = LoggerFactory.getLogger(DesktopIntegration.class);
|
||||||
|
|
||||||
private static SystemTray tray;
|
|
||||||
private static TrayIcon trayIcon;
|
|
||||||
private static Recorder recorder;
|
private static Recorder recorder;
|
||||||
private static Stage primaryStage;
|
private static Stage primaryStage;
|
||||||
|
private static TrayIcon trayIcon;
|
||||||
|
|
||||||
public static void open(String uri) {
|
public static void open(String uri) {
|
||||||
try {
|
try {
|
||||||
|
@ -64,7 +47,7 @@ public class DesktopIntegration {
|
||||||
}
|
}
|
||||||
|
|
||||||
// try external helpers
|
// try external helpers
|
||||||
var externalHelpers = new String[] { "kde-open5", "kde-open", "gnome-open", "xdg-open" };
|
var externalHelpers = new String[]{"kde-open5", "kde-open", "gnome-open", "xdg-open"};
|
||||||
var rt = Runtime.getRuntime();
|
var rt = Runtime.getRuntime();
|
||||||
for (String helper : externalHelpers) {
|
for (String helper : externalHelpers) {
|
||||||
try {
|
try {
|
||||||
|
@ -99,7 +82,7 @@ public class DesktopIntegration {
|
||||||
}
|
}
|
||||||
|
|
||||||
// try external helpers
|
// try external helpers
|
||||||
var externalHelpers = new String[] { "kde-open5", "kde-open", "gnome-open", "xdg-open" };
|
var externalHelpers = new String[]{"kde-open5", "kde-open", "gnome-open", "xdg-open"};
|
||||||
var rt = Runtime.getRuntime();
|
var rt = Runtime.getRuntime();
|
||||||
for (String helper : externalHelpers) {
|
for (String helper : externalHelpers) {
|
||||||
try {
|
try {
|
||||||
|
@ -140,7 +123,7 @@ public class DesktopIntegration {
|
||||||
|
|
||||||
private static void notifyLinux(String title, String header, String msg) {
|
private static void notifyLinux(String title, String header, String msg) {
|
||||||
try {
|
try {
|
||||||
Process p = Runtime.getRuntime().exec(new String[] {
|
Process p = Runtime.getRuntime().exec(new String[]{
|
||||||
"notify-send",
|
"notify-send",
|
||||||
"-u", "normal",
|
"-u", "normal",
|
||||||
"-t", "5000",
|
"-t", "5000",
|
||||||
|
@ -185,91 +168,18 @@ public class DesktopIntegration {
|
||||||
|
|
||||||
private static boolean createTrayIcon(Stage stage) {
|
private static boolean createTrayIcon(Stage stage) {
|
||||||
if (SystemTray.isSupported()) {
|
if (SystemTray.isSupported()) {
|
||||||
if (tray == null) {
|
boolean created = false;
|
||||||
String title = CamrecApplication.title;
|
if (trayIcon == null) {
|
||||||
tray = SystemTray.getSystemTray();
|
trayIcon = new ctbrec.ui.TrayIcon(stage, recorder);
|
||||||
var image = Toolkit.getDefaultToolkit().createImage(DesktopIntegration.class.getResource("/icon64.png"));
|
created = trayIcon.createTrayIcon();
|
||||||
|
|
||||||
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;
|
return created;
|
||||||
} else {
|
} else {
|
||||||
LOG.error("SystemTray notifications not supported by this OS");
|
LOG.error("SystemTray notifications not supported by this OS");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static PopupMenu createTrayContextMenu(Stage stage) {
|
|
||||||
var menu = new PopupMenu();
|
|
||||||
var show = new MenuItem("Show");
|
|
||||||
show.addActionListener(evt -> restoreStage(stage));
|
|
||||||
menu.add(show);
|
|
||||||
menu.addSeparator();
|
|
||||||
var 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);
|
|
||||||
var 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();
|
|
||||||
var exit = new MenuItem("Exit");
|
|
||||||
exit.addActionListener(evt -> exit());
|
|
||||||
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.setX(Config.getInstance().getSettings().windowX);
|
|
||||||
stage.setY(Config.getInstance().getSettings().windowY);
|
|
||||||
LOG.debug("Restoring window location {},{}", stage.getX(), stage.getY());
|
|
||||||
stage.setIconified(false);
|
|
||||||
stage.show();
|
|
||||||
stage.toFront();
|
|
||||||
EventBusHolder.BUS.post(Map.of("event", "stage_restored"));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void exit() {
|
|
||||||
EventBusHolder.BUS.post(Map.of("event", "shutdown"));
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void setRecorder(Recorder recorder) {
|
public static void setRecorder(Recorder recorder) {
|
||||||
DesktopIntegration.recorder = recorder;
|
DesktopIntegration.recorder = recorder;
|
||||||
}
|
}
|
||||||
|
@ -277,4 +187,10 @@ public class DesktopIntegration {
|
||||||
public static void setPrimaryStage(Stage primaryStage) {
|
public static void setPrimaryStage(Stage primaryStage) {
|
||||||
DesktopIntegration.primaryStage = primaryStage;
|
DesktopIntegration.primaryStage = primaryStage;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void updateTrayIcon(int activeRecordings) {
|
||||||
|
if (trayIcon != null) {
|
||||||
|
trayIcon.updateActiveRecordings(activeRecordings);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,179 @@
|
||||||
|
package ctbrec.ui;
|
||||||
|
|
||||||
|
import ctbrec.Config;
|
||||||
|
import ctbrec.event.EventBusHolder;
|
||||||
|
import ctbrec.recorder.Recorder;
|
||||||
|
import ctbrec.ui.controls.Dialogs;
|
||||||
|
import javafx.application.Platform;
|
||||||
|
import javafx.stage.Stage;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import javax.imageio.ImageIO;
|
||||||
|
import javax.swing.*;
|
||||||
|
import java.awt.*;
|
||||||
|
import java.awt.event.MouseAdapter;
|
||||||
|
import java.awt.event.MouseEvent;
|
||||||
|
import java.awt.font.LineMetrics;
|
||||||
|
import java.awt.geom.Rectangle2D;
|
||||||
|
import java.awt.image.BufferedImage;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.security.InvalidKeyException;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import static java.awt.Font.BOLD;
|
||||||
|
import static java.awt.RenderingHints.KEY_ANTIALIASING;
|
||||||
|
import static java.awt.RenderingHints.VALUE_ANTIALIAS_ON;
|
||||||
|
|
||||||
|
public class TrayIcon {
|
||||||
|
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(TrayIcon.class);
|
||||||
|
|
||||||
|
private final Stage stage;
|
||||||
|
private final Recorder recorder;
|
||||||
|
private SystemTray tray;
|
||||||
|
private java.awt.TrayIcon awtTrayIcon;
|
||||||
|
private BufferedImage background;
|
||||||
|
|
||||||
|
public TrayIcon(Stage stage, Recorder recorder) {
|
||||||
|
this.stage = stage;
|
||||||
|
this.recorder = recorder;
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean createTrayIcon() {
|
||||||
|
if (SystemTray.isSupported()) {
|
||||||
|
if (tray == null) {
|
||||||
|
String title = CamrecApplication.title;
|
||||||
|
tray = SystemTray.getSystemTray();
|
||||||
|
BufferedImage image = null;
|
||||||
|
try {
|
||||||
|
image = createImage(recorder.getCurrentlyRecording().size());
|
||||||
|
} catch (Exception e) {
|
||||||
|
// fail silently
|
||||||
|
}
|
||||||
|
|
||||||
|
PopupMenu menu = createTrayContextMenu(stage);
|
||||||
|
awtTrayIcon = new java.awt.TrayIcon(image, title, menu);
|
||||||
|
awtTrayIcon.setImageAutoSize(true);
|
||||||
|
awtTrayIcon.setToolTip(title);
|
||||||
|
try {
|
||||||
|
tray.add(awtTrayIcon);
|
||||||
|
} catch (AWTException e) {
|
||||||
|
LOG.error("Couldn't add tray icon", e);
|
||||||
|
}
|
||||||
|
awtTrayIcon.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 PopupMenu createTrayContextMenu(Stage stage) {
|
||||||
|
var menu = new PopupMenu();
|
||||||
|
var show = new MenuItem("Show");
|
||||||
|
show.addActionListener(evt -> restoreStage(stage));
|
||||||
|
menu.add(show);
|
||||||
|
menu.addSeparator();
|
||||||
|
var 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);
|
||||||
|
var 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();
|
||||||
|
var exit = new MenuItem("Exit");
|
||||||
|
exit.addActionListener(evt -> exit());
|
||||||
|
menu.add(exit);
|
||||||
|
return menu;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void restoreStage(Stage stage) {
|
||||||
|
Platform.runLater(() -> {
|
||||||
|
stage.setX(Config.getInstance().getSettings().windowX);
|
||||||
|
stage.setY(Config.getInstance().getSettings().windowY);
|
||||||
|
LOG.debug("Restoring window location {},{}", stage.getX(), stage.getY());
|
||||||
|
stage.setIconified(false);
|
||||||
|
stage.show();
|
||||||
|
stage.toFront();
|
||||||
|
EventBusHolder.BUS.post(Map.of("event", "stage_restored"));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void toggleVisibility(Stage stage) {
|
||||||
|
if (stage.isShowing()) {
|
||||||
|
Platform.runLater(stage::hide);
|
||||||
|
} else {
|
||||||
|
restoreStage(stage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void exit() {
|
||||||
|
EventBusHolder.BUS.post(Map.of("event", "shutdown"));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void displayMessage(String header, String msg, java.awt.TrayIcon.MessageType info) {
|
||||||
|
createTrayIcon();
|
||||||
|
awtTrayIcon.displayMessage(header, msg, info);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void updateActiveRecordings(int activeRecordings) {
|
||||||
|
try {
|
||||||
|
createTrayIcon();
|
||||||
|
awtTrayIcon.setImage(createImage(activeRecordings));
|
||||||
|
} catch (IOException e) {
|
||||||
|
LOG.error("Couldn't update tray icon image", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private BufferedImage createImage(int number) throws IOException {
|
||||||
|
if (this.background == null) {
|
||||||
|
this.background = ImageIO.read(TrayIcon.class.getResource("/icon64.png"));
|
||||||
|
}
|
||||||
|
|
||||||
|
BufferedImage image = new BufferedImage(64, 64, BufferedImage.TYPE_INT_ARGB);
|
||||||
|
Graphics2D g2 = (Graphics2D) image.getGraphics();
|
||||||
|
g2.drawImage(background, 0, 0, (img, infoflags, x, y, width, height) -> false);
|
||||||
|
if (number > 0 && Config.getInstance().getSettings().showActiveRecordingsInTray) {
|
||||||
|
g2.setColor(Color.decode("#dc4444"));
|
||||||
|
g2.fillOval(0, 0, 64, 64);
|
||||||
|
|
||||||
|
String text = String.valueOf(number);
|
||||||
|
String fontFamily = Config.getInstance().getSettings().showActiveRecordingsInTrayFont;
|
||||||
|
int fontSize = Config.getInstance().getSettings().showActiveRecordingsInTrayFontSize;
|
||||||
|
g2.setRenderingHint(KEY_ANTIALIASING, VALUE_ANTIALIAS_ON);
|
||||||
|
Font font = new Font(fontFamily, BOLD, fontSize);
|
||||||
|
g2.setFont(font);
|
||||||
|
FontMetrics fontMetrics = g2.getFontMetrics(font);
|
||||||
|
LineMetrics lineMetrics = fontMetrics.getLineMetrics(text, g2);
|
||||||
|
Rectangle2D stringBounds = fontMetrics.getStringBounds(text, g2);
|
||||||
|
g2.setColor(Color.decode(Config.getInstance().getSettings().showActiveRecordingsInTrayColor));
|
||||||
|
int x = (int) (image.getWidth() - stringBounds.getWidth()) / 2;
|
||||||
|
int y = (int) (((image.getHeight() - lineMetrics.getAscent()) / 2) - 8 - stringBounds.getY());
|
||||||
|
g2.drawString(text, x, y);
|
||||||
|
g2.dispose();
|
||||||
|
}
|
||||||
|
return image;
|
||||||
|
}
|
||||||
|
}
|
|
@ -103,6 +103,7 @@ public class SettingsTab extends Tab implements TabSelectionListener {
|
||||||
private SimpleBooleanProperty recordedModelsPerSite;
|
private SimpleBooleanProperty recordedModelsPerSite;
|
||||||
private SimpleBooleanProperty requireAuthentication;
|
private SimpleBooleanProperty requireAuthentication;
|
||||||
private SimpleBooleanProperty totalModelCountInTitle;
|
private SimpleBooleanProperty totalModelCountInTitle;
|
||||||
|
private SimpleBooleanProperty showActiveRecordingsInTray;
|
||||||
private SimpleBooleanProperty transportLayerSecurity;
|
private SimpleBooleanProperty transportLayerSecurity;
|
||||||
private SimpleBooleanProperty fastScrollSpeed;
|
private SimpleBooleanProperty fastScrollSpeed;
|
||||||
private SimpleBooleanProperty useHlsdl;
|
private SimpleBooleanProperty useHlsdl;
|
||||||
|
@ -174,6 +175,7 @@ public class SettingsTab extends Tab implements TabSelectionListener {
|
||||||
requireAuthentication = new SimpleBooleanProperty(null, "requireAuthentication", settings.requireAuthentication);
|
requireAuthentication = new SimpleBooleanProperty(null, "requireAuthentication", settings.requireAuthentication);
|
||||||
requireAuthentication.addListener(this::requireAuthenticationChanged);
|
requireAuthentication.addListener(this::requireAuthenticationChanged);
|
||||||
totalModelCountInTitle = new SimpleBooleanProperty(null, "totalModelCountInTitle", settings.totalModelCountInTitle);
|
totalModelCountInTitle = new SimpleBooleanProperty(null, "totalModelCountInTitle", settings.totalModelCountInTitle);
|
||||||
|
showActiveRecordingsInTray = new SimpleBooleanProperty(null, "showActiveRecordingsInTray", settings.showActiveRecordingsInTray);
|
||||||
transportLayerSecurity = new SimpleBooleanProperty(null, "transportLayerSecurity", settings.transportLayerSecurity);
|
transportLayerSecurity = new SimpleBooleanProperty(null, "transportLayerSecurity", settings.transportLayerSecurity);
|
||||||
recordLocal = new ExclusiveSelectionProperty(null, "localRecording", settings.localRecording, "Local", "Remote");
|
recordLocal = new ExclusiveSelectionProperty(null, "localRecording", settings.localRecording, "Local", "Remote");
|
||||||
postProcessingThreads = new SimpleIntegerProperty(null, "postProcessingThreads", settings.postProcessingThreads);
|
postProcessingThreads = new SimpleIntegerProperty(null, "postProcessingThreads", settings.postProcessingThreads);
|
||||||
|
@ -200,7 +202,7 @@ public class SettingsTab extends Tab implements TabSelectionListener {
|
||||||
List<Category> siteCategories = new ArrayList<>();
|
List<Category> siteCategories = new ArrayList<>();
|
||||||
for (Site site : sites) {
|
for (Site site : sites) {
|
||||||
ofNullable(SiteUiFactory.getUi(site)).map(SiteUI::getConfigUI).map(ConfigUI::createConfigPanel)
|
ofNullable(SiteUiFactory.getUi(site)).map(SiteUI::getConfigUI).map(ConfigUI::createConfigPanel)
|
||||||
.ifPresent(configPanel -> siteCategories.add(Category.of(site.getName(), configPanel)));
|
.ifPresent(configPanel -> siteCategories.add(Category.of(site.getName(), configPanel)));
|
||||||
}
|
}
|
||||||
|
|
||||||
var storage = new CtbrecPreferencesStorage(config);
|
var storage = new CtbrecPreferencesStorage(config);
|
||||||
|
@ -234,6 +236,7 @@ public class SettingsTab extends Tab implements TabSelectionListener {
|
||||||
Setting.of("Date format (empty = system default)", dateTimeFormat, DATE_FORMATTER_TOOLTIP).needsRestart(),
|
Setting.of("Date format (empty = system default)", dateTimeFormat, DATE_FORMATTER_TOOLTIP).needsRestart(),
|
||||||
Setting.of("Display stream resolution in overview", determineResolution),
|
Setting.of("Display stream resolution in overview", determineResolution),
|
||||||
Setting.of("Total model count in title", totalModelCountInTitle, "Show the total number of models in the title bar"),
|
Setting.of("Total model count in title", totalModelCountInTitle, "Show the total number of models in the title bar"),
|
||||||
|
Setting.of("Show active recordings counter in tray", showActiveRecordingsInTray, "Show the number of running recorings in the tray icon"),
|
||||||
Setting.of("Show grid lines in tables", showGridLinesInTables, "Show grid lines in tables").needsRestart(),
|
Setting.of("Show grid lines in tables", showGridLinesInTables, "Show grid lines in tables").needsRestart(),
|
||||||
Setting.of("Fast scroll speed", fastScrollSpeed, "Makes the thumbnail overviews scroll faster with the mouse wheel").needsRestart())),
|
Setting.of("Fast scroll speed", fastScrollSpeed, "Makes the thumbnail overviews scroll faster with the mouse wheel").needsRestart())),
|
||||||
Category.of("Recorder",
|
Category.of("Recorder",
|
||||||
|
@ -255,7 +258,7 @@ public class SettingsTab extends Tab implements TabSelectionListener {
|
||||||
Group.of("Timeout",
|
Group.of("Timeout",
|
||||||
Setting.of("Don't record from", timeoutRecordingStartingAt),
|
Setting.of("Don't record from", timeoutRecordingStartingAt),
|
||||||
Setting.of("Until", timeoutRecordingEndingAt)
|
Setting.of("Until", timeoutRecordingEndingAt)
|
||||||
),
|
),
|
||||||
Group.of("Location",
|
Group.of("Location",
|
||||||
Setting.of("Record Location", recordLocal).needsRestart(),
|
Setting.of("Record Location", recordLocal).needsRestart(),
|
||||||
Setting.of("Server", server),
|
Setting.of("Server", server),
|
||||||
|
@ -302,7 +305,7 @@ public class SettingsTab extends Tab implements TabSelectionListener {
|
||||||
restartNotification.setOpacity(0);
|
restartNotification.setOpacity(0);
|
||||||
restartNotification.setStyle("-fx-font-size: 28; -fx-padding: .3em");
|
restartNotification.setStyle("-fx-font-size: 28; -fx-padding: .3em");
|
||||||
restartNotification
|
restartNotification
|
||||||
.setBorder(new Border(new BorderStroke(Color.web(settings.colorAccent), BorderStrokeStyle.SOLID, new CornerRadii(5), new BorderWidths(2))));
|
.setBorder(new Border(new BorderStroke(Color.web(settings.colorAccent), BorderStrokeStyle.SOLID, new CornerRadii(5), new BorderWidths(2))));
|
||||||
restartNotification.setBackground(new Background(new BackgroundFill(Color.web(settings.colorBase), new CornerRadii(5), Insets.EMPTY)));
|
restartNotification.setBackground(new Background(new BackgroundFill(Color.web(settings.colorBase), new CornerRadii(5), Insets.EMPTY)));
|
||||||
stackPane.getChildren().add(restartNotification);
|
stackPane.getChildren().add(restartNotification);
|
||||||
StackPane.setAlignment(restartNotification, Pos.TOP_RIGHT);
|
StackPane.setAlignment(restartNotification, Pos.TOP_RIGHT);
|
||||||
|
|
|
@ -1,18 +1,12 @@
|
||||||
package ctbrec;
|
package ctbrec;
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.time.LocalTime;
|
|
||||||
import java.time.format.DateTimeFormatter;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
import ctbrec.event.EventHandlerConfiguration;
|
import ctbrec.event.EventHandlerConfiguration;
|
||||||
import ctbrec.recorder.postprocessing.PostProcessor;
|
import ctbrec.recorder.postprocessing.PostProcessor;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.time.LocalTime;
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
public class Settings {
|
public class Settings {
|
||||||
|
|
||||||
public enum DirectoryStructure {
|
public enum DirectoryStructure {
|
||||||
|
@ -21,6 +15,7 @@ public class Settings {
|
||||||
ONE_PER_RECORDING("one directory for each recording");
|
ONE_PER_RECORDING("one directory for each recording");
|
||||||
|
|
||||||
private final String description;
|
private final String description;
|
||||||
|
|
||||||
DirectoryStructure(String description) {
|
DirectoryStructure(String description) {
|
||||||
this.description = description;
|
this.description = description;
|
||||||
}
|
}
|
||||||
|
@ -169,6 +164,10 @@ public class Settings {
|
||||||
public int segmentErrorMeasurePeriodInSecs = 20;
|
public int segmentErrorMeasurePeriodInSecs = 20;
|
||||||
public int segmentErrorThresholdToStopRecording = 5;
|
public int segmentErrorThresholdToStopRecording = 5;
|
||||||
public String servletContext = "";
|
public String servletContext = "";
|
||||||
|
public boolean showActiveRecordingsInTray = true;
|
||||||
|
public String showActiveRecordingsInTrayColor = "#FFFFFF";
|
||||||
|
public String showActiveRecordingsInTrayFont = "SansSerif";
|
||||||
|
public int showActiveRecordingsInTrayFontSize = 48;
|
||||||
public boolean showGridLinesInTables = true;
|
public boolean showGridLinesInTables = true;
|
||||||
public boolean showPlayerStarting = false;
|
public boolean showPlayerStarting = false;
|
||||||
public String showupUsername = "";
|
public String showupUsername = "";
|
||||||
|
|
Loading…
Reference in New Issue