diff --git a/MasterPassword/Java/masterpassword-gui/pom.xml b/MasterPassword/Java/masterpassword-gui/pom.xml
new file mode 100644
index 00000000..646ae8c5
--- /dev/null
+++ b/MasterPassword/Java/masterpassword-gui/pom.xml
@@ -0,0 +1,79 @@
+
+
+
+ 4.0.0
+
+
+
+ com.lyndir.lhunath.masterpassword
+ masterpassword
+ GIT-SNAPSHOT
+
+
+ Master Password GUI
+ A GUI interface to the Master Password algorithm
+
+ com.lyndir.lhunath.masterpassword
+ masterpassword-gui
+ jar
+
+
+
+
+
+ src/main/resources
+ false
+
+
+
+
+ org.apache.maven.plugins
+ maven-shade-plugin
+ 2.2
+
+
+ package
+
+ shade
+
+
+
+
+ com.lyndir.lhunath.masterpassword.GUI
+
+
+
+
+ *:*
+
+ META-INF/*.SF
+ META-INF/*.DSA
+ META-INF/*.RSA
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ com.lyndir.lhunath.masterpassword
+ masterpassword-algorithm
+ GIT-SNAPSHOT
+
+
+
+ ch.qos.logback
+ logback-classic
+
+
+
+
+
diff --git a/MasterPassword/Java/masterpassword-gui/src/main/java/com/lyndir/lhunath/masterpassword/GUI.java b/MasterPassword/Java/masterpassword-gui/src/main/java/com/lyndir/lhunath/masterpassword/GUI.java
new file mode 100644
index 00000000..42ac90cc
--- /dev/null
+++ b/MasterPassword/Java/masterpassword-gui/src/main/java/com/lyndir/lhunath/masterpassword/GUI.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright 2008, Maarten Billemont
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.lyndir.lhunath.masterpassword;
+
+import com.apple.eawt.*;
+import java.io.IOException;
+import javax.swing.*;
+
+
+/**
+ *
Jun 10, 2008
+ *
+ * @author mbillemo
+ */
+public class GUI implements UnlockFrame.SignInCallback {
+
+ private UnlockFrame unlockFrame = new UnlockFrame( this );
+ private PasswordFrame passwordFrame;
+
+ public static void main(final String[] args)
+ throws IOException {
+
+ new GUI().open();
+ }
+
+ public GUI() {
+
+ try {
+ getClass().getClassLoader().loadClass( "com.apple.eawt.Application" );
+ Application application = Application.getApplication();
+ application.addAppEventListener( new AppForegroundListener() {
+
+ @Override
+ public void appMovedToBackground(AppEvent.AppForegroundEvent arg0) {
+ }
+
+ @Override
+ public void appRaisedToForeground(AppEvent.AppForegroundEvent arg0) {
+ open();
+ }
+ } );
+ application.addAppEventListener( new AppReOpenedListener() {
+ @Override
+ public void appReOpened(AppEvent.AppReOpenedEvent arg0) {
+ open();
+ }
+ } );
+ }
+ catch (ClassNotFoundException | NoClassDefFoundError ignored) {
+ }
+ }
+
+ private void open() {
+ SwingUtilities.invokeLater( new Runnable() {
+ @Override
+ public void run() {
+ if (passwordFrame == null) {
+ unlockFrame.setVisible( true );
+ } else {
+ passwordFrame.setVisible( true );
+ }
+ }
+ } );
+ }
+
+ @Override
+ public boolean signedIn(final String userName, final String masterPassword) {
+ final byte[] key = MasterPassword.keyForPassword( masterPassword, userName );
+ passwordFrame = new PasswordFrame( new User( userName, key ) );
+
+ open();
+ return true;
+ }
+}
diff --git a/MasterPassword/Java/masterpassword-gui/src/main/java/com/lyndir/lhunath/masterpassword/PasswordFrame.java b/MasterPassword/Java/masterpassword-gui/src/main/java/com/lyndir/lhunath/masterpassword/PasswordFrame.java
new file mode 100644
index 00000000..0acb737b
--- /dev/null
+++ b/MasterPassword/Java/masterpassword-gui/src/main/java/com/lyndir/lhunath/masterpassword/PasswordFrame.java
@@ -0,0 +1,152 @@
+package com.lyndir.lhunath.masterpassword;
+
+import static com.lyndir.lhunath.opal.system.util.StringUtils.*;
+
+import com.lyndir.lhunath.masterpassword.util.Components;
+import java.awt.*;
+import java.awt.datatransfer.StringSelection;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import javax.swing.*;
+import javax.swing.border.*;
+import javax.swing.event.*;
+
+
+/**
+ * @author lhunath, 2014-06-08
+ */
+public class PasswordFrame extends JFrame implements DocumentListener {
+
+ private static final ExecutorService executor = Executors.newSingleThreadExecutor();
+
+ private final User user;
+ private final JPanel root;
+ private final JTextField siteNameField;
+ private final JComboBox siteTypeField;
+ private final JSpinner siteCounterField;
+ private final JLabel passwordLabel;
+
+ public PasswordFrame(User user)
+ throws HeadlessException {
+ super( "Master Password" );
+ this.user = user;
+
+ JLabel label;
+
+ setDefaultCloseOperation( DISPOSE_ON_CLOSE );
+ setContentPane( root = new JPanel( new BorderLayout( 20, 20 ) ) );
+ root.setBorder( new EmptyBorder( 20, 20, 20, 20 ) );
+
+ // User
+ add( new JLabel( strf( "Generating passwords for: %s", user.getName() ) ), BorderLayout.NORTH );
+
+ // Site
+ JPanel sitePanel = new JPanel();
+ sitePanel.setLayout( new BoxLayout( sitePanel, BoxLayout.PAGE_AXIS ) );
+ sitePanel.setBorder( new CompoundBorder( new EtchedBorder( EtchedBorder.RAISED ), new EmptyBorder( 8, 8, 8, 8 ) ) );
+ add( sitePanel, BorderLayout.CENTER );
+
+ // Site Name
+ sitePanel.add( new JLabel( "Site Name:", JLabel.LEADING ) );
+ sitePanel.add( siteNameField = new JTextField() {
+ @Override
+ public Dimension getMaximumSize() {
+ return new Dimension( Integer.MAX_VALUE, getPreferredSize().height );
+ }
+ } );
+ siteNameField.getDocument().addDocumentListener( this );
+ siteNameField.addActionListener( new ActionListener() {
+ @Override
+ public void actionPerformed(final ActionEvent e) {
+ updatePassword( new PasswordCallback() {
+ @Override
+ public void passwordGenerated(final String siteName, final String sitePassword) {
+ StringSelection clipboardContents = new StringSelection( sitePassword );
+ Toolkit.getDefaultToolkit().getSystemClipboard().setContents( clipboardContents, null );
+
+ SwingUtilities.invokeLater( new Runnable() {
+ @Override
+ public void run() {
+ dispose();
+ }
+ } );
+ }
+ } );
+ }
+ } );
+
+ // Site Type & Counter
+ sitePanel.add( Components.boxLayout( BoxLayout.LINE_AXIS, //
+ siteTypeField = new JComboBox<>( MPElementType.values() ),
+ siteCounterField = new JSpinner( new SpinnerNumberModel( 1, 1, Integer.MAX_VALUE, 1 ) ) {
+ @Override
+ public Dimension getMaximumSize() {
+ return new Dimension( 50, getPreferredSize().height );
+ }
+ } ) );
+ siteTypeField.setSelectedItem( MPElementType.GeneratedLong );
+ siteCounterField.addChangeListener( new ChangeListener() {
+ @Override
+ public void stateChanged(final ChangeEvent e) {
+ updatePassword( null );
+ }
+ } );
+
+ // Password
+ add( passwordLabel = new JLabel( " ", JLabel.CENTER ), BorderLayout.SOUTH );
+ passwordLabel.setFont( passwordLabel.getFont().deriveFont( 40f ) );
+
+ pack();
+ setMinimumSize( getSize() );
+ setPreferredSize( new Dimension( 600, getSize().height ) );
+ pack();
+
+ setLocationByPlatform( true );
+ setLocationRelativeTo( null );
+ }
+
+ private void updatePassword(final PasswordCallback callback) {
+ final MPElementType siteType = (MPElementType) siteTypeField.getSelectedItem();
+ final String siteName = siteNameField.getText();
+ final int siteCounter = (Integer) siteCounterField.getValue();
+
+ executor.submit( new Runnable() {
+ @Override
+ public void run() {
+ final String sitePassword = MasterPassword.generateContent( siteType, siteName, user.getKey(), siteCounter );
+ if (callback != null) {
+ callback.passwordGenerated( siteName, sitePassword );
+ }
+
+ SwingUtilities.invokeLater( new Runnable() {
+ @Override
+ public void run() {
+ passwordLabel.setText( sitePassword );
+ }
+ } );
+ }
+ } );
+ }
+
+ @Override
+ public void insertUpdate(final DocumentEvent e) {
+ updatePassword( null );
+ }
+
+ @Override
+ public void removeUpdate(final DocumentEvent e) {
+ updatePassword( null );
+ }
+
+ @Override
+ public void changedUpdate(final DocumentEvent e) {
+ updatePassword( null );
+ }
+
+ interface PasswordCallback {
+
+ void passwordGenerated(String siteName, String sitePassword);
+ }
+}
diff --git a/MasterPassword/Java/masterpassword-gui/src/main/java/com/lyndir/lhunath/masterpassword/UnlockFrame.java b/MasterPassword/Java/masterpassword-gui/src/main/java/com/lyndir/lhunath/masterpassword/UnlockFrame.java
new file mode 100644
index 00000000..bcaac7c0
--- /dev/null
+++ b/MasterPassword/Java/masterpassword-gui/src/main/java/com/lyndir/lhunath/masterpassword/UnlockFrame.java
@@ -0,0 +1,175 @@
+package com.lyndir.lhunath.masterpassword;
+
+import com.google.common.io.Resources;
+import com.lyndir.lhunath.masterpassword.util.Components;
+import java.awt.*;
+import java.awt.event.ActionEvent;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import javax.swing.*;
+import javax.swing.border.*;
+import javax.swing.event.DocumentEvent;
+import javax.swing.event.DocumentListener;
+
+
+/**
+ * @author lhunath, 2014-06-08
+ */
+public class UnlockFrame extends JFrame implements DocumentListener {
+
+ private static final ExecutorService executor = Executors.newSingleThreadExecutor();
+
+ private final SignInCallback signInCallback;
+ private final JPanel root;
+ private final JLabel avatarView;
+ private final JTextField userNameField;
+ private final JTextField masterPasswordField;
+ private final JButton signInButton;
+
+ public UnlockFrame(final SignInCallback signInCallback)
+ throws HeadlessException {
+ super( "Unlock Master Password" );
+ this.signInCallback = signInCallback;
+
+ JLabel label;
+
+ setDefaultCloseOperation( DISPOSE_ON_CLOSE );
+ setContentPane( root = new JPanel( new BorderLayout( 20, 20 ) ) );
+ root.setBorder( new EmptyBorder( 20, 20, 20, 20 ) );
+
+ JPanel userAndPassword = new JPanel();
+ userAndPassword.setLayout( new BoxLayout( userAndPassword, BoxLayout.PAGE_AXIS ) );
+ userAndPassword.setBorder( new CompoundBorder( new EtchedBorder( EtchedBorder.RAISED ), new EmptyBorder( 8, 8, 8, 8 ) ) );
+ add( userAndPassword, BorderLayout.CENTER );
+
+ // Avatar
+ userAndPassword.add( Box.createVerticalGlue() );
+ userAndPassword.add( avatarView = new JLabel( new ImageIcon( Resources.getResource( "media/Avatars/avatar-0.png" ) ) ) {
+ @Override
+ public Dimension getMaximumSize() {
+ return new Dimension( Integer.MAX_VALUE, Integer.MAX_VALUE );
+ }
+ } );
+ userAndPassword.add( Box.createVerticalGlue() );
+
+ // User Name
+ userAndPassword.add( label = new JLabel( "User Name:" ) );
+ label.setHorizontalAlignment( SwingConstants.CENTER );
+ label.setVerticalAlignment( SwingConstants.BOTTOM );
+ userAndPassword.add( userNameField = new JTextField() {
+ @Override
+ public Dimension getMaximumSize() {
+ return new Dimension( Integer.MAX_VALUE, getPreferredSize().height );
+ }
+ } );
+ userNameField.getDocument().addDocumentListener( this );
+ userNameField.addActionListener( new AbstractAction() {
+ @Override
+ public void actionPerformed(final ActionEvent e) {
+ trySignIn();
+ }
+ } );
+
+ // Master Password
+ userAndPassword.add( label = new JLabel( "Master Password:" ) );
+ label.setHorizontalAlignment( SwingConstants.CENTER );
+ label.setVerticalAlignment( SwingConstants.BOTTOM );
+ userAndPassword.add( masterPasswordField = new JPasswordField() {
+ @Override
+ public Dimension getMaximumSize() {
+ return new Dimension( Integer.MAX_VALUE, getPreferredSize().height );
+ }
+ } );
+ masterPasswordField.getDocument().addDocumentListener( this );
+ masterPasswordField.addActionListener( new AbstractAction() {
+ @Override
+ public void actionPerformed(final ActionEvent e) {
+ trySignIn();
+ }
+ } );
+
+ // Sign In
+ add( Components.boxLayout( BoxLayout.LINE_AXIS, Box.createGlue(), signInButton = new JButton( "Sign In" ), Box.createGlue() ),
+ BorderLayout.SOUTH );
+ signInButton.addActionListener( new AbstractAction() {
+ @Override
+ public void actionPerformed(final ActionEvent e) {
+ trySignIn();
+ }
+ } );
+
+ checkSignIn();
+
+ pack();
+ setMinimumSize( getSize() );
+ setPreferredSize( new Dimension( 300, 300 ) );
+ pack();
+
+ setLocationByPlatform( true );
+ setLocationRelativeTo( null );
+ }
+
+ private boolean checkSignIn() {
+ String userName = userNameField.getText();
+ String masterPassword = masterPasswordField.getText();
+
+ boolean enabled = !userName.isEmpty() && !masterPassword.isEmpty();
+ signInButton.setEnabled( enabled );
+
+ return enabled;
+ }
+
+ private void trySignIn() {
+ if (!checkSignIn())
+ return;
+
+ final String userName = userNameField.getText();
+ final String masterPassword = masterPasswordField.getText();
+
+ userNameField.setEnabled( false );
+ masterPasswordField.setEnabled( false );
+ signInButton.setEnabled( false );
+ signInButton.setText( "Signing In..." );
+
+ executor.submit( new Runnable() {
+ @Override
+ public void run() {
+ final boolean success = signInCallback.signedIn( userName, masterPassword );
+ SwingUtilities.invokeLater( new Runnable() {
+ @Override
+ public void run() {
+ if (success) {
+ dispose();
+ return;
+ }
+
+ userNameField.setEnabled( true );
+ masterPasswordField.setEnabled( true );
+ signInButton.setText( "Sign In" );
+ checkSignIn();
+ }
+ } );
+ }
+ } );
+ }
+
+ @Override
+ public void insertUpdate(final DocumentEvent e) {
+ checkSignIn();
+ }
+
+ @Override
+ public void removeUpdate(final DocumentEvent e) {
+ checkSignIn();
+ }
+
+ @Override
+ public void changedUpdate(final DocumentEvent e) {
+ checkSignIn();
+ }
+
+ interface SignInCallback {
+
+ boolean signedIn(String userName, String masterPassword);
+ }
+}
diff --git a/MasterPassword/Java/masterpassword-gui/src/main/java/com/lyndir/lhunath/masterpassword/User.java b/MasterPassword/Java/masterpassword-gui/src/main/java/com/lyndir/lhunath/masterpassword/User.java
new file mode 100644
index 00000000..2c579796
--- /dev/null
+++ b/MasterPassword/Java/masterpassword-gui/src/main/java/com/lyndir/lhunath/masterpassword/User.java
@@ -0,0 +1,22 @@
+package com.lyndir.lhunath.masterpassword;
+
+/**
+ * @author lhunath, 2014-06-08
+ */
+public class User {
+ private final String name;
+ private final byte[] key;
+
+ public User(final String name, final byte[] key) {
+ this.name = name;
+ this.key = key;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public byte[] getKey() {
+ return key;
+ }
+}
diff --git a/MasterPassword/Java/masterpassword-gui/src/main/java/com/lyndir/lhunath/masterpassword/util/Components.java b/MasterPassword/Java/masterpassword-gui/src/main/java/com/lyndir/lhunath/masterpassword/util/Components.java
new file mode 100644
index 00000000..0c3aff58
--- /dev/null
+++ b/MasterPassword/Java/masterpassword-gui/src/main/java/com/lyndir/lhunath/masterpassword/util/Components.java
@@ -0,0 +1,20 @@
+package com.lyndir.lhunath.masterpassword.util;
+
+import java.awt.*;
+import javax.swing.*;
+
+
+/**
+ * @author lhunath, 2014-06-08
+ */
+public abstract class Components {
+
+ public static JComponent boxLayout(int axis, Component... components) {
+ JPanel container = new JPanel();
+ container.setLayout( new BoxLayout( container, axis ) );
+ for (Component component : components)
+ container.add( component );
+
+ return container;
+ }
+}
diff --git a/MasterPassword/Java/masterpassword-gui/src/main/resources/logback.xml b/MasterPassword/Java/masterpassword-gui/src/main/resources/logback.xml
new file mode 100644
index 00000000..a5c492fa
--- /dev/null
+++ b/MasterPassword/Java/masterpassword-gui/src/main/resources/logback.xml
@@ -0,0 +1,15 @@
+
+
+
+
+ %-8relative %22c{0} [%-5level] %msg%n
+
+
+
+
+
+
+
+
+
+
diff --git a/MasterPassword/Java/masterpassword-gui/src/main/resources/media b/MasterPassword/Java/masterpassword-gui/src/main/resources/media
new file mode 120000
index 00000000..c0b8c864
--- /dev/null
+++ b/MasterPassword/Java/masterpassword-gui/src/main/resources/media
@@ -0,0 +1 @@
+../../../../../Resources/Media/
\ No newline at end of file
diff --git a/MasterPassword/Java/pom.xml b/MasterPassword/Java/pom.xml
index ea36869d..048e14a1 100644
--- a/MasterPassword/Java/pom.xml
+++ b/MasterPassword/Java/pom.xml
@@ -21,6 +21,7 @@
masterpassword-algorithm
masterpassword-cli
+ masterpassword-gui
diff --git a/MasterPassword/ObjC/Mac/MasterPassword-Mac.xcodeproj/project.pbxproj b/MasterPassword/ObjC/Mac/MasterPassword-Mac.xcodeproj/project.pbxproj
index 7ff8f86c..a4802b08 100644
--- a/MasterPassword/ObjC/Mac/MasterPassword-Mac.xcodeproj/project.pbxproj
+++ b/MasterPassword/ObjC/Mac/MasterPassword-Mac.xcodeproj/project.pbxproj
@@ -1876,7 +1876,7 @@
);
GCC_PREFIX_HEADER = "MasterPassword-Prefix.pch";
INFOPLIST_FILE = "MasterPassword-Info.plist";
- PROVISIONING_PROFILE = "9FCC90BB-24CA-40E2-8565-860F95CCC039";
+ PROVISIONING_PROFILE = "9BA628A2-36F5-438B-9F08-A2527CECC9F8";
SKIP_INSTALL = NO;
WRAPPER_NAME = "Master Password.${WRAPPER_EXTENSION}";
};
@@ -1896,7 +1896,7 @@
);
GCC_PREFIX_HEADER = "MasterPassword-Prefix.pch";
INFOPLIST_FILE = "MasterPassword-Info.plist";
- PROVISIONING_PROFILE = "9FCC90BB-24CA-40E2-8565-860F95CCC039";
+ PROVISIONING_PROFILE = "9BA628A2-36F5-438B-9F08-A2527CECC9F8";
SKIP_INSTALL = NO;
WRAPPER_NAME = "Master Password.${WRAPPER_EXTENSION}";
};