package org.openslx.dozmod; import java.awt.AWTEvent; import java.awt.Font; import java.awt.Toolkit; import java.awt.event.AWTEventListener; import java.awt.event.ContainerEvent; import java.io.File; import java.io.IOException; import java.lang.reflect.InvocationTargetException; import java.util.Arrays; import java.util.HashSet; import java.util.Locale; import java.util.Set; import java.util.concurrent.CountDownLatch; import java.util.zip.Deflater; import javax.net.ssl.SSLContext; import javax.swing.SwingUtilities; import javax.swing.UIDefaults; import javax.swing.UIManager; import org.apache.logging.log4j.Level; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.core.LoggerContext; import org.apache.logging.log4j.core.appender.RollingFileAppender; import org.apache.logging.log4j.core.appender.rolling.DefaultRolloverStrategy; import org.apache.logging.log4j.core.appender.rolling.OnStartupTriggeringPolicy; import org.apache.logging.log4j.core.config.Configuration; import org.apache.logging.log4j.core.config.Configurator; import org.apache.logging.log4j.core.config.DefaultConfiguration; import org.apache.logging.log4j.core.layout.PatternLayout; import org.openslx.dozmod.Config.ProxyMode; import org.openslx.dozmod.gui.Gui; import org.openslx.dozmod.gui.MainWindow; import org.openslx.dozmod.gui.helper.I18n; import org.openslx.dozmod.gui.helper.Language; import org.openslx.dozmod.gui.helper.MessageType; import org.openslx.dozmod.util.ClientVersion; import org.openslx.dozmod.util.ProxyConfigurator; import org.openslx.thrifthelper.ThriftManager; import org.openslx.util.AppUtil; import org.openslx.util.Util; import com.formdev.flatlaf.FlatDarkLaf; import com.formdev.flatlaf.FlatLightLaf; public class App { // Logger private final static Logger LOGGER = LogManager.getLogger(App.class); public static final int THRIFT_PORT = 9090; public static final int THRIFT_SSL_PORT = THRIFT_PORT + 1; public static final int THRIFT_TIMEOUT_MS = 15000; private static CountDownLatch proxyLatch = new CountDownLatch(1); private static boolean proxyInitDone = false; private static String masterServerHost = null; private static String setupFileLogger() { // path to the log file final String logFileName = Config.getPath() + File.separator + Branding.getConfigDirectory() + ".log"; final LoggerContext loggingContext = LoggerContext.class.cast(LogManager.getContext(false)); final Configuration loggingConfig = loggingContext.getConfiguration(); // add rolling file appender final RollingFileAppender fileAppender = RollingFileAppender.newBuilder() .setName("logToFile") .withFileName(logFileName) .withFilePattern(logFileName + ".%i") .withAppend(true) .setBufferedIo(true) .setConfiguration(loggingConfig) .withCreateOnDemand(false) .setLayout(PatternLayout.newBuilder() .withConfiguration(loggingConfig) .withPattern("%d{ABSOLUTE} [%t] %-5p %F - %m%n") .build()) .withStrategy(DefaultRolloverStrategy.newBuilder() .withMin("1") .withMax("9") .withFileIndex("1") .withCompressionLevelStr(Integer.toString(Deflater.NO_COMPRESSION)) .withConfig(loggingConfig) .build()) .withPolicy(OnStartupTriggeringPolicy.createPolicy(1)) .build(); fileAppender.start(); // register rolling file appender loggingConfig.addAppender(fileAppender); loggingConfig.getRootLogger().addAppender(fileAppender, Level.ALL, null); loggingContext.updateLoggers(loggingConfig); return logFileName; } public static void main(final String[] args) throws InvocationTargetException, InterruptedException { // setup basic logging appender to log output on console if no external appender (log4j2.properties) is configured if (org.apache.logging.log4j.core.Logger.class.cast(LogManager.getRootLogger()).getAppenders().isEmpty()) { Configurator.initialize(new DefaultConfiguration()); } if (args.length >= 2) { if (args[0].equals("--json")) { writeJsonUpdateFile(args[1]); return; } if (args[0].equals("--dump")) { Branding.dump(args[1]); return; } } if (args.length >= 3) { if (args[0].equals("--pack")) { Branding.pack(args[1], args[2]); return; } } try { Config.init(); } catch (Exception e) { Gui.showMessageBox(null, I18n.APP.getString("App.Message.error.loadingConfigurationFailed"), MessageType.ERROR, LOGGER, e); return; } final String logFilePath = setupFileLogger(); AppUtil.logHeader(LOGGER, Branding.getApplicationName(), App.class.getPackage().getImplementationVersion()); LOGGER.info("Starting logging to " + logFilePath); // Setting the locale if (!setPreferredLanguage()) { // Detect operating system language String ul = System.getProperty("user.language"); if (ul.equals("de")) { Locale.setDefault(new Locale("de", "DE")); Config.setPreferredLanguage(Language.DE_DE.value); } else if (ul.equals("tr")) { Locale.setDefault(new Locale("tr", "TR")); Config.setPreferredLanguage(Language.TR_TR.value); } else { Locale.setDefault(new Locale("en", "US")); Config.setPreferredLanguage(Language.EN_US.value); } } // Install FlatLaf look and feel FlatLightLaf.installLafInfo(); FlatDarkLaf.installLafInfo(); try { if (System.getProperty("swing.defaultlaf") != null) { UIManager.setLookAndFeel(System.getProperty("swing.defaultlaf")); } else if(Config.getLookAndFeel() != null) { UIManager.setLookAndFeel(Config.getLookAndFeel()); } else { UIManager.setLookAndFeel("com.sun.java.swing.plaf.gtk.GTKLookAndFeel"); Config.setLookAndFeel("com.sun.java.swing.plaf.gtk.GTKLookAndFeel"); } } catch (Throwable e1) { try { LOGGER.error("Something went wrong with the chosen 'LookAndFeel'. Falling back to default 'SystemLookAndFeel'"); UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); if(Config.getLookAndFeel() == null) { Config.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); } } catch (Throwable e) { LOGGER.error("Cannot select system look and feel", e); LOGGER.error("----------------------------------"); LOGGER.error("Cannot initialize GUI. Make sure you have the non-headless version of Java installed."); System.exit(1); } } // Setup swing style System.setProperty("awt.useSystemAAFontSettings", "on"); System.setProperty("swing.aatext", "true"); // Adjust font size adjustFontSize(Config.getFontScaling()); // Set up connection to master server final String host; int port; boolean useSsl; if (args.length == 3) { host = args[0]; port = Util.parseInt(args[1], -1); useSsl = Boolean.parseBoolean(args[2]); } else { host = Branding.getMasterServerAddress(); port = THRIFT_SSL_PORT; useSsl = true; } // remember masterserver host masterServerHost = host; // now start the proxy detection if (Config.getProxyMode() == ProxyMode.AUTO) { // Initialize the proxy settings new Thread() { @Override public void run() { ProxyConfigurator.init(); proxyInitDone = true; proxyLatch.countDown(); } }.start(); } else { proxyInitDone = true; proxyLatch.countDown(); } // SSL if (useSsl) { try { SSLContext ctx = SSLContext.getInstance("TLSv1.2"); ctx.init(null, null, null); ThriftManager.setMasterServerAddress(ctx, host, port, THRIFT_TIMEOUT_MS); } catch (final Exception e1) { SwingUtilities.invokeAndWait(new Runnable() { @Override public void run() { boolean ret = Gui.showMessageBox(null, I18n.APP.getString("App.Message.yesNo.SSLNotAvailable"), MessageType.QUESTION_YESNO, LOGGER, e1); if (!ret) { System.exit(1); } } }); useSsl = false; port = port - 1; // This assumes SSL port is always plain port + 1 } } // No "else", might be fallback for failed SSL if (!useSsl) { ThriftManager.setMasterServerAddress(null, host, port, THRIFT_TIMEOUT_MS); } // Setup global thrift connection error handler and then open the GUI SwingUtilities.invokeLater(new Runnable() { @Override public void run() { Thread.currentThread().setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() { @Override public void uncaughtException(Thread t, Throwable e) { if (e instanceof ClassCastException) { // HACK HACK: Endless chains of exceptions from nowhere on Linux after suspend // (seems to be driver/version/model specific) if (e.getMessage().contains("SurfaceData")) return; } Gui.showMessageBox(null, I18n.APP.getString("App.Message.warning.uncaughtException", t.getName()), MessageType.WARNING, LOGGER, e); } }); MainWindow.open(); } }); } private static void writeJsonUpdateFile(String destination) { try { ClientVersion.createJson(destination); } catch (IOException e) { LOGGER.error("Failed to write JSON update file to '" + destination + "': ", e); } } private static void adjustFontSize(int percent) { if (percent == 100 || percent <= 0 || percent > 1000) return; final float scaling = 0.01f * (float) percent; int size = determineDefaultFontSize(UIManager.getLookAndFeelDefaults()); if (size == -1) { size = determineDefaultFontSize(UIManager.getDefaults()); } if (size == -1) { size = 12; } final float defaultSize = size; Toolkit.getDefaultToolkit().addAWTEventListener(new AWTEventListener() { @Override public void eventDispatched(AWTEvent event) { if (event instanceof ContainerEvent) { ContainerEvent containerEvent = (ContainerEvent) event; if (containerEvent.getID() == ContainerEvent.COMPONENT_ADDED) { Font font = containerEvent.getChild().getFont(); // Do not update font in tables and ComboBoxes on every renderer call to prevent weirdness. // This prevents multiple instances of fonts being scaled multiple times. if (containerEvent.getChild().getName() != null && (containerEvent.getChild().getName().toLowerCase().contains("render") || (containerEvent.getChild().getName().toLowerCase().contains("combo")))){ return; } if (font != null && font.getSize2D() <= defaultSize) { containerEvent.getChild().setFont( new Font(font.getName(), font.getStyle(), Math.round(font.getSize2D() * scaling))); } } } } }, AWTEvent.COMPONENT_EVENT_MASK | AWTEvent.CONTAINER_EVENT_MASK); Font tbFont = UIManager.getFont("TitledBorder.font"); if (tbFont != null) { UIManager.put("TitledBorder.font", tbFont.deriveFont(tbFont.getSize2D() * scaling)); } } private static int determineDefaultFontSize(UIDefaults defaults) { if (defaults == null) return -1; int sizes[] = new int[100]; Set keys = new HashSet<>(defaults.keySet()); for (Object key : keys) { if (key == null) continue; Object value = defaults.get(key); if (value == null) continue; if (value instanceof Font) { Font font = (Font) value; if (font.getSize() > 0 && font.getSize() < sizes.length) { sizes[font.getSize()]++; } } } int best = -1; for (int index = 0; index < sizes.length; ++index) { if (best == -1 || sizes[best] < sizes[index]) { best = index; } } return sizes[best]; } /** * Blocks as long as initialization is still going on. Currently this is * just the proxy setup, so this should be used before any network * communication happens. */ public static void waitForInit() { if (proxyInitDone) return; try { proxyLatch.await(); } catch (InterruptedException e) { } } /** * Check if any default language is already set and valid. * @return true, if any default language exists and is valid. */ private static boolean setPreferredLanguage() { // Check if any preferred language exists String language = Config.getPreferredLanguage(); // Check whether the provided string is null or empty if (language == null || language.trim().isEmpty()) { return false; } // Check if the provided string has the format language_country e.g. en_US String[] parts = language.split("_"); if (parts.length != 2) { return false; } Locale locale = new Locale(parts[0], parts[1]); // Check now if the locale is valid if (!Arrays.asList(Locale.getAvailableLocales()).contains(locale)) { return false; } Locale.setDefault(locale); return true; } public static synchronized boolean isInitDone() { return proxyInitDone; } public static synchronized String getMasterServerAddress() { return masterServerHost; } }