View Javadoc
1   /*******************************************************************************
2    * Copyhacked (H) 2012-2025.
3    * This program and the accompanying materials
4    * are made available under no term at all, use it like
5    * you want, but share and discuss it
6    * every time possible with every body.
7    * 
8    * Contributors:
9    *      ron190 at ymail dot com - initial implementation
10   ******************************************************************************/
11  package com.jsql.view.swing;
12  
13  import com.jsql.model.InjectionModel;
14  import com.jsql.util.*;
15  import com.jsql.view.interaction.SubscriberInteraction;
16  import com.jsql.view.swing.action.HotkeyUtil;
17  import com.jsql.view.swing.menubar.AppMenubar;
18  import com.jsql.view.swing.panel.PanelAddressBar;
19  import com.jsql.view.swing.panel.split.SplitNS;
20  import com.jsql.view.swing.terminal.AbstractExploit;
21  import com.jsql.view.swing.tab.TabManagers;
22  import com.jsql.view.swing.util.MediatorHelper;
23  import com.jsql.view.swing.util.UiUtil;
24  import org.apache.commons.lang3.SystemUtils;
25  import org.apache.logging.log4j.LogManager;
26  import org.apache.logging.log4j.Logger;
27  
28  import javax.swing.*;
29  import java.awt.*;
30  import java.awt.event.WindowAdapter;
31  import java.awt.event.WindowEvent;
32  import java.util.HashMap;
33  import java.util.Map;
34  import java.util.UUID;
35  import java.util.prefs.Preferences;
36  import java.util.stream.Stream;
37  
38  /**
39   * View in the MVC pattern, defines all the components
40   * and process actions sent by the model.<br>
41   * Main groups of components:<br>
42   * - at the top: textfield inputs,<br>
43   * - at the center: tree on the left, table on the right,<br>
44   * - at the bottom: information labels.
45   */
46  public class JFrameView extends JFrame {
47  
48      private static final Logger LOGGER = LogManager.getRootLogger();
49  
50      /**
51       * Map of terminal by unique identifier.
52       */
53      private final Map<UUID, AbstractExploit> mapUuidShell = new HashMap<>();
54      private final transient SubscriberInteraction subscriber = new SubscriberInteraction("com.jsql.view.swing.interaction");
55      private TabManagers tabManagers;
56      private boolean isMaximized = false;
57      private final InjectionModel injectionModel;
58      private SplitNS splitNS;  // main
59  
60      public JFrameView(InjectionModel injectionModel) {  // Build the GUI: add app icon, tree icons, the 3 main panels
61          super(StringUtil.APP_NAME);
62          this.injectionModel = injectionModel;
63          MediatorHelper.register(this);
64          UiUtil.prepareGUI();  // Load UI before any component
65          this.initPaneComponents();
66          this.initWindow();
67          this.initShortcuts();
68          this.displayVersion();
69          I18nUtil.checkCurrentLanguage();
70          this.check4K();
71  
72          SwingUtilities.invokeLater(() -> {  // paint native blu svg in theme color behind the scene
73              AppMenubar.applyTheme(injectionModel.getMediatorUtils().getPreferencesUtil().getThemeFlatLafName());  // refresh missing components
74              if (injectionModel.getMediatorUtils().getProxyUtil().isNotLive(GitUtil.ShowOnConsole.YES)) {  // network access
75                  return;
76              }
77              if (injectionModel.getMediatorUtils().getPreferencesUtil().isCheckingUpdate()) {
78                  injectionModel.getMediatorUtils().getGitUtil().checkUpdate(GitUtil.ShowOnConsole.NO);
79              }
80              if (injectionModel.getMediatorUtils().getPreferencesUtil().isShowNews()) {  // disabled when UT only
81                  injectionModel.getMediatorUtils().getGitUtil().showNews();
82              }
83              this.setVisible(true);
84              MediatorHelper.panelAddressBar().getTextFieldAddress().requestFocusInWindow();  // required here to get focus
85          });
86      }
87  
88      private void initPaneComponents() {
89          // Define the default panel: each component on a vertical line
90          this.getContentPane().setLayout(new BoxLayout(this.getContentPane(), BoxLayout.PAGE_AXIS));
91  
92          this.tabManagers = new TabManagers();  // Tab manager linked to cards
93          this.add(this.tabManagers);
94  
95          var menubar = new AppMenubar();
96          this.setJMenuBar(menubar);
97          MediatorHelper.register(menubar);
98  
99          var panelAddressBar = new PanelAddressBar();  // Textfield at the top
100         MediatorHelper.register(panelAddressBar);
101         this.add(panelAddressBar);
102 
103         var mainPanel = new JPanel(new BorderLayout());  // Main panel for tree and tables in the middle
104         this.splitNS = new SplitNS();
105         mainPanel.add(this.splitNS);
106         this.add(mainPanel);
107 
108         menubar.getMenuWindows().switchLocaleFromPreferences();
109     }
110 
111     private void initWindow() {
112         this.setIconImages(UiUtil.getIcons());  // define small and large app icons
113         var preferences = Preferences.userRoot().node(InjectionModel.class.getName());
114         this.addWindowStateListener(e -> this.isMaximized = (e.getNewState() & Frame.MAXIMIZED_BOTH) == Frame.MAXIMIZED_BOTH);
115         this.addWindowListener(new WindowAdapter() {
116             @Override
117             public void windowOpened(WindowEvent event) {
118                 double nsProportion = preferences.getDouble(PreferencesUtil.NS_SPLIT, 0.75);
119                 double ewProportion = preferences.getDouble(PreferencesUtil.EW_SPLIT, 0.33);
120                 if (preferences.getBoolean(PreferencesUtil.IS_MAXIMIZED, false)) {
121                     JFrameView.this.setExtendedState(Frame.MAXIMIZED_BOTH);
122                 }
123                 // Proportion means different location relative to whether frame is maximized or not
124                 // Maximizing must wait AWT events to reflect the new divider location proportion
125                 SwingUtilities.invokeLater(() -> {
126                     JFrameView.this.splitNS.setDividerLocation(
127                         Math.max(0.0, Math.min(1.0, nsProportion))
128                     );
129                     JFrameView.this.splitNS.getSplitEW().setDividerLocation(
130                         Math.max(0.0, Math.min(1.0, ewProportion))
131                     );
132                     MediatorHelper.panelConsoles().networkSplitPane.setDividerLocation(
133                         ComponentOrientation.getOrientation(I18nUtil.getCurrentLocale()).isLeftToRight()
134                         ? 0.33
135                         : 0.66
136                     );
137                 });
138             }
139 
140             @Override
141             public void windowClosing(WindowEvent e) {
142                 preferences.putBoolean(PreferencesUtil.IS_MAXIMIZED, JFrameView.this.isMaximized);
143 
144                 int ewDividerLocation = JFrameView.this.splitNS.getSplitEW().getDividerLocation();
145                 int ewHeight = JFrameView.this.splitNS.getSplitEW().getWidth();
146                 double ewProportion = 100.0 * ewDividerLocation / ewHeight;
147                 double ewLocationProportionCapped = Math.max(0.0, Math.min(1.0,
148                     ewProportion / 100.0
149                 ));
150 
151                 int nsDividerLocation = JFrameView.this.splitNS.getDividerLocation();
152                 int nsHeight = JFrameView.this.splitNS.getHeight();
153                 double nsProportion = 100.0 * nsDividerLocation / nsHeight;
154                 double nsLocationProportionCapped = Math.max(0.0, Math.min(1.0,
155                     nsProportion / 100.0
156                 ));
157 
158                 // Divider location changes when window is maximized, instead stores location percentage between 0.0 and 1.0
159                 preferences.putDouble(PreferencesUtil.NS_SPLIT, nsLocationProportionCapped);
160                 preferences.putDouble(PreferencesUtil.EW_SPLIT, ewLocationProportionCapped);
161 
162                 preferences.putBoolean(PreferencesUtil.BINARY_VISIBLE, false);
163                 preferences.putBoolean(PreferencesUtil.CHUNK_VISIBLE, false);
164                 preferences.putBoolean(PreferencesUtil.NETWORK_VISIBLE, false);
165                 preferences.putBoolean(PreferencesUtil.JAVA_VISIBLE, false);
166                 for (var i = 0 ; i < MediatorHelper.tabConsoles().getTabCount() ; i++) {
167                     if ("CONSOLE_BINARY_LABEL".equals(MediatorHelper.tabConsoles().getTabComponentAt(i).getName())) {
168                         preferences.putBoolean(PreferencesUtil.BINARY_VISIBLE, true);
169                     } else if ("CONSOLE_CHUNK_LABEL".equals(MediatorHelper.tabConsoles().getTabComponentAt(i).getName())) {
170                         preferences.putBoolean(PreferencesUtil.CHUNK_VISIBLE, true);
171                     } else if ("CONSOLE_NETWORK_LABEL".equals(MediatorHelper.tabConsoles().getTabComponentAt(i).getName())) {
172                         preferences.putBoolean(PreferencesUtil.NETWORK_VISIBLE, true);
173                     } else if ("CONSOLE_JAVA_LABEL".equals(MediatorHelper.tabConsoles().getTabComponentAt(i).getName())) {
174                         preferences.putBoolean(PreferencesUtil.JAVA_VISIBLE, true);
175                     }
176                 }
177             }
178         });
179         
180         this.setSize(1024, 768);
181         this.setLocationRelativeTo(null);  // center the window
182         this.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
183     }
184 
185     private void initShortcuts() {
186         HotkeyUtil.addShortcut(this.getRootPane(), MediatorHelper.tabResults());
187         HotkeyUtil.addTextFieldShortcutSelectAll();
188     }
189 
190     public void resetInterface() {  // Empty the interface
191         this.mapUuidShell.clear();
192         MediatorHelper.panelAddressBar().getPanelTrailingAddress().reset();
193         MediatorHelper.panelConsoles().reset();
194         MediatorHelper.treeDatabase().reset();
195         
196         for (var i = 0 ; i < MediatorHelper.tabConsoles().getTabCount() ; i++) {
197             var tabComponent = MediatorHelper.tabConsoles().getTabComponentAt(i);
198             if (tabComponent != null) {
199                 tabComponent.setFont(tabComponent.getFont().deriveFont(Font.PLAIN));
200             }
201         }
202         
203         Stream.of(MediatorHelper.managerFile(), MediatorHelper.managerExploit()).forEach(managerList -> {
204             managerList.setButtonEnable(false);
205             managerList.changePrivilegeIcon(UiUtil.SQUARE.getIcon());
206         });
207     }
208 
209     private void displayVersion() {
210         LOGGER.log(
211             LogLevelUtil.CONSOLE_DEFAULT,
212             "{} v{} on Java {}-{}-{}",
213             () -> StringUtil.APP_NAME,
214             () -> this.injectionModel.getPropertiesUtil().getVersionJsql(),
215             () -> SystemUtils.JAVA_VERSION,
216             () -> SystemUtils.OS_ARCH,
217             () -> SystemUtils.USER_LANGUAGE
218         );
219     }
220 
221     private void check4K() {
222         Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
223         int width = (int) screenSize.getWidth();
224         if (width >= 3840 && !this.injectionModel.getMediatorUtils().getPreferencesUtil().is4K()) {
225             LOGGER.log(LogLevelUtil.CONSOLE_ERROR, "Screen compatible with resolution 4K, enable high-definition in Preferences");
226         }
227     }
228 
229 
230     // Getters and setters
231 
232     public final Map<UUID, AbstractExploit> getMapUuidShell() {
233         return this.mapUuidShell;
234     }
235 
236     public SubscriberInteraction getSubscriber() {
237         return this.subscriber;
238     }
239 
240     public SplitNS getSplitNS() {
241         return this.splitNS;
242     }
243 
244     public TabManagers getTabManagers() {
245         return this.tabManagers;
246     }
247 }