diff --git a/CHANGELOG.md b/CHANGELOG.md index e30c4f91..6c30f51f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +4.7.5 +======================== +* Add setting to show the number of active recordings in the tray + 4.7.4 ======================== * Fixed AmateurTV recordings diff --git a/client/src/main/java/ctbrec/ui/CamrecApplication.java b/client/src/main/java/ctbrec/ui/CamrecApplication.java index e8467040..95a57972 100644 --- a/client/src/main/java/ctbrec/ui/CamrecApplication.java +++ b/client/src/main/java/ctbrec/ui/CamrecApplication.java @@ -1,37 +1,9 @@ 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.squareup.moshi.JsonAdapter; import com.squareup.moshi.Moshi; import com.squareup.moshi.Types; - import ctbrec.Config; import ctbrec.Model; import ctbrec.StringUtil; @@ -54,11 +26,13 @@ import ctbrec.sites.bonga.BongaCams; import ctbrec.sites.cam4.Cam4; import ctbrec.sites.camsoda.Camsoda; import ctbrec.sites.chaturbate.Chaturbate; +import ctbrec.sites.cherrytv.CherryTv; import ctbrec.sites.fc2live.Fc2Live; import ctbrec.sites.flirt4free.Flirt4Free; import ctbrec.sites.jasmin.LiveJasmin; import ctbrec.sites.manyvids.MVLive; import ctbrec.sites.mfc.MyFreeCams; +import ctbrec.sites.secretfriends.SecretFriends; import ctbrec.sites.showup.Showup; import ctbrec.sites.streamate.Streamate; import ctbrec.sites.stripchat.Stripchat; @@ -66,13 +40,7 @@ import ctbrec.sites.xlovecam.XloveCam; import ctbrec.ui.controls.Dialogs; import ctbrec.ui.news.NewsTab; import ctbrec.ui.settings.SettingsTab; -import ctbrec.ui.tabs.DonateTabFx; -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.*; import ctbrec.ui.tabs.logging.LoggingTab; import ctbrec.ui.tabs.recorded.RecordedTab; import javafx.application.Application; @@ -81,11 +49,8 @@ import javafx.application.Platform; import javafx.beans.value.ObservableValue; import javafx.geometry.Insets; import javafx.scene.Scene; -import javafx.scene.control.Alert; -import javafx.scene.control.ButtonType; import javafx.scene.control.Label; -import javafx.scene.control.Tab; -import javafx.scene.control.TabPane; +import javafx.scene.control.*; import javafx.scene.control.TabPane.TabDragPolicy; import javafx.scene.image.Image; import javafx.scene.layout.BorderPane; @@ -94,6 +59,22 @@ import javafx.scene.paint.Color; import javafx.stage.Stage; import javafx.stage.WindowEvent; 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 { @@ -273,7 +254,7 @@ public class CamrecApplication extends Application { primaryStage.getScene().getStylesheets().add("/ctbrec/ui/tabs/ThumbCell.css"); primaryStage.getScene().widthProperty().addListener((observable, oldVal, newVal) -> Config.getInstance().getSettings().windowWidth = newVal.intValue()); 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.maximizedProperty().addListener((observable, oldVal, newVal) -> Config.getInstance().getSettings().windowMaximized = newVal); Player.setScene(primaryStage.getScene()); @@ -328,8 +309,8 @@ public class CamrecApplication extends Application { private void suspendTabUpdates() { tabPane.getTabs().stream() - .filter(TabSelectionListener.class::isInstance) - .forEach(t -> ((TabSelectionListener)t).deselected()); + .filter(TabSelectionListener.class::isInstance) + .forEach(t -> ((TabSelectionListener) t).deselected()); } private javafx.event.EventHandler createShutdownHandler() { @@ -433,6 +414,7 @@ public class CamrecApplication extends Application { int modelCount = recorder.getModelCount(); List currentlyRecording = recorder.getCurrentlyRecording(); activeRecordings = currentlyRecording.size(); + DesktopIntegration.updateTrayIcon(activeRecordings); String windowTitle = getActiveRecordings(activeRecordings, modelCount) + title; Platform.runLater(() -> primaryStage.setTitle(windowTitle)); updateStatus(); diff --git a/client/src/main/java/ctbrec/ui/DesktopIntegration.java b/client/src/main/java/ctbrec/ui/DesktopIntegration.java index c8acb709..11b7fa63 100644 --- a/client/src/main/java/ctbrec/ui/DesktopIntegration.java +++ b/client/src/main/java/ctbrec/ui/DesktopIntegration.java @@ -1,33 +1,8 @@ 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.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; @@ -35,17 +10,25 @@ import javafx.scene.control.Label; import javafx.scene.control.TextField; import javafx.scene.layout.BorderPane; 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 { - private 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; + private static TrayIcon trayIcon; public static void open(String uri) { try { @@ -64,7 +47,7 @@ public class DesktopIntegration { } // 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(); for (String helper : externalHelpers) { try { @@ -99,7 +82,7 @@ public class DesktopIntegration { } // 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(); for (String helper : externalHelpers) { try { @@ -140,7 +123,7 @@ public class DesktopIntegration { private static void notifyLinux(String title, String header, String msg) { try { - Process p = Runtime.getRuntime().exec(new String[] { + Process p = Runtime.getRuntime().exec(new String[]{ "notify-send", "-u", "normal", "-t", "5000", @@ -185,91 +168,18 @@ public class DesktopIntegration { private static boolean createTrayIcon(Stage stage) { if (SystemTray.isSupported()) { - if (tray == null) { - String title = CamrecApplication.title; - tray = SystemTray.getSystemTray(); - var 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); - } - } - }); + boolean created = false; + if (trayIcon == null) { + trayIcon = new ctbrec.ui.TrayIcon(stage, recorder); + created = trayIcon.createTrayIcon(); } - return true; + return created; } else { LOG.error("SystemTray notifications not supported by this OS"); 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) { DesktopIntegration.recorder = recorder; } @@ -277,4 +187,10 @@ public class DesktopIntegration { public static void setPrimaryStage(Stage primaryStage) { DesktopIntegration.primaryStage = primaryStage; } + + public static void updateTrayIcon(int activeRecordings) { + if (trayIcon != null) { + trayIcon.updateActiveRecordings(activeRecordings); + } + } } diff --git a/client/src/main/java/ctbrec/ui/TrayIcon.java b/client/src/main/java/ctbrec/ui/TrayIcon.java new file mode 100644 index 00000000..2544767a --- /dev/null +++ b/client/src/main/java/ctbrec/ui/TrayIcon.java @@ -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; + } +} diff --git a/client/src/main/java/ctbrec/ui/settings/SettingsTab.java b/client/src/main/java/ctbrec/ui/settings/SettingsTab.java index bb0b70b0..c739a14a 100644 --- a/client/src/main/java/ctbrec/ui/settings/SettingsTab.java +++ b/client/src/main/java/ctbrec/ui/settings/SettingsTab.java @@ -103,6 +103,7 @@ public class SettingsTab extends Tab implements TabSelectionListener { private SimpleBooleanProperty recordedModelsPerSite; private SimpleBooleanProperty requireAuthentication; private SimpleBooleanProperty totalModelCountInTitle; + private SimpleBooleanProperty showActiveRecordingsInTray; private SimpleBooleanProperty transportLayerSecurity; private SimpleBooleanProperty fastScrollSpeed; private SimpleBooleanProperty useHlsdl; @@ -174,6 +175,7 @@ public class SettingsTab extends Tab implements TabSelectionListener { requireAuthentication = new SimpleBooleanProperty(null, "requireAuthentication", settings.requireAuthentication); requireAuthentication.addListener(this::requireAuthenticationChanged); totalModelCountInTitle = new SimpleBooleanProperty(null, "totalModelCountInTitle", settings.totalModelCountInTitle); + showActiveRecordingsInTray = new SimpleBooleanProperty(null, "showActiveRecordingsInTray", settings.showActiveRecordingsInTray); transportLayerSecurity = new SimpleBooleanProperty(null, "transportLayerSecurity", settings.transportLayerSecurity); recordLocal = new ExclusiveSelectionProperty(null, "localRecording", settings.localRecording, "Local", "Remote"); postProcessingThreads = new SimpleIntegerProperty(null, "postProcessingThreads", settings.postProcessingThreads); @@ -200,7 +202,7 @@ public class SettingsTab extends Tab implements TabSelectionListener { List siteCategories = new ArrayList<>(); for (Site site : sites) { 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); @@ -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("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("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("Fast scroll speed", fastScrollSpeed, "Makes the thumbnail overviews scroll faster with the mouse wheel").needsRestart())), Category.of("Recorder", @@ -255,7 +258,7 @@ public class SettingsTab extends Tab implements TabSelectionListener { Group.of("Timeout", Setting.of("Don't record from", timeoutRecordingStartingAt), Setting.of("Until", timeoutRecordingEndingAt) - ), + ), Group.of("Location", Setting.of("Record Location", recordLocal).needsRestart(), Setting.of("Server", server), @@ -302,7 +305,7 @@ public class SettingsTab extends Tab implements TabSelectionListener { restartNotification.setOpacity(0); restartNotification.setStyle("-fx-font-size: 28; -fx-padding: .3em"); 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))); stackPane.getChildren().add(restartNotification); StackPane.setAlignment(restartNotification, Pos.TOP_RIGHT); diff --git a/common/src/main/java/ctbrec/Settings.java b/common/src/main/java/ctbrec/Settings.java index c244bd62..1a2b6c56 100644 --- a/common/src/main/java/ctbrec/Settings.java +++ b/common/src/main/java/ctbrec/Settings.java @@ -1,18 +1,12 @@ 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.recorder.postprocessing.PostProcessor; +import java.io.File; +import java.time.LocalTime; +import java.util.*; + public class Settings { public enum DirectoryStructure { @@ -21,6 +15,7 @@ public class Settings { ONE_PER_RECORDING("one directory for each recording"); private final String description; + DirectoryStructure(String description) { this.description = description; } @@ -169,6 +164,10 @@ public class Settings { public int segmentErrorMeasurePeriodInSecs = 20; public int segmentErrorThresholdToStopRecording = 5; 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 showPlayerStarting = false; public String showupUsername = "";