2
0

Compare commits

..

76 Commits

Author SHA1 Message Date
Maarten Billemont
8f35ac5f64 Improved libxml2 cflags/ldflags. 2018-09-26 00:43:23 -04:00
Maarten Billemont
06100510c3 2.7-java-9 2018-09-26 00:03:36 -04:00
Maarten Billemont
1bf6109038 Hide passwords option & fix settings for new sites. 2018-09-26 00:00:05 -04:00
Maarten Billemont
f2fa2a25b2 Pass font resolution through the GraphicsEnvironment. 2018-09-25 20:55:04 -04:00
Maarten Billemont
2a0cfd3a32 Documentation and interface tweaks. 2018-09-22 19:43:13 -04:00
Maarten Billemont
3070967d34 Convenience flag for building a debuggable binary. 2018-09-22 14:22:17 -04:00
Maarten Billemont
e4837a284a Fall back to getline if ncurses cannot be initialized (eg. TERM not set). 2018-09-22 14:14:18 -04:00
Maarten Billemont
06ebe954f1 Clarify the interface a bit. 2018-09-22 14:13:00 -04:00
Maarten Billemont
48d4668575 Ensure all read utilities yield constant string pointers for safety. 2018-09-22 14:12:53 -04:00
Maarten Billemont
af768329a3 2.7-java-8 2018-09-13 22:38:45 -04:00
Maarten Billemont
9a04c28054 Fix initialization of text consumers & action handlers on sites list. 2018-09-13 22:37:28 -04:00
Maarten Billemont
ec9c55ec4d Access login names by holding shift. 2018-09-13 15:49:58 -04:00
Maarten Billemont
d8a735e1b1 Improve render speed of lists. 2018-09-13 15:49:42 -04:00
Maarten Billemont
a1eee88a54 Improved search query support. 2018-09-12 13:12:10 -04:00
Maarten Billemont
ac5286853a Write release JARs to site directory. 2018-08-28 02:43:24 -04:00
Maarten Billemont
39f6893742 2.7-java-7 2018-08-27 18:24:46 -04:00
Maarten Billemont
7bf7b8981c Fix duplication of user names in files list. 2018-08-27 18:24:00 -04:00
Maarten Billemont
09abe21fed Release masterpassword-gui-2.7.6 2018-08-27 13:22:03 -04:00
Maarten Billemont
6fae0fe425 2.7-java-6 2018-08-27 13:11:32 -04:00
Maarten Billemont
0558176847 Fix dependencies in native mpw DLLs. 2018-08-27 13:07:38 -04:00
Maarten Billemont
c553201cda Small site update. 2018-08-26 20:41:27 -04:00
Maarten Billemont
665be9494b Release masterpassword-gui-2.7.5 2018-08-26 18:19:01 -04:00
Maarten Billemont
5ca81b4aa7 Don't use daemon when setting release passwords in environment. 2018-08-25 11:57:36 -04:00
Maarten Billemont
3cbb063926 Refactor Native to try and load other architectures. 2018-08-24 16:33:14 -04:00
Maarten Billemont
d5551c8c8c Key calculator and access to the full algorithm. 2018-08-24 13:48:53 -04:00
Maarten Billemont
9a40e52d53 2.7-java-5 2018-08-19 16:13:39 -04:00
Maarten Billemont
6f0d768e69 Saving custom passwords and logins. 2018-08-19 16:11:43 -04:00
Maarten Billemont
40fdc8d248 Fix initialization dependency cycle & load files on init. 2018-08-18 13:43:41 -04:00
Maarten Billemont
6b9e1b8cb8 Standard label font & fix warnings. 2018-08-14 12:06:04 -04:00
Maarten Billemont
f41cdb8742 Site security questions and copy login name. 2018-08-13 17:53:31 -04:00
Maarten Billemont
10c6d203b8 Implement security answers & immediate site lookup. 2018-08-07 00:07:16 -04:00
Maarten Billemont
7d1aa9c9f4 Release 2.7.4 2018-08-02 12:34:24 -04:00
Maarten Billemont
c26281e3b7 2.7-java-4 2018-08-02 12:20:45 -04:00
Maarten Billemont
f0b1f0c9e0 Build for older glibc. 2018-08-02 12:19:49 -04:00
Maarten Billemont
9682efc7c9 Release masterpassword-gui-2.7.3 2018-08-02 01:44:50 -04:00
Maarten Billemont
1264cad377 2.7-java-3 2018-08-02 01:37:37 -04:00
Maarten Billemont
d185a0af14 Add mpw native binary for windows 32-bit. 2018-08-02 01:37:10 -04:00
Maarten Billemont
4275a6cc61 Fix build on Windows. 2018-08-02 01:32:55 -04:00
Maarten Billemont
c94ff429e8 Switch linux build of libmpw to debian for glibc instead of musl libc. 2018-08-01 20:13:42 -04:00
Maarten Billemont
00744cb264 Statically link the mpw library. 2018-08-01 14:20:47 -04:00
Maarten Billemont
7202fe6d1d Bump site for release of masterpassword-gui-2.7.2.jar 2018-07-31 15:35:57 -04:00
Maarten Billemont
63b4d9cd2e 2.7-java-2 2018-07-31 15:32:13 -04:00
Maarten Billemont
36a7c7f423 Clean up iconifying on copy. 2018-07-31 15:31:47 -04:00
Maarten Billemont
c2c4fb18bf Help improvements. 2018-07-31 15:16:33 -04:00
Maarten Billemont
3fc8acba70 Global hotkey, iconifying and application activation, help text. 2018-07-31 14:55:19 -04:00
Maarten Billemont
f5c0c4d787 Fix offsetting local time back to UTC. 2018-07-31 12:44:49 -04:00
Maarten Billemont
86775f1c75 Standardize epoch time calculation. 2018-07-31 09:27:41 -04:00
Maarten Billemont
2bb190f49a Bump site for masterpassword-gui-2.7.1 release. 2018-07-29 15:38:54 -04:00
Maarten Billemont
77c4a2af46 2.7-java-1 2018-07-29 15:29:19 -04:00
Maarten Billemont
3da82d30b1 Add support for creating incognito users. 2018-07-29 15:26:48 -04:00
Maarten Billemont
97532fdce6 Print UI dates in current time zone. 2018-07-29 15:18:54 -04:00
Maarten Billemont
fe63a2756a Fix default type for new sites & site UI updating. 2018-07-29 15:10:45 -04:00
Maarten Billemont
928b617ed0 Import & export users + improved user state tracking. 2018-07-29 14:01:07 -04:00
Maarten Billemont
18ecc41b39 Fix issues with UnsignedIntegerModel in a spinner. 2018-07-29 01:38:58 -04:00
Maarten Billemont
a6e9e89ace Update UI instead of resetting when site is updated. 2018-07-29 01:38:15 -04:00
Maarten Billemont
0b7494ecbf We use JDK9 APIs now. 2018-07-29 00:19:05 -04:00
Maarten Billemont
8377c9c615 JDK9 platform-independent way of managing application events. 2018-07-29 00:08:09 -04:00
Maarten Billemont
37a7cfa530 Support resetting user's master password. 2018-07-28 21:53:08 -04:00
Maarten Billemont
978b758079 New user fixes. 2018-07-28 19:56:20 -04:00
Maarten Billemont
38f09021b3 Button tooltips and improvements. 2018-07-28 18:11:36 -04:00
Maarten Billemont
7455fba55e Adding and deleting users and sites. 2018-07-28 17:52:43 -04:00
Maarten Billemont
8cd9755616 Update GradientPanel's opaque properly. 2018-07-28 14:30:36 -04:00
Maarten Billemont
46d301df94 Site settings & add sites. 2018-07-28 14:03:49 -04:00
Maarten Billemont
e639137304 Avatar configuration & move preferences into user panel. 2018-07-26 15:07:37 -04:00
Maarten Billemont
7c83a62f91 Support for building with JDK 10. 2018-07-26 15:07:17 -04:00
Maarten Billemont
513840e2c4 Read in site questions from json & don't serialize incomplete MPFileUser 2018-07-23 23:59:11 -04:00
Maarten Billemont
8f7faa9e4e User preferences. 2018-07-23 23:34:32 -04:00
Maarten Billemont
16cdcda94b Identicon support and UI improvements. 2018-07-23 11:23:26 -04:00
Maarten Billemont
400ebe59db Implement sites list and copy result. 2018-07-19 13:56:26 -04:00
Maarten Billemont
476a4046e7 Use standard control highlight color for password. 2018-07-18 17:02:51 -04:00
Maarten Billemont
3403449ca2 Cleanup and fix some warnings. 2018-07-18 15:46:06 -04:00
Maarten Billemont
596ace51ea WIP - new Java UI. 2018-07-18 12:27:19 -04:00
Maarten Billemont
80b5fcd785 Refactor model, improved isolation & access unauthenticated file metadata. 2018-07-18 12:23:53 -04:00
Maarten Billemont
a16bc9a318 Don't save changes made to model while it's being read from file. 2018-07-10 00:54:11 -04:00
Maarten Billemont
462dd4e89b Prepare for 2.7 release. 2018-07-10 00:39:52 -04:00
Maarten Billemont
e5ff374a9c Better way of checking implementation-version. 2018-07-10 00:39:35 -04:00
117 changed files with 4288 additions and 2366 deletions

1
.gitignore vendored
View File

@@ -7,6 +7,7 @@ Thumbs.db
*.iml *.iml
*.ipr *.ipr
*.iws *.iws
out
# Xcode IDE # Xcode IDE
xcuserdata/ xcuserdata/

View File

@@ -14,5 +14,5 @@ build_project:
- "( xcodebuild -workspace platform-darwin/MasterPassword.xcworkspace -configuration 'Test' -scheme 'MasterPassword macOS' clean build )" - "( xcodebuild -workspace platform-darwin/MasterPassword.xcworkspace -configuration 'Test' -scheme 'MasterPassword macOS' clean build )"
tags: tags:
- brew - brew
- java - java_9
- xcode_9 - xcode_9

View File

@@ -1,9 +1,12 @@
FROM alpine FROM debian:stable-slim
# For i386 # For i386
#FROM i386/alpine #FROM i386/debian:stable-slim
#ENTRYPOINT ["linux32", "--"] #ENTRYPOINT ["linux32", "--"]
RUN apk add --no-cache git libtool automake autoconf make g++ bash openjdk8 # https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=863199
RUN mkdir -p /usr/share/man/man1
RUN apt-get update && apt-get install -y default-jdk-headless git-core bash libtool automake autoconf make g++
RUN git clone --depth=3 $(: --shallow-submodules) --recurse-submodules https://gitlab.com/MasterPassword/MasterPassword.git /mpw RUN git clone --depth=3 $(: --shallow-submodules) --recurse-submodules https://gitlab.com/MasterPassword/MasterPassword.git /mpw
RUN cd /mpw/gradle && ./gradlew -i clean build RUN cd /mpw/gradle && ./gradlew -i clean build

16
gradle/.idea/misc.xml generated
View File

@@ -5,26 +5,36 @@
<option name="myDefaultNotNull" value="javax.annotation.Nonnull" /> <option name="myDefaultNotNull" value="javax.annotation.Nonnull" />
<option name="myNullables"> <option name="myNullables">
<value> <value>
<list size="4"> <list size="9">
<item index="0" class="java.lang.String" itemvalue="org.jetbrains.annotations.Nullable" /> <item index="0" class="java.lang.String" itemvalue="org.jetbrains.annotations.Nullable" />
<item index="1" class="java.lang.String" itemvalue="javax.annotation.Nullable" /> <item index="1" class="java.lang.String" itemvalue="javax.annotation.Nullable" />
<item index="2" class="java.lang.String" itemvalue="edu.umd.cs.findbugs.annotations.Nullable" /> <item index="2" class="java.lang.String" itemvalue="edu.umd.cs.findbugs.annotations.Nullable" />
<item index="3" class="java.lang.String" itemvalue="android.support.annotation.Nullable" /> <item index="3" class="java.lang.String" itemvalue="android.support.annotation.Nullable" />
<item index="4" class="java.lang.String" itemvalue="javax.annotation.CheckForNull" />
<item index="5" class="java.lang.String" itemvalue="androidx.annotation.Nullable" />
<item index="6" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.qual.Nullable" />
<item index="7" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.compatqual.NullableDecl" />
<item index="8" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.compatqual.NullableType" />
</list> </list>
</value> </value>
</option> </option>
<option name="myNotNulls"> <option name="myNotNulls">
<value> <value>
<list size="4"> <list size="9">
<item index="0" class="java.lang.String" itemvalue="org.jetbrains.annotations.NotNull" /> <item index="0" class="java.lang.String" itemvalue="org.jetbrains.annotations.NotNull" />
<item index="1" class="java.lang.String" itemvalue="javax.annotation.Nonnull" /> <item index="1" class="java.lang.String" itemvalue="javax.annotation.Nonnull" />
<item index="2" class="java.lang.String" itemvalue="edu.umd.cs.findbugs.annotations.NonNull" /> <item index="2" class="java.lang.String" itemvalue="edu.umd.cs.findbugs.annotations.NonNull" />
<item index="3" class="java.lang.String" itemvalue="android.support.annotation.NonNull" /> <item index="3" class="java.lang.String" itemvalue="android.support.annotation.NonNull" />
<item index="4" class="java.lang.String" itemvalue="javax.validation.constraints.NotNull" />
<item index="5" class="java.lang.String" itemvalue="androidx.annotation.NonNull" />
<item index="6" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.qual.NonNull" />
<item index="7" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.compatqual.NonNullDecl" />
<item index="8" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.compatqual.NonNullType" />
</list> </list>
</value> </value>
</option> </option>
</component> </component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" default="false" project-jdk-name="1.8" project-jdk-type="JavaSDK"> <component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" default="false" project-jdk-name="10" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/build/classes" /> <output url="file://$PROJECT_DIR$/build/classes" />
</component> </component>
<component name="ProjectType"> <component name="ProjectType">

View File

@@ -2,11 +2,11 @@ To build a release distribution:
Desktop: Desktop:
STORE_PW=$(mpw masterpassword.keystore) KEY_PW_DESKTOP=$(mpw masterpassword-desktop) gradle clean masterpassword-gui:shadowJar STORE_PW=$(mpw masterpassword.keystore) KEY_PW_DESKTOP=$(mpw masterpassword-desktop) gradle --no-daemon clean masterpassword-gui:shadowJar
Android: Android:
STORE_PW=$(mpw masterpassword.keystore) KEY_PW_ANDROID=$(mpw masterpassword-android) gradle clean masterpassword-android:assembleRelease STORE_PW=$(mpw masterpassword.keystore) KEY_PW_ANDROID=$(mpw masterpassword-android) gradle --no-daemon clean masterpassword-android:assembleRelease
Note: Note:

View File

@@ -2,7 +2,7 @@ allprojects {
apply plugin: 'findbugs' apply plugin: 'findbugs'
group = 'com.lyndir.masterpassword' group = 'com.lyndir.masterpassword'
version = 'GIT-SNAPSHOT' version = '2.7.9'
tasks.withType( JavaCompile ) { tasks.withType( JavaCompile ) {
options.encoding = 'UTF-8' options.encoding = 'UTF-8'

View File

@@ -1,3 +1,4 @@
org.gradle.daemon=true org.gradle.daemon=true
org.gradle.configureondemand=true org.gradle.configureondemand=true
org.gradle.jvmargs=-Xmx1536M org.gradle.jvmargs=-Xmx1536M
android.enableD8.desugaring=true

View File

@@ -24,6 +24,7 @@
# target_prepare() { make -s distclean; } # target_prepare() { make -s distclean; }
# target_configure() { _target_configure "$@" --enable-minimal; } # target_configure() { _target_configure "$@" --enable-minimal; }
set -e set -e
PATH+=:/usr/local/bin
# needs <binary> ... # needs <binary> ...
# #
@@ -31,8 +32,15 @@ set -e
needs() { _needs "$@"; } needs() { _needs "$@"; }
_needs() { _needs() {
local failed=0 local failed=0
for tool; do for spec; do
hash "$tool" || { echo >&2 "Missing: $tool. Please install this tool."; (( failed++ )); } IFS=: read pkg tools <<< "$spec"
IFS=, read -a tools <<< "${tools:-$pkg}"
for tool in "${tools[@]}"; do
hash "$tool" 2>/dev/null && continue 2
done
echo >&2 "Missing: $pkg. Please install this package."
(( failed++ ))
done done
return $failed return $failed
@@ -50,15 +58,15 @@ _initialize() {
# #
# Check if all tools needed for the default implementations are available. # Check if all tools needed for the default implementations are available.
# #
# By default, this will check for `libtool` (for libtoolize), `automake` (for aclocal) and `autoconf` (for autoreconf). # By default, this will check for `libtool` (for libtoolize), `automake` (for aclocal), `autoconf` (for autoreconf) and make.
initialize_needs() { _initialize_needs "$@"; } initialize_needs() { _initialize_needs "$@"; }
_initialize_needs() { _initialize_needs() {
if [[ $platform = windows ]]; then if [[ $platform = windows ]]; then
needs cmd needs cmd
export VSINSTALLDIR="${VSINSTALLDIR:-$(cd "$(cygpath -F 0x002a)/Microsoft Visual Studio"/*/*/Common7/.. && pwd)}" export VSINSTALLDIR="${VSINSTALLDIR:-$(cd "$(cygpath -F 0x002a)/Microsoft Visual Studio"/*/*/Common7/.. && pwd)}"
[[ -e "$VSINSTALLDIR/Common7/Tools/VsMSBuildCmd.bat" ]] || { echo >&2 "Missing: msbuild. Please install 'Build Tools for Visual Studio'."; return 1; } [[ -e "$VSINSTALLDIR/Common7/Tools/VsMSBuildCmd.bat" ]] || { echo >&2 "Missing: msbuild. Please install 'Build Tools for Visual Studio'. See https://visualstudio.microsoft.com/downloads/?q=build+tools"; return 1; }
else else
needs libtool automake autoconf needs libtool:libtoolize,glibtoolize automake autoconf make
fi fi
} }
@@ -194,7 +202,7 @@ _target_build() {
if [[ $platform = windows ]]; then if [[ $platform = windows ]]; then
# I cannot for the life of me figure out how to pass this command directly into cmd. # I cannot for the life of me figure out how to pass this command directly into cmd.
printf '"%%VSINSTALLDIR%%\Common7\Tools\VsMSBuildCmd.bat" && msbuild /t:Rebuild /p:Configuration=ReleaseDLL;Platform=%s;OutDir=%s' "$arch" "$(cygpath -w "${prefix##$PWD/}/$arch/")" > .build.bat printf '"%%VSINSTALLDIR%%\Common7\Tools\VsMSBuildCmd.bat" && msbuild /t:Rebuild /p:Configuration=Release;Platform=%s;OutDir=%s' "$arch" "$(cygpath -w "${prefix##$PWD/}/$arch/")" > .build.bat
cmd //c .build.bat cmd //c .build.bat
rm -f .build.bat rm -f .build.bat
else else
@@ -270,7 +278,10 @@ _finalize_merge() {
# By default, this will run `make clean`. # By default, this will run `make clean`.
finalize_clean() { _finalize_clean "$@"; } finalize_clean() { _finalize_clean "$@"; }
_finalize_clean() { _finalize_clean() {
if [[ $platform = windows ]]; then :
else
[[ ! -e Makefile ]] || make -s clean [[ ! -e Makefile ]] || make -s clean
fi
} }
# build <name> [<platform>] # build <name> [<platform>]

View File

@@ -1,4 +1,13 @@
#!/usr/bin/env bash #!/usr/bin/env bash
source "${BASH_SOURCE%/*}/build_lib" source "${BASH_SOURCE%/*}/build_lib"
finalize_merge() {
local prefix=$1 platform=$2; shift 2
local archs=( "$@" )
cp -a "src/libsodium/include" "$prefix/out"
_finalize_merge "$prefix" "$platform" "${archs[@]}"
}
build libsodium windows build libsodium windows

View File

@@ -1,9 +1,10 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?> <?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="10117" systemVersion="15F34" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES"> <document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="14113" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
<dependencies> <dependencies>
<deployment identifier="macosx"/> <deployment identifier="macosx"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="10117"/> <plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14113"/>
<capability name="box content view" minToolsVersion="7.0"/> <capability name="box content view" minToolsVersion="7.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
<capability name="stacking Non-gravity area distributions on NSStackView" minToolsVersion="7.0" minSystemVersion="10.11"/> <capability name="stacking Non-gravity area distributions on NSStackView" minToolsVersion="7.0" minSystemVersion="10.11"/>
</dependencies> </dependencies>
<objects> <objects>
@@ -27,7 +28,7 @@
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/> <customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
<customObject id="-3" userLabel="Application" customClass="NSObject"/> <customObject id="-3" userLabel="Application" customClass="NSObject"/>
<window title="Master Password" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" oneShot="NO" releasedWhenClosed="NO" showsToolbarButton="NO" visibleAtLaunch="NO" animationBehavior="default" id="QvC-M9-y7g" customClass="MPSitesWindow"> <window title="Master Password" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" oneShot="NO" releasedWhenClosed="NO" showsToolbarButton="NO" visibleAtLaunch="NO" animationBehavior="default" id="QvC-M9-y7g" customClass="MPSitesWindow">
<windowStyleMask key="styleMask" texturedBackground="YES" unifiedTitleAndToolbar="YES" fullSizeContentView="YES"/> <windowStyleMask key="styleMask" texturedBackground="YES" fullSizeContentView="YES"/>
<windowCollectionBehavior key="collectionBehavior" moveToActiveSpace="YES" transient="YES" ignoresCycle="YES" fullScreenAuxiliary="YES"/> <windowCollectionBehavior key="collectionBehavior" moveToActiveSpace="YES" transient="YES" ignoresCycle="YES" fullScreenAuxiliary="YES"/>
<rect key="contentRect" x="0.0" y="0.0" width="640" height="577"/> <rect key="contentRect" x="0.0" y="0.0" width="640" height="577"/>
<rect key="screenRect" x="0.0" y="0.0" width="1440" height="877"/> <rect key="screenRect" x="0.0" y="0.0" width="1440" height="877"/>
@@ -35,7 +36,7 @@
<rect key="frame" x="0.0" y="0.0" width="640" height="577"/> <rect key="frame" x="0.0" y="0.0" width="640" height="577"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews> <subviews>
<visualEffectView blendingMode="behindWindow" state="followsWindowActiveState" translatesAutoresizingMaskIntoConstraints="NO" id="eRe-Ef-AZx"> <visualEffectView blendingMode="behindWindow" material="appearanceBased" state="followsWindowActiveState" translatesAutoresizingMaskIntoConstraints="NO" id="eRe-Ef-AZx">
<rect key="frame" x="0.0" y="0.0" width="640" height="577"/> <rect key="frame" x="0.0" y="0.0" width="640" height="577"/>
</visualEffectView> </visualEffectView>
<progressIndicator hidden="YES" wantsLayer="YES" horizontalHuggingPriority="750" verticalHuggingPriority="750" maxValue="100" displayedWhenStopped="NO" bezeled="NO" indeterminate="YES" controlSize="small" style="spinning" translatesAutoresizingMaskIntoConstraints="NO" id="oSh-Ec-8Nf" userLabel="Progress Spinner"> <progressIndicator hidden="YES" wantsLayer="YES" horizontalHuggingPriority="750" verticalHuggingPriority="750" maxValue="100" displayedWhenStopped="NO" bezeled="NO" indeterminate="YES" controlSize="small" style="spinning" translatesAutoresizingMaskIntoConstraints="NO" id="oSh-Ec-8Nf" userLabel="Progress Spinner">
@@ -45,7 +46,7 @@
<rect key="frame" x="20" y="383" width="600" height="150"/> <rect key="frame" x="20" y="383" width="600" height="150"/>
<subviews> <subviews>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="Ond-dT-x5d" userLabel="Site Password Label"> <textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="Ond-dT-x5d" userLabel="Site Password Label">
<rect key="frame" x="157" y="116" width="285" height="14"/> <rect key="frame" x="157" y="116" width="286" height="14"/>
<shadow key="shadow" blurRadius="0.5"> <shadow key="shadow" blurRadius="0.5">
<size key="offset" width="0.0" height="1"/> <size key="offset" width="0.0" height="1"/>
<color key="color" name="controlLightHighlightColor" catalog="System" colorSpace="catalog"/> <color key="color" name="controlLightHighlightColor" catalog="System" colorSpace="catalog"/>
@@ -73,7 +74,7 @@
</connections> </connections>
</textField> </textField>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="Ia6-7b-dFr"> <textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="Ia6-7b-dFr">
<rect key="frame" x="115" y="68" width="370" height="14"/> <rect key="frame" x="127" y="68" width="347" height="14"/>
<shadow key="shadow" blurRadius="0.5"> <shadow key="shadow" blurRadius="0.5">
<size key="offset" width="0.0" height="1"/> <size key="offset" width="0.0" height="1"/>
<color key="color" name="controlLightHighlightColor" catalog="System" colorSpace="catalog"/> <color key="color" name="controlLightHighlightColor" catalog="System" colorSpace="catalog"/>
@@ -186,7 +187,7 @@
</contentFilters> </contentFilters>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="center" title="KeljXoleKowi9@" placeholderString="" id="WVV-EE-tkB"> <textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="center" title="KeljXoleKowi9@" placeholderString="" id="WVV-EE-tkB">
<font key="font" size="64" name="SourceCodePro-Regular"/> <font key="font" size="64" name="SourceCodePro-Regular"/>
<color key="textColor" name="keyboardFocusIndicatorColor" catalog="System" colorSpace="catalog"/> <color key="textColor" name="selectedControlColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/> <color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell> </textFieldCell>
<connections> <connections>
@@ -223,13 +224,13 @@
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews> <subviews>
<tableView verticalHuggingPriority="750" allowsExpansionToolTips="YES" columnReordering="NO" columnResizing="NO" multipleSelection="NO" emptySelection="NO" autosaveColumns="NO" rowHeight="33" rowSizeStyle="automatic" viewBased="YES" floatsGroupRows="NO" id="xvJ-5c-vDp" customClass="MPSitesTableView"> <tableView verticalHuggingPriority="750" allowsExpansionToolTips="YES" columnReordering="NO" columnResizing="NO" multipleSelection="NO" emptySelection="NO" autosaveColumns="NO" rowHeight="33" rowSizeStyle="automatic" viewBased="YES" floatsGroupRows="NO" id="xvJ-5c-vDp" customClass="MPSitesTableView">
<rect key="frame" x="0.0" y="0.0" width="515" height="0.0"/> <rect key="frame" x="0.0" y="0.0" width="515" height="180"/>
<autoresizingMask key="autoresizingMask"/> <autoresizingMask key="autoresizingMask"/>
<size key="intercellSpacing" width="3" height="2"/> <size key="intercellSpacing" width="3" height="2"/>
<color key="backgroundColor" white="1" alpha="0.0" colorSpace="deviceWhite"/> <color key="backgroundColor" white="1" alpha="0.0" colorSpace="deviceWhite"/>
<color key="gridColor" name="gridColor" catalog="System" colorSpace="catalog"/> <color key="gridColor" name="gridColor" catalog="System" colorSpace="catalog"/>
<tableColumns> <tableColumns>
<tableColumn editable="NO" width="512" minWidth="512" maxWidth="512" id="S71-gk-yF7"> <tableColumn identifier="" editable="NO" width="512" minWidth="512" maxWidth="512" id="S71-gk-yF7">
<tableHeaderCell key="headerCell" lineBreakMode="truncatingTail" borderStyle="border" alignment="left"> <tableHeaderCell key="headerCell" lineBreakMode="truncatingTail" borderStyle="border" alignment="left">
<font key="font" metaFont="smallSystem"/> <font key="font" metaFont="smallSystem"/>
<color key="textColor" name="headerTextColor" catalog="System" colorSpace="catalog"/> <color key="textColor" name="headerTextColor" catalog="System" colorSpace="catalog"/>
@@ -312,7 +313,7 @@
</connections> </connections>
</scrollView> </scrollView>
<customView translatesAutoresizingMaskIntoConstraints="NO" id="nM8-O3-spM" customClass="MPGradientView"> <customView translatesAutoresizingMaskIntoConstraints="NO" id="nM8-O3-spM" customClass="MPGradientView">
<rect key="frame" x="0.0" y="0.0" width="640" height="212"/> <rect key="frame" x="0.0" y="0.0" width="640" height="214"/>
<userDefinedRuntimeAttributes> <userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="color" keyPath="startingColor"> <userDefinedRuntimeAttribute type="color" keyPath="startingColor">
<color key="value" red="0.7019608021" green="0.7019608021" blue="0.7019608021" alpha="0.0" colorSpace="calibratedRGB"/> <color key="value" red="0.7019608021" green="0.7019608021" blue="0.7019608021" alpha="0.0" colorSpace="calibratedRGB"/>
@@ -570,7 +571,7 @@ Use the arrows ⇅ to navigate the list or esc ⎋ to exit.</string>
</connections> </connections>
</textField> </textField>
<stackView distribution="fill" orientation="horizontal" alignment="bottom" horizontalStackHuggingPriority="249.99998474121094" verticalStackHuggingPriority="249.99998474121094" detachesHiddenViews="YES" translatesAutoresizingMaskIntoConstraints="NO" id="pHt-gg-ZNX"> <stackView distribution="fill" orientation="horizontal" alignment="bottom" horizontalStackHuggingPriority="249.99998474121094" verticalStackHuggingPriority="249.99998474121094" detachesHiddenViews="YES" translatesAutoresizingMaskIntoConstraints="NO" id="pHt-gg-ZNX">
<rect key="frame" x="72" y="20" width="495" height="152"/> <rect key="frame" x="72" y="20" width="495" height="154"/>
<subviews> <subviews>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="1Qo-iG-CQt"> <button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="1Qo-iG-CQt">
<rect key="frame" x="0.0" y="-1" width="85" height="19"/> <rect key="frame" x="0.0" y="-1" width="85" height="19"/>
@@ -593,10 +594,10 @@ Use the arrows ⇅ to navigate the list or esc ⎋ to exit.</string>
</connections> </connections>
</button> </button>
<stackView distribution="fill" orientation="vertical" alignment="centerX" horizontalStackHuggingPriority="249.99998474121094" verticalStackHuggingPriority="249.99998474121094" detachesHiddenViews="YES" translatesAutoresizingMaskIntoConstraints="NO" id="DT0-RU-3LT"> <stackView distribution="fill" orientation="vertical" alignment="centerX" horizontalStackHuggingPriority="249.99998474121094" verticalStackHuggingPriority="249.99998474121094" detachesHiddenViews="YES" translatesAutoresizingMaskIntoConstraints="NO" id="DT0-RU-3LT">
<rect key="frame" x="93" y="0.0" width="177" height="152"/> <rect key="frame" x="93" y="0.0" width="177" height="154"/>
<subviews> <subviews>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="uol-dE-I8H"> <textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="uol-dE-I8H">
<rect key="frame" x="77" y="138" width="23" height="14"/> <rect key="frame" x="77" y="140" width="23" height="14"/>
<shadow key="shadow" blurRadius="0.5"> <shadow key="shadow" blurRadius="0.5">
<size key="offset" width="0.0" height="1"/> <size key="offset" width="0.0" height="1"/>
<color key="color" name="controlLightHighlightColor" catalog="System" colorSpace="catalog"/> <color key="color" name="controlLightHighlightColor" catalog="System" colorSpace="catalog"/>
@@ -633,7 +634,7 @@ Use the arrows ⇅ to navigate the list or esc ⎋ to exit.</string>
</connections> </connections>
</textField> </textField>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="brI-fg-Kav"> <button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="brI-fg-Kav">
<rect key="frame" x="40" y="111" width="96" height="19"/> <rect key="frame" x="41" y="113" width="96" height="19"/>
<shadow key="shadow"> <shadow key="shadow">
<color key="color" name="controlLightHighlightColor" catalog="System" colorSpace="catalog"/> <color key="color" name="controlLightHighlightColor" catalog="System" colorSpace="catalog"/>
</shadow> </shadow>
@@ -662,7 +663,7 @@ Use the arrows ⇅ to navigate the list or esc ⎋ to exit.</string>
</connections> </connections>
</button> </button>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="R46-fx-n14"> <button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="R46-fx-n14">
<rect key="frame" x="0.0" y="85" width="177" height="19"/> <rect key="frame" x="0.0" y="87" width="177" height="19"/>
<shadow key="shadow"> <shadow key="shadow">
<color key="color" name="controlLightHighlightColor" catalog="System" colorSpace="catalog"/> <color key="color" name="controlLightHighlightColor" catalog="System" colorSpace="catalog"/>
</shadow> </shadow>
@@ -688,10 +689,10 @@ Use the arrows ⇅ to navigate the list or esc ⎋ to exit.</string>
</connections> </connections>
</button> </button>
<stackView distribution="fill" orientation="horizontal" alignment="top" horizontalStackHuggingPriority="249.99998474121094" verticalStackHuggingPriority="249.99998474121094" detachesHiddenViews="YES" translatesAutoresizingMaskIntoConstraints="NO" id="Bgn-Ne-fQ7" userLabel="Version Container"> <stackView distribution="fill" orientation="horizontal" alignment="top" horizontalStackHuggingPriority="249.99998474121094" verticalStackHuggingPriority="249.99998474121094" detachesHiddenViews="YES" translatesAutoresizingMaskIntoConstraints="NO" id="Bgn-Ne-fQ7" userLabel="Version Container">
<rect key="frame" x="70" y="56" width="36" height="22"/> <rect key="frame" x="71" y="57" width="36" height="23"/>
<subviews> <subviews>
<stepper horizontalHuggingPriority="750" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="mcq-qD-yte"> <stepper horizontalHuggingPriority="750" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="mcq-qD-yte">
<rect key="frame" x="-3" y="-3" width="19" height="27"/> <rect key="frame" x="-3" y="-2" width="19" height="27"/>
<shadow key="shadow"> <shadow key="shadow">
<color key="color" name="controlLightHighlightColor" catalog="System" colorSpace="catalog"/> <color key="color" name="controlLightHighlightColor" catalog="System" colorSpace="catalog"/>
</shadow> </shadow>
@@ -701,7 +702,7 @@ Use the arrows ⇅ to navigate the list or esc ⎋ to exit.</string>
</connections> </connections>
</stepper> </stepper>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="gyg-Fh-yn7"> <textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="gyg-Fh-yn7">
<rect key="frame" x="19" y="3" width="19" height="19"/> <rect key="frame" x="19" y="4" width="19" height="19"/>
<shadow key="shadow" blurRadius="0.5"> <shadow key="shadow" blurRadius="0.5">
<size key="offset" width="0.0" height="1"/> <size key="offset" width="0.0" height="1"/>
<color key="color" name="controlLightHighlightColor" catalog="System" colorSpace="catalog"/> <color key="color" name="controlLightHighlightColor" catalog="System" colorSpace="catalog"/>
@@ -742,10 +743,10 @@ Use the arrows ⇅ to navigate the list or esc ⎋ to exit.</string>
</connections> </connections>
</stackView> </stackView>
<stackView distribution="fill" orientation="horizontal" alignment="centerY" horizontalStackHuggingPriority="249.99998474121094" verticalStackHuggingPriority="249.99998474121094" detachesHiddenViews="YES" translatesAutoresizingMaskIntoConstraints="NO" id="6II-KA-cNi" userLabel="Counter Container"> <stackView distribution="fill" orientation="horizontal" alignment="centerY" horizontalStackHuggingPriority="249.99998474121094" verticalStackHuggingPriority="249.99998474121094" detachesHiddenViews="YES" translatesAutoresizingMaskIntoConstraints="NO" id="6II-KA-cNi" userLabel="Counter Container">
<rect key="frame" x="74" y="26" width="28" height="22"/> <rect key="frame" x="75" y="26" width="28" height="23"/>
<subviews> <subviews>
<stepper horizontalHuggingPriority="750" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="XgA-Vl-CKh" userLabel="Counter Stepper"> <stepper horizontalHuggingPriority="750" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="XgA-Vl-CKh" userLabel="Counter Stepper">
<rect key="frame" x="-3" y="-3" width="19" height="27"/> <rect key="frame" x="-3" y="-2" width="19" height="27"/>
<shadow key="shadow"> <shadow key="shadow">
<color key="color" name="controlLightHighlightColor" catalog="System" colorSpace="catalog"/> <color key="color" name="controlLightHighlightColor" catalog="System" colorSpace="catalog"/>
</shadow> </shadow>
@@ -755,7 +756,7 @@ Use the arrows ⇅ to navigate the list or esc ⎋ to exit.</string>
</connections> </connections>
</stepper> </stepper>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="NvO-kt-eZ2" userLabel="Counter Field"> <textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="NvO-kt-eZ2" userLabel="Counter Field">
<rect key="frame" x="19" y="1" width="11" height="19"/> <rect key="frame" x="19" y="2" width="11" height="19"/>
<shadow key="shadow" blurRadius="0.5"> <shadow key="shadow" blurRadius="0.5">
<size key="offset" width="0.0" height="1"/> <size key="offset" width="0.0" height="1"/>
<color key="color" name="controlLightHighlightColor" catalog="System" colorSpace="catalog"/> <color key="color" name="controlLightHighlightColor" catalog="System" colorSpace="catalog"/>
@@ -888,7 +889,7 @@ Use the arrows ⇅ to navigate the list or esc ⎋ to exit.</string>
</customSpacing> </customSpacing>
</stackView> </stackView>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="luC-0j-BeV"> <textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="luC-0j-BeV">
<rect key="frame" x="134" y="50" width="103" height="14"/> <rect key="frame" x="135" y="51" width="103" height="14"/>
<shadow key="shadow" blurRadius="0.5"> <shadow key="shadow" blurRadius="0.5">
<size key="offset" width="0.0" height="1"/> <size key="offset" width="0.0" height="1"/>
<color key="color" name="controlLightHighlightColor" catalog="System" colorSpace="catalog"/> <color key="color" name="controlLightHighlightColor" catalog="System" colorSpace="catalog"/>
@@ -925,7 +926,7 @@ Use the arrows ⇅ to navigate the list or esc ⎋ to exit.</string>
</connections> </connections>
</textField> </textField>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="gjx-bt-fKM"> <textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="gjx-bt-fKM">
<rect key="frame" x="133" y="80" width="100" height="14"/> <rect key="frame" x="134" y="82" width="100" height="14"/>
<shadow key="shadow" blurRadius="0.5"> <shadow key="shadow" blurRadius="0.5">
<size key="offset" width="0.0" height="1"/> <size key="offset" width="0.0" height="1"/>
<color key="color" name="controlLightHighlightColor" catalog="System" colorSpace="catalog"/> <color key="color" name="controlLightHighlightColor" catalog="System" colorSpace="catalog"/>
@@ -962,7 +963,7 @@ Use the arrows ⇅ to navigate the list or esc ⎋ to exit.</string>
</connections> </connections>
</textField> </textField>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="dbM-ja-dKO" userLabel="Version Tip"> <textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="dbM-ja-dKO" userLabel="Version Tip">
<rect key="frame" x="87" y="106" width="332" height="14"/> <rect key="frame" x="88" y="108" width="332" height="14"/>
<shadow key="shadow" blurRadius="0.5"> <shadow key="shadow" blurRadius="0.5">
<size key="offset" width="0.0" height="1"/> <size key="offset" width="0.0" height="1"/>
<color key="color" name="controlLightHighlightColor" catalog="System" colorSpace="catalog"/> <color key="color" name="controlLightHighlightColor" catalog="System" colorSpace="catalog"/>
@@ -1170,7 +1171,7 @@ Use the arrows ⇅ to navigate the list or esc ⎋ to exit.</string>
<binding destination="-2" name="contentArray" keyPath="sites" id="c96-Dv-HK1"/> <binding destination="-2" name="contentArray" keyPath="sites" id="c96-Dv-HK1"/>
</connections> </connections>
</arrayController> </arrayController>
<box autoresizesSubviews="NO" title="Choose a password type for apple.com:" borderType="line" id="bZe-7q-i6q"> <box autoresizesSubviews="NO" borderType="line" title="Choose a password type for apple.com:" id="bZe-7q-i6q">
<rect key="frame" x="0.0" y="0.0" width="416" height="296"/> <rect key="frame" x="0.0" y="0.0" width="416" height="296"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<view key="contentView" id="hAc-y9-IMT"> <view key="contentView" id="hAc-y9-IMT">
@@ -1243,11 +1244,9 @@ Use the arrows ⇅ to navigate the list or esc ⎋ to exit.</string>
<constraint firstItem="3fr-Fd-pxx" firstAttribute="top" secondItem="hAc-y9-IMT" secondAttribute="top" constant="8" id="xVT-HC-qsE"/> <constraint firstItem="3fr-Fd-pxx" firstAttribute="top" secondItem="hAc-y9-IMT" secondAttribute="top" constant="8" id="xVT-HC-qsE"/>
</constraints> </constraints>
</view> </view>
<color key="borderColor" white="0.0" alpha="0.41999999999999998" colorSpace="calibratedWhite"/>
<color key="fillColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
<point key="canvasLocation" x="333" y="125"/> <point key="canvasLocation" x="333" y="125"/>
</box> </box>
<box autoresizesSubviews="NO" title="Answer to security questions for apple.com:" borderType="line" id="hi3-SX-Td3"> <box autoresizesSubviews="NO" borderType="line" title="Answer to security questions for apple.com:" id="hi3-SX-Td3">
<rect key="frame" x="0.0" y="0.0" width="416" height="180"/> <rect key="frame" x="0.0" y="0.0" width="416" height="180"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<view key="contentView" wantsLayer="YES" id="8Ep-zH-Nzv"> <view key="contentView" wantsLayer="YES" id="8Ep-zH-Nzv">
@@ -1321,8 +1320,6 @@ Use the arrows ⇅ to navigate the list or esc ⎋ to exit.</string>
<constraint firstAttribute="trailing" secondItem="12d-V9-LDB" secondAttribute="trailing" constant="12" id="Ysu-F5-ukt"/> <constraint firstAttribute="trailing" secondItem="12d-V9-LDB" secondAttribute="trailing" constant="12" id="Ysu-F5-ukt"/>
<constraint firstItem="12d-V9-LDB" firstAttribute="leading" secondItem="hi3-SX-Td3" secondAttribute="leading" constant="12" id="oPv-4N-T9I"/> <constraint firstItem="12d-V9-LDB" firstAttribute="leading" secondItem="hi3-SX-Td3" secondAttribute="leading" constant="12" id="oPv-4N-T9I"/>
</constraints> </constraints>
<color key="borderColor" white="0.0" alpha="0.41999999999999998" colorSpace="calibratedWhite"/>
<color key="fillColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
<point key="canvasLocation" x="333" y="491"/> <point key="canvasLocation" x="333" y="491"/>
</box> </box>
</objects> </objects>

View File

@@ -1,10 +1,14 @@
#!/usr/bin/env bash #!/usr/bin/env bash
# #
# USAGE # USAGE
# [targets='...'] [mpw_feature=0|1 ...] [CFLAGS='...'] [LDFLAGS='...'] ./build [cc arguments ...] # [targets='...'] [mpw_feature=0|1 ...] [CFLAGS='...'] [LDFLAGS='...'] ./build [-v|-d|-h|--] [cc arguments ...]
# #
# By default, you should only need to run ./build # By default, you should only need to run ./build
# #
# -v: verbose mode, outputs state information and compiler commands.
# -d: debug build, modifies default build flags to produce binaries best suited for debugging.
# -h: show this usage information.
#
# You can customize the targets that are built using targets='...'. Use targets='all' to build all targets. # You can customize the targets that are built using targets='...'. Use targets='all' to build all targets.
# By default, we only build the 'mpw' target. # By default, we only build the 'mpw' target.
# See targets_all for all possible targets as well as the features they support and require. # See targets_all for all possible targets as well as the features they support and require.
@@ -27,29 +31,53 @@ set -e
### CONFIGURATION ### CONFIGURATION
# Targets to build. verbose=0
# Options
while getopts :vdh opt; do
case $opt in
v) verbose=1 ;;
d) debug=1 ;;
h|?) sed -n '/^[^#]/q;p' "${BASH_SOURCE##*/}"; exit ;;
esac
done
shift "$(( OPTIND - 1 ))"
# Targets to build
targets_all=( targets_all=(
mpw # C CLI version of Master Password (needs: mpw_sodium, optional: mpw_color, mpw_json). mpw # C CLI version of Master Password (needs: mpw_sodium, optional: mpw_color, mpw_json).
mpw-bench # C CLI Master Password benchmark utility (needs: mpw_sodium). mpw-bench # C CLI Master Password benchmark utility (needs: mpw_sodium).
mpw-tests # C Master Password algorithm test suite (needs: mpw_sodium, mpw_xml). mpw-tests # C Master Password algorithm test suite (needs: mpw_sodium, mpw_xml).
) )
targets_default='mpw' # Override with: targets='...' ./build targets_default='mpw' # Override with: targets='...' ./build
targets=${targets[*]:-$targets_default}
# Features. # Features
mpw_sodium=${mpw_sodium:-1} # Implement crypto functions with sodium (depends on libsodium). mpw_sodium=${mpw_sodium:-1} # Implement crypto functions with sodium (depends on libsodium).
mpw_json=${mpw_json:-1} # Support JSON-based user configuration format (depends on libjson-c). mpw_json=${mpw_json:-1} # Support JSON-based user configuration format (depends on libjson-c).
mpw_color=${mpw_color:-1} # Colorized identicon (depends on libncurses). mpw_color=${mpw_color:-1} # Colorized identicon (depends on libncurses).
mpw_xml=${mpw_xml:-1} # XML parsing (depends on libxml2). mpw_xml=${mpw_xml:-1} # XML parsing (depends on libxml2).
# Default build flags. # Default build flags
cflags=( -O3 $CFLAGS ) cflags=( -O3 $CFLAGS ); unset CFLAGS
ldflags=( $LDFLAGS ) ldflags=( $LDFLAGS ); unset LDFLAGS
if (( debug )); then
cflags+=( -O0 -g )
fi
# Version. # Version
if { mpw_version=$(git describe --match '*-cli*' --long --dirty) || mpw_version=$(<VERSION); } 2>/dev/null; then if { mpw_version=$(git describe --match '*-cli*' --long --dirty) || mpw_version=$(<VERSION); } 2>/dev/null; then
cflags+=( -D"MP_VERSION=$mpw_version" ) cflags+=( -D"MP_VERSION=$mpw_version" )
fi fi
echo 2>&1 "Current mpw source version ${mpw_version:-<unknown>}..." echo "Current mpw source version ${mpw_version:-<unknown>}..."
# Meta
if (( verbose )); then
echo "mpw_sodium=${mpw_sodium}, mpw_json=${mpw_json}, mpw_color=${mpw_color}, mpw_xml=${mpw_xml}"
echo "CFLAGS: ${cflags[*]}"
echo "LDFLAGS: ${ldflags[*]}"
echo "targets: ${targets[*]}"
fi
### TARGET: MPW ### TARGET: MPW
@@ -132,18 +160,20 @@ mpw-tests() {
haslib() { haslib() {
cc -x c "${ldflags[@]}" -l"$1" -o /dev/null - <<< 'int main() { return 0; }' &>/dev/null cc -x c "${ldflags[@]}" -l"$1" -o /dev/null - <<< 'int main() { return 0; }' &>/dev/null
} }
cc() { cc() (
if hash llvm-gcc 2>/dev/null; then (( verbose )) && set -x
if { hash llvm-gcc; } 2>/dev/null; then
llvm-gcc "$@" llvm-gcc "$@"
elif hash gcc 2>/dev/null; then elif { hash gcc; } 2>/dev/null; then
gcc -std=c11 "$@" gcc -std=c11 "$@"
elif hash clang 2>/dev/null; then elif { hash clang; } 2>/dev/null; then
clang "$@" clang "$@"
else else
echo >&2 "Need a compiler. Please install GCC or LLVM." echo >&2 "Need a compiler. Please install GCC or LLVM."
exit 1 exit 1
fi fi
} )
### DEPENDENCIES ### DEPENDENCIES
@@ -156,7 +186,7 @@ use() {
for lib in "$lib" "$@"; do for lib in "$lib" "$@"; do
haslib "$lib" && ldflags+=( -l"$lib" ) haslib "$lib" && ldflags+=( -l"$lib" )
done done
echo >&2 "INFO: Enabled $option (lib$lib)." echo "INFO: Enabled $option (lib$lib)."
return 0 return 0
elif [[ $requisite == required ]]; then elif [[ $requisite == required ]]; then
@@ -174,7 +204,7 @@ use() {
exit 1 exit 1
else else
echo >&2 "INFO: $option is supported but not enabled." echo "INFO: $option is supported but not enabled."
return 1 return 1
fi fi
} }
@@ -192,13 +222,13 @@ use_mpw_json() {
} }
use_mpw_xml() { use_mpw_xml() {
local requisite=$1 local requisite=$1
use mpw_xml "$requisite" xml2 && cflags+=( -D"MPW_XML=1" -I"/usr/include/libxml2" -I"/usr/local/include/libxml2" ) ||: use mpw_xml "$requisite" xml2 && cflags+=( $(xml2-config --cflags) ) ldflags+=( $(xml2-config --libs) ) ||:
} }
### BUILD TARGETS ### BUILD TARGETS
for target in "${targets_all[@]}"; do for target in "${targets_all[@]}"; do
if [[ ${targets:-$targets_default} == 'all' || " ${targets:-$targets_default} " = *" $target "* ]]; then if [[ $targets == 'all' || " $targets " = *" $target "* ]]; then
echo echo
echo "Building target: $target..." echo "Building target: $target..."
( "$target" "$@" ) ( "$target" "$@" )

View File

@@ -43,7 +43,7 @@ const char *mpw_getenv(const char *variableName) {
return envBuf? mpw_strdup( envBuf ): NULL; return envBuf? mpw_strdup( envBuf ): NULL;
} }
char *mpw_askpass(const char *prompt) { const char *mpw_askpass(const char *prompt) {
const char *askpass = mpw_getenv( MP_ENV_askpass ); const char *askpass = mpw_getenv( MP_ENV_askpass );
if (!askpass) if (!askpass)
@@ -74,7 +74,7 @@ char *mpw_askpass(const char *prompt) {
} }
close( pipes[1] ); close( pipes[1] );
char *answer = mpw_read_fd( pipes[0] ); const char *answer = mpw_read_fd( pipes[0] );
close( pipes[0] ); close( pipes[0] );
int status; int status;
if (waitpid( pid, &status, 0 ) == ERR) { if (waitpid( pid, &status, 0 ) == ERR) {
@@ -86,7 +86,7 @@ char *mpw_askpass(const char *prompt) {
if (WIFEXITED( status ) && WEXITSTATUS( status ) == EXIT_SUCCESS && answer && strlen( answer )) { if (WIFEXITED( status ) && WEXITSTATUS( status ) == EXIT_SUCCESS && answer && strlen( answer )) {
// Remove trailing newline. // Remove trailing newline.
if (answer[strlen( answer ) - 1] == '\n') if (answer[strlen( answer ) - 1] == '\n')
answer[strlen( answer ) - 1] = '\0'; mpw_replace_string( answer, mpw_strndup( answer, strlen( answer ) - 1 ) );
return answer; return answer;
} }
@@ -97,13 +97,14 @@ char *mpw_askpass(const char *prompt) {
static const char *_mpw_getline(const char *prompt, bool silent) { static const char *_mpw_getline(const char *prompt, bool silent) {
// Get answer from askpass. // Get answer from askpass.
char *answer = mpw_askpass( prompt ); const char *answer = mpw_askpass( prompt );
if (answer) if (answer)
return answer; return answer;
#if MPW_COLOR #if MPW_COLOR
// Initialize a curses screen. // Initialize a curses screen.
SCREEN *screen = newterm( NULL, stderr, stdin ); SCREEN *screen = newterm( NULL, stderr, stdin );
if (screen) {
start_color(); start_color();
init_pair( 1, COLOR_WHITE, COLOR_BLUE ); init_pair( 1, COLOR_WHITE, COLOR_BLUE );
init_pair( 2, COLOR_BLACK, COLOR_WHITE ); init_pair( 2, COLOR_BLACK, COLOR_WHITE );
@@ -135,7 +136,8 @@ static const char *_mpw_getline(const char *prompt, bool silent) {
noecho(); noecho();
result = mvgetnstr( rows / 2 + 1, (cols - 1) / 2, str, MPW_MAX_INPUT ); result = mvgetnstr( rows / 2 + 1, (cols - 1) / 2, str, MPW_MAX_INPUT );
echo(); echo();
} else { }
else {
mvprintw( rows / 2 + 1, (cols - (MPW_MAX_INPUT + 2)) / 2, "%*s", MPW_MAX_INPUT + 2, "" ); mvprintw( rows / 2 + 1, (cols - (MPW_MAX_INPUT + 2)) / 2, "%*s", MPW_MAX_INPUT + 2, "" );
refresh(); refresh();
@@ -147,21 +149,22 @@ static const char *_mpw_getline(const char *prompt, bool silent) {
delscreen( screen ); delscreen( screen );
return result == ERR? NULL: mpw_strndup( str, MPW_MAX_INPUT ); return result == ERR? NULL: mpw_strndup( str, MPW_MAX_INPUT );
#else }
#endif
// Get password from terminal. // Get password from terminal.
fprintf( stderr, "%s ", prompt ); fprintf( stderr, "%s ", prompt );
size_t bufSize = 0; size_t bufSize = 0;
ssize_t lineSize = getline( &answer, &bufSize, stdin ); ssize_t lineSize = getline( (char **)&answer, &bufSize, stdin );
if (lineSize <= 1) { if (lineSize <= 1) {
mpw_free_string( &answer ); mpw_free_string( &answer );
return NULL; return NULL;
} }
// Remove trailing newline. // Remove trailing newline.
answer[lineSize - 1] = '\0'; mpw_replace_string( answer, mpw_strndup( answer, (size_t)lineSize - 1 ) );
return answer; return answer;
#endif
} }
const char *mpw_getline(const char *prompt) { const char *mpw_getline(const char *prompt) {
@@ -250,7 +253,7 @@ bool mpw_mkdirs(const char *filePath) {
return success; return success;
} }
char *mpw_read_fd(int fd) { const char *mpw_read_fd(int fd) {
char *buf = NULL; char *buf = NULL;
size_t blockSize = 4096, bufSize = 0, bufOffset = 0; size_t blockSize = 4096, bufSize = 0, bufOffset = 0;
@@ -263,7 +266,7 @@ char *mpw_read_fd(int fd) {
return buf; return buf;
} }
char *mpw_read_file(FILE *file) { const char *mpw_read_file(FILE *file) {
if (!file) if (!file)
return NULL; return NULL;

View File

@@ -36,7 +36,7 @@ const char *mpw_getenv(const char *variableName);
/** Use the askpass program to prompt the user. /** Use the askpass program to prompt the user.
* @return A newly allocated string or NULL if askpass is not supported or an error occurred. */ * @return A newly allocated string or NULL if askpass is not supported or an error occurred. */
char *mpw_askpass(const char *prompt); const char *mpw_askpass(const char *prompt);
/** Ask the user a question. /** Ask the user a question.
* @return A newly allocated string or NULL if an error occurred trying to read from the user. */ * @return A newly allocated string or NULL if an error occurred trying to read from the user. */
@@ -58,11 +58,11 @@ bool mpw_mkdirs(const char *filePath);
/** Read until EOF from the given file descriptor. /** Read until EOF from the given file descriptor.
* @return A newly allocated string or NULL the read buffer couldn't be allocated. */ * @return A newly allocated string or NULL the read buffer couldn't be allocated. */
char *mpw_read_fd(int fd); const char *mpw_read_fd(int fd);
/** Read the file contents of a given file. /** Read the file contents of a given file.
* @return A newly allocated string or NULL the read buffer couldn't be allocated. */ * @return A newly allocated string or NULL the read buffer couldn't be allocated. */
char *mpw_read_file(FILE *file); const char *mpw_read_file(FILE *file);
/** Encode a visual fingerprint for a user. /** Encode a visual fingerprint for a user.
* @return A newly allocated string. */ * @return A newly allocated string. */

View File

@@ -476,8 +476,8 @@ void cli_user(Arguments *args, Operation *operation) {
else { else {
// Read file. // Read file.
char *sitesInputData = mpw_read_file( sitesFile ); const char *sitesInputData = mpw_read_file( sitesFile );
if (ferror( sitesFile )) if (!sitesInputData || ferror( sitesFile ))
wrn( "Error while reading configuration file:\n %s: %d", operation->sitesPath, ferror( sitesFile ) ); wrn( "Error while reading configuration file:\n %s: %d", operation->sitesPath, ferror( sitesFile ) );
fclose( sitesFile ); fclose( sitesFile );

View File

@@ -14,13 +14,12 @@ artifacts {
from 'lib' from 'lib'
components.withType( ComponentWithRuntimeFile ) { components.withType( ComponentWithRuntimeFile ) {
if (isOptimized()) { if (optimized)
from runtimeFile, { from runtimeFile, {
into standardOperatingSystem( targetPlatform ) + '/' + standardArchitecture( targetPlatform ) into standardOperatingSystem( targetPlatform ) + '/' + standardArchitecture( targetPlatform )
} }
} }
} }
}
} }
library { library {
@@ -36,7 +35,7 @@ library {
} }
withType( GccCompatibleToolChain ) { withType( GccCompatibleToolChain ) {
eachPlatform { eachPlatform {
cppCompiler.withArguments { addAll( ['-x', 'c', '-O3', '-std=c11', '-Werror', '-DMPW_SODIUM=1'] ) } cppCompiler.withArguments { addAll( ['-x', 'c', '-O3', '-Werror', '-DMPW_SODIUM=1'] ) }
} }
} }
} }
@@ -55,27 +54,29 @@ library {
from files( new File( Jvm.current().javaHome, 'include' ) ) { first().eachDir { from it } } from files( new File( Jvm.current().javaHome, 'include' ) ) { first().eachDir { from it } }
} }
binaries.configureEach { binaries.whenElementFinalized {
project.dependencies {
def system = standardOperatingSystem( targetPlatform ) def system = standardOperatingSystem( targetPlatform )
project.dependencies { // libsodium
add( linkLibraries.name, archive.dependsOn project.tasks.maybeCreate( "build_libsodium-${system}", Exec ).configure {
fileTree( "$rootDir/../lib/libsodium/build-${system}~/out/lib" ) )
}
archive.dependsOn project.tasks.maybeCreate( "build_libsodium-${system}", Exec.class ).configure {
commandLine 'bash', "$rootDir/../lib/bin/build_libsodium-${system}" commandLine 'bash', "$rootDir/../lib/bin/build_libsodium-${system}"
privateHeaders.from "$rootDir/../lib/libsodium/build-${system}~/out/include" privateHeaders.from "$rootDir/../lib/libsodium/build-${system}~/out/include"
add( linkLibraries.name, fileTree( "$rootDir/../lib/libsodium/build-${system}~/out/lib" ) )
} }
clean.dependsOn project.tasks.maybeCreate( "clean_libsodium-${system}", Exec.class ).configure { clean.dependsOn project.tasks.maybeCreate( "clean_libsodium-${system}", Exec ).configure {
commandLine 'bash', "$rootDir/../lib/bin/build_libsodium-${system}", 'clean' commandLine 'bash', "$rootDir/../lib/bin/build_libsodium-${system}", 'clean'
} }
archive.dependsOn project.tasks.maybeCreate( "build_libjson-c-${system}", Exec.class ).configure {
// libjson-c
/*archive.dependsOn project.tasks.maybeCreate( "build_libjson-c-${system}", Exec ).configure {
commandLine 'bash', "$rootDir/../lib/bin/build_libjson-c-${system}" commandLine 'bash', "$rootDir/../lib/bin/build_libjson-c-${system}"
privateHeaders.from "$rootDir/../lib/libjson-c/build-${system}~/out/include" privateHeaders.from "$rootDir/../lib/libjson-c/build-${system}~/out/include"
add( linkLibraries.name, fileTree( "$rootDir/../lib/libjson-c/build-${system}~/out/lib" ) )
} }
clean.dependsOn project.tasks.maybeCreate( "clean_libjson-c-${system}", Exec.class ).configure { clean.dependsOn project.tasks.maybeCreate( "clean_libjson-c-${system}", Exec ).configure {
commandLine 'bash', "$rootDir/../lib/bin/build_libjson-c-${system}", 'clean' commandLine 'bash', "$rootDir/../lib/bin/build_libjson-c-${system}", 'clean'
}*/
} }
} }
} }

Binary file not shown.

View File

@@ -1,12 +1,13 @@
#include <string.h> #include <string.h>
#include "mpw-jni.h" #include "java/com_lyndir_masterpassword_impl_MPAlgorithmV0.h"
#include "mpw-algorithm.h" #include "mpw-algorithm.h"
#include "mpw-util.h" #include "mpw-util.h"
// TODO: We may need to zero the jbytes safely. // TODO: We may need to zero the jbytes safely.
JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved) { JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved) {
JNIEnv* env; JNIEnv* env;
if ((*vm)->GetEnv( vm, (void **)&env, JNI_VERSION_1_6 ) != JNI_OK) if ((*vm)->GetEnv( vm, (void **)&env, JNI_VERSION_1_6 ) != JNI_OK)
return -1; return -1;

View File

@@ -35,10 +35,10 @@ char *mpw_get_token(const char **in, const char *eol, char *delim) {
return token; return token;
} }
time_t mpw_mktime( time_t mpw_timegm(const char *time) {
const char *time) {
// TODO: Support for parsing non-UTC time strings // TODO: Support for parsing non-UTC time strings
// Parse time as a UTC timestamp, into a tm.
struct tm tm = { .tm_isdst = -1 }; struct tm tm = { .tm_isdst = -1 };
if (time && sscanf( time, "%4d-%2d-%2dT%2d:%2d:%2dZ", if (time && sscanf( time, "%4d-%2d-%2dT%2d:%2d:%2dZ",
&tm.tm_year, &tm.tm_mon, &tm.tm_mday, &tm.tm_year, &tm.tm_mon, &tm.tm_mday,
@@ -46,8 +46,10 @@ time_t mpw_mktime(
tm.tm_year -= 1900; // tm_year 0 = rfc3339 year 1900 tm.tm_year -= 1900; // tm_year 0 = rfc3339 year 1900
tm.tm_mon -= 1; // tm_mon 0 = rfc3339 month 1 tm.tm_mon -= 1; // tm_mon 0 = rfc3339 month 1
// mktime converts tm to local, setting tm_gmtoff; use it to offset the result back to UTC. // mktime interprets tm as being local, we need to offset back to UTC (timegm/tm_gmtoff are non-standard).
return mktime( &tm ) + tm.tm_gmtoff; time_t local_time = mktime( &tm ), local_dst = tm.tm_isdst > 0? 3600: 0;
time_t gmtoff = local_time + local_dst - mktime( gmtime( &local_time ) );
return local_time + gmtoff;
} }
return false; return false;

View File

@@ -34,7 +34,7 @@
char *mpw_get_token( char *mpw_get_token(
const char **in, const char *eol, char *delim); const char **in, const char *eol, char *delim);
/** Convert an RFC 3339 time string into epoch time. */ /** Convert an RFC 3339 time string into epoch time. */
time_t mpw_mktime( time_t mpw_timegm(
const char *time); const char *time);
/// JSON parsing. /// JSON parsing.

View File

@@ -407,7 +407,7 @@ static void mpw_marshal_read_flat_info(
if (strcmp( headerName, "Passwords" ) == 0) if (strcmp( headerName, "Passwords" ) == 0)
info->redacted = strcmp( headerValue, "VISIBLE" ) != 0; info->redacted = strcmp( headerValue, "VISIBLE" ) != 0;
if (strcmp( headerName, "Date" ) == 0) if (strcmp( headerName, "Date" ) == 0)
info->date = mpw_mktime( headerValue ); info->date = mpw_timegm( headerValue );
mpw_free_strings( &headerName, &headerValue, NULL ); mpw_free_strings( &headerName, &headerValue, NULL );
continue; continue;
@@ -580,7 +580,7 @@ static MPMarshalledUser *mpw_marshal_read_flat(
return NULL; return NULL;
} }
MPAlgorithmVersion siteAlgorithm = (MPAlgorithmVersion)value; MPAlgorithmVersion siteAlgorithm = (MPAlgorithmVersion)value;
time_t siteLastUsed = mpw_mktime( str_lastUsed ); time_t siteLastUsed = mpw_timegm( str_lastUsed );
if (!siteLastUsed) { if (!siteLastUsed) {
*error = (MPMarshalError){ MPMarshalErrorIllegal, mpw_str( "Invalid site last used: %s: %s", siteName, str_lastUsed ) }; *error = (MPMarshalError){ MPMarshalErrorIllegal, mpw_str( "Invalid site last used: %s: %s", siteName, str_lastUsed ) };
return NULL; return NULL;
@@ -650,7 +650,7 @@ static void mpw_marshal_read_json_info(
if (fileFormat < 1) if (fileFormat < 1)
return; return;
info->redacted = mpw_get_json_boolean( json_file, "export.redacted", true ); info->redacted = mpw_get_json_boolean( json_file, "export.redacted", true );
info->date = mpw_mktime( mpw_get_json_string( json_file, "export.date", NULL ) ); info->date = mpw_timegm( mpw_get_json_string( json_file, "export.date", NULL ) );
// Section: "user" // Section: "user"
info->algorithm = (MPAlgorithmVersion)mpw_get_json_int( json_file, "user.algorithm", MPAlgorithmVersionCurrent ); info->algorithm = (MPAlgorithmVersion)mpw_get_json_int( json_file, "user.algorithm", MPAlgorithmVersionCurrent );
@@ -707,7 +707,7 @@ static MPMarshalledUser *mpw_marshal_read_json(
*error = (MPMarshalError){ MPMarshalErrorIllegal, mpw_str( "Invalid user default type: %u", defaultType ) }; *error = (MPMarshalError){ MPMarshalErrorIllegal, mpw_str( "Invalid user default type: %u", defaultType ) };
return NULL; return NULL;
} }
time_t lastUsed = mpw_mktime( str_lastUsed ); time_t lastUsed = mpw_timegm( str_lastUsed );
if (!lastUsed) { if (!lastUsed) {
*error = (MPMarshalError){ MPMarshalErrorIllegal, mpw_str( "Invalid user last used: %s", str_lastUsed ) }; *error = (MPMarshalError){ MPMarshalErrorIllegal, mpw_str( "Invalid user last used: %s", str_lastUsed ) };
return NULL; return NULL;
@@ -760,7 +760,7 @@ static MPMarshalledUser *mpw_marshal_read_json(
MPResultType siteLoginType = (MPResultType)mpw_get_json_int( json_site.val, "login_type", MPResultTypeTemplateName ); MPResultType siteLoginType = (MPResultType)mpw_get_json_int( json_site.val, "login_type", MPResultTypeTemplateName );
unsigned int siteUses = (unsigned int)mpw_get_json_int( json_site.val, "uses", 0 ); unsigned int siteUses = (unsigned int)mpw_get_json_int( json_site.val, "uses", 0 );
str_lastUsed = mpw_get_json_string( json_site.val, "last_used", NULL ); str_lastUsed = mpw_get_json_string( json_site.val, "last_used", NULL );
time_t siteLastUsed = mpw_mktime( str_lastUsed ); time_t siteLastUsed = mpw_timegm( str_lastUsed );
if (!siteLastUsed) { if (!siteLastUsed) {
*error = (MPMarshalError){ MPMarshalErrorIllegal, mpw_str( "Invalid site last used: %s: %s", siteName, str_lastUsed ) }; *error = (MPMarshalError){ MPMarshalErrorIllegal, mpw_str( "Invalid site last used: %s: %s", siteName, str_lastUsed ) };
return NULL; return NULL;

View File

@@ -115,6 +115,8 @@ bool mpw_string_pushf(
/** Push an integer onto a buffer. reallocs the given buffer and appends the given integer. */ /** Push an integer onto a buffer. reallocs the given buffer and appends the given integer. */
bool mpw_push_int( bool mpw_push_int(
uint8_t **buffer, size_t *bufferSize, const uint32_t pushInt); uint8_t **buffer, size_t *bufferSize, const uint32_t pushInt);
// These defines merely exist to force the void** cast (& do type-checking), since void** casts are not automatic.
/** Reallocate the given buffer from the given size by adding the delta size. /** Reallocate the given buffer from the given size by adding the delta size.
* On success, the buffer size pointer will be updated to the buffer's new size * On success, the buffer size pointer will be updated to the buffer's new size
* and the buffer pointer may be updated to a new memory address. * and the buffer pointer may be updated to a new memory address.
@@ -124,26 +126,26 @@ bool mpw_push_int(
* @param deltaSize The amount to increase the buffer's size by. * @param deltaSize The amount to increase the buffer's size by.
* @return true if successful, false if reallocation failed. * @return true if successful, false if reallocation failed.
*/ */
#define mpw_realloc(buffer, bufferSize, deltaSize) \ #define mpw_realloc( \
/* const void** */buffer, /* size_t* */bufferSize, /* const size_t */deltaSize) \
({ __typeof__(buffer) _b = buffer; const void *__b = *_b; (void)__b; __mpw_realloc( (const void **)_b, bufferSize, deltaSize ); }) ({ __typeof__(buffer) _b = buffer; const void *__b = *_b; (void)__b; __mpw_realloc( (const void **)_b, bufferSize, deltaSize ); })
bool __mpw_realloc(const void **buffer, size_t *bufferSize, const size_t deltaSize);
void mpw_zero(
void *buffer, size_t bufferSize);
/** Free a buffer after zero'ing its contents, then set the reference to NULL. */ /** Free a buffer after zero'ing its contents, then set the reference to NULL. */
#define mpw_free(buffer, bufferSize) \ #define mpw_free( \
/* void** */buffer, /* size_t */ bufferSize) \
({ __typeof__(buffer) _b = buffer; const void *__b = *_b; (void)__b; __mpw_free( (void **)_b, bufferSize ); }) ({ __typeof__(buffer) _b = buffer; const void *__b = *_b; (void)__b; __mpw_free( (void **)_b, bufferSize ); })
bool __mpw_free(
void **buffer, size_t bufferSize);
/** Free a string after zero'ing its contents, then set the reference to NULL. */ /** Free a string after zero'ing its contents, then set the reference to NULL. */
#define mpw_free_string(string) \ #define mpw_free_string( \
/* char** */string) \
({ __typeof__(string) _s = string; const char *__s = *_s; (void)__s; __mpw_free_string( (char **)_s ); }) ({ __typeof__(string) _s = string; const char *__s = *_s; (void)__s; __mpw_free_string( (char **)_s ); })
bool __mpw_free_string(
char **string);
/** Free strings after zero'ing their contents, then set the references to NULL. Terminate the va_list with NULL. */ /** Free strings after zero'ing their contents, then set the references to NULL. Terminate the va_list with NULL. */
#define mpw_free_strings(strings, ...) \ #define mpw_free_strings( \
/* char** */strings, ...) \
({ __typeof__(strings) _s = strings; const char *__s = *_s; (void)__s; __mpw_free_strings( (char **)_s, __VA_ARGS__ ); }) ({ __typeof__(strings) _s = strings; const char *__s = *_s; (void)__s; __mpw_free_strings( (char **)_s, __VA_ARGS__ ); })
bool __mpw_free_strings( /** Free a string after zero'ing its contents, then set the reference to the replacement string.
char **strings, ...); * The replacement string is generated before the original is freed; it may be a derivative of the original. */
#define mpw_replace_string( \
/* char* */string, /* char* */replacement) \
do { const char *replacement_ = replacement; mpw_free_string( &string ); string = replacement_; } while (0)
#ifdef _MSC_VER #ifdef _MSC_VER
#undef mpw_realloc #undef mpw_realloc
#define mpw_realloc(buffer, bufferSize, deltaSize) \ #define mpw_realloc(buffer, bufferSize, deltaSize) \
@@ -158,6 +160,16 @@ bool __mpw_free_strings(
#define mpw_free_strings(strings, ...) \ #define mpw_free_strings(strings, ...) \
__mpw_free_strings( (char **)strings, __VA_ARGS__ ) __mpw_free_strings( (char **)strings, __VA_ARGS__ )
#endif #endif
bool __mpw_realloc(
const void **buffer, size_t *bufferSize, const size_t deltaSize);
bool __mpw_free(
void **buffer, size_t bufferSize);
bool __mpw_free_string(
char **string);
bool __mpw_free_strings(
char **strings, ...);
void mpw_zero(
void *buffer, size_t bufferSize);
//// Cryptographic functions. //// Cryptographic functions.

View File

@@ -4,6 +4,11 @@ plugins {
description = 'Master Password Algorithm Implementation' description = 'Master Password Algorithm Implementation'
tasks.withType( JavaCompile ) {
// Native headers
options.compilerArgs += ["-h", new File( new File( project.project( ':masterpassword-core' ).projectDir, 'src' ), 'java' ).absolutePath]
}
configurations { configurations {
lib lib
} }
@@ -24,17 +29,7 @@ processResources {
into new File( processResources.outputs.files.singleFile, "lib" ) into new File( processResources.outputs.files.singleFile, "lib" )
dependsOn configurations.lib { dependsOn configurations.lib {
files.each { libFile -> files.each { libFile -> from( zipTree( libFile ) ) }
from( zipTree( libFile ) )
}
} }
} ) } )
} }
compileJava {
doLast {
ant.javah( class: 'com.lyndir.masterpassword.impl.MPAlgorithmV0',
outputFile: new File( project( ':masterpassword-core' ).projectDir, 'src/mpw-jni.h' ),
classpath: files( sourceSets.main.compileClasspath, sourceSets.main.output ).asPath )
}
}

View File

@@ -28,6 +28,7 @@ import com.lyndir.lhunath.opal.system.MessageDigests;
import com.lyndir.masterpassword.impl.*; import com.lyndir.masterpassword.impl.*;
import java.nio.ByteOrder; import java.nio.ByteOrder;
import java.nio.charset.Charset; import java.nio.charset.Charset;
import javax.annotation.Nonnull;
import javax.annotation.Nullable; import javax.annotation.Nullable;
@@ -88,46 +89,55 @@ public abstract class MPAlgorithm {
/** /**
* The linear version identifier of this algorithm's implementation. * The linear version identifier of this algorithm's implementation.
*/ */
@Nonnull
public abstract Version version(); public abstract Version version();
/** /**
* mpw: defaults: initial counter value. * mpw: defaults: initial counter value.
*/ */
@Nonnull
public abstract UnsignedInteger mpw_default_counter(); public abstract UnsignedInteger mpw_default_counter();
/** /**
* mpw: defaults: password result type. * mpw: defaults: password result type.
*/ */
@Nonnull
public abstract MPResultType mpw_default_result_type(); public abstract MPResultType mpw_default_result_type();
/** /**
* mpw: defaults: login result type. * mpw: defaults: login result type.
*/ */
@Nonnull
public abstract MPResultType mpw_default_login_type(); public abstract MPResultType mpw_default_login_type();
/** /**
* mpw: defaults: answer result type. * mpw: defaults: answer result type.
*/ */
@Nonnull
public abstract MPResultType mpw_default_answer_type(); public abstract MPResultType mpw_default_answer_type();
/** /**
* mpw: Input character encoding. * mpw: Input character encoding.
*/ */
@Nonnull
public abstract Charset mpw_charset(); public abstract Charset mpw_charset();
/** /**
* mpw: Platform-agnostic byte order. * mpw: Platform-agnostic byte order.
*/ */
@Nonnull
public abstract ByteOrder mpw_byteOrder(); public abstract ByteOrder mpw_byteOrder();
/** /**
* mpw: Key ID hash. * mpw: Key ID hash.
*/ */
@Nonnull
public abstract MessageDigests mpw_hash(); public abstract MessageDigests mpw_hash();
/** /**
* mpw: Site digest. * mpw: Site digest.
*/ */
@Nonnull
public abstract MessageAuthenticationDigests mpw_digest(); public abstract MessageAuthenticationDigests mpw_digest();
/** /**
@@ -167,12 +177,16 @@ public abstract class MPAlgorithm {
// Utilities // Utilities
@Nonnull
protected abstract byte[] toBytes(int number); protected abstract byte[] toBytes(int number);
@Nonnull
protected abstract byte[] toBytes(UnsignedInteger number); protected abstract byte[] toBytes(UnsignedInteger number);
@Nonnull
protected abstract byte[] toBytes(char[] characters); protected abstract byte[] toBytes(char[] characters);
@Nonnull
protected abstract byte[] toID(byte[] bytes); protected abstract byte[] toID(byte[] bytes);
@Override @Override

View File

@@ -27,6 +27,7 @@ import com.lyndir.lhunath.opal.system.logging.Logger;
import java.nio.*; import java.nio.*;
import java.nio.charset.Charset; import java.nio.charset.Charset;
import java.util.Arrays; import java.util.Arrays;
import java.util.Locale;
/** /**
@@ -83,6 +84,10 @@ public class MPIdenticon {
return text; return text;
} }
public String getHTML() {
return strf( "<span style='color: %s'>%s</span>", color.getCSS(), text );
}
public Color getColor() { public Color getColor() {
return color; return color;
} }
@@ -94,6 +99,15 @@ public class MPIdenticon {
BLUE, BLUE,
MAGENTA, MAGENTA,
CYAN, CYAN,
MONO MONO {
@Override
public String getCSS() {
return "inherit";
}
};
public String getCSS() {
return name().toLowerCase( Locale.ROOT );
}
} }
} }

View File

@@ -44,7 +44,8 @@ public class MPMasterKey {
/** /**
* @param masterPassword The characters of the user's master password. * @param masterPassword The characters of the user's master password.
* <b>Note: this method destroys the contents of the array.</b> *
* @apiNote This method destroys the contents of the {@code masterPassword} array.
*/ */
@SuppressWarnings("AssignmentToCollectionOrArrayFieldFromParameter") @SuppressWarnings("AssignmentToCollectionOrArrayFieldFromParameter")
public MPMasterKey(final String fullName, final char[] masterPassword) { public MPMasterKey(final String fullName, final char[] masterPassword) {
@@ -65,6 +66,7 @@ public class MPMasterKey {
* *
* @throws MPKeyUnavailableException {@link #invalidate()} has been called on this object. * @throws MPKeyUnavailableException {@link #invalidate()} has been called on this object.
*/ */
@Nonnull
public byte[] getKeyID(final MPAlgorithm algorithm) public byte[] getKeyID(final MPAlgorithm algorithm)
throws MPKeyUnavailableException, MPAlgorithmException { throws MPKeyUnavailableException, MPAlgorithmException {
@@ -82,11 +84,16 @@ public class MPMasterKey {
Arrays.fill( masterPassword, (char) 0 ); Arrays.fill( masterPassword, (char) 0 );
} }
public boolean isValid() {
return !invalidated;
}
@Nonnull
private byte[] masterKey(final MPAlgorithm algorithm) private byte[] masterKey(final MPAlgorithm algorithm)
throws MPKeyUnavailableException, MPAlgorithmException { throws MPKeyUnavailableException, MPAlgorithmException {
Preconditions.checkArgument( masterPassword.length > 0 ); Preconditions.checkArgument( masterPassword.length > 0 );
if (invalidated) if (!isValid())
throw new MPKeyUnavailableException( "Master key was invalidated." ); throw new MPKeyUnavailableException( "Master key was invalidated." );
byte[] masterKey = keyByVersion.get( algorithm.version() ); byte[] masterKey = keyByVersion.get( algorithm.version() );
@@ -104,6 +111,7 @@ public class MPMasterKey {
return masterKey; return masterKey;
} }
@Nonnull
private byte[] siteKey(final String siteName, final MPAlgorithm algorithm, final UnsignedInteger siteCounter, private byte[] siteKey(final String siteName, final MPAlgorithm algorithm, final UnsignedInteger siteCounter,
final MPKeyPurpose keyPurpose, @Nullable final String keyContext) final MPKeyPurpose keyPurpose, @Nullable final String keyContext)
throws MPKeyUnavailableException, MPAlgorithmException { throws MPKeyUnavailableException, MPAlgorithmException {
@@ -136,13 +144,20 @@ public class MPMasterKey {
* In the case of {@link MPResultTypeClass#Stateful} types, the result of * In the case of {@link MPResultTypeClass#Stateful} types, the result of
* {@link #siteState(String, MPAlgorithm, UnsignedInteger, MPKeyPurpose, String, MPResultType, String)}. * {@link #siteState(String, MPAlgorithm, UnsignedInteger, MPKeyPurpose, String, MPResultType, String)}.
* *
* @return {@code null} if the result type is missing a required parameter.
*
* @throws MPKeyUnavailableException {@link #invalidate()} has been called on this object. * @throws MPKeyUnavailableException {@link #invalidate()} has been called on this object.
* @throws MPAlgorithmException An internal system or algorithm error has occurred.
*/ */
@Nullable
public String siteResult(final String siteName, final MPAlgorithm algorithm, final UnsignedInteger siteCounter, public String siteResult(final String siteName, final MPAlgorithm algorithm, final UnsignedInteger siteCounter,
final MPKeyPurpose keyPurpose, @Nullable final String keyContext, final MPKeyPurpose keyPurpose, @Nullable final String keyContext,
final MPResultType resultType, @Nullable final String resultParam) final MPResultType resultType, @Nullable final String resultParam)
throws MPKeyUnavailableException, MPAlgorithmException { throws MPKeyUnavailableException, MPAlgorithmException {
if ((resultType.getTypeClass() == MPResultTypeClass.Stateful) && (resultParam == null))
return null;
byte[] masterKey = masterKey( algorithm ); byte[] masterKey = masterKey( algorithm );
byte[] siteKey = siteKey( siteName, algorithm, siteCounter, keyPurpose, keyContext ); byte[] siteKey = siteKey( siteName, algorithm, siteCounter, keyPurpose, keyContext );
@@ -170,7 +185,9 @@ public class MPMasterKey {
* {@link #siteResult(String, MPAlgorithm, UnsignedInteger, MPKeyPurpose, String, MPResultType, String)}. * {@link #siteResult(String, MPAlgorithm, UnsignedInteger, MPKeyPurpose, String, MPResultType, String)}.
* *
* @throws MPKeyUnavailableException {@link #invalidate()} has been called on this object. * @throws MPKeyUnavailableException {@link #invalidate()} has been called on this object.
* @throws MPAlgorithmException An internal system or algorithm error has occurred.
*/ */
@Nonnull
public String siteState(final String siteName, final MPAlgorithm algorithm, final UnsignedInteger siteCounter, public String siteState(final String siteName, final MPAlgorithm algorithm, final UnsignedInteger siteCounter,
final MPKeyPurpose keyPurpose, @Nullable final String keyContext, final MPKeyPurpose keyPurpose, @Nullable final String keyContext,
final MPResultType resultType, final String resultParam) final MPResultType resultType, final String resultParam)

View File

@@ -41,7 +41,7 @@ public enum MPResultType {
/** /**
* 16: pg^VMAUBk5x3p%HP%i4= * 16: pg^VMAUBk5x3p%HP%i4=
*/ */
GeneratedMaximum( "maximum", "20 characters, contains symbols.", // GeneratedMaximum( "maximum", "Maximum Security", "pg^VMAUBk5x3p%HP%i4=", "20 characters, contains symbols", //
ImmutableList.of( new MPTemplate( "anoxxxxxxxxxxxxxxxxx" ), ImmutableList.of( new MPTemplate( "anoxxxxxxxxxxxxxxxxx" ),
new MPTemplate( "axxxxxxxxxxxxxxxxxno" ) ), // new MPTemplate( "axxxxxxxxxxxxxxxxxno" ) ), //
MPResultTypeClass.Template, 0x0 ), MPResultTypeClass.Template, 0x0 ),
@@ -49,7 +49,7 @@ public enum MPResultType {
/** /**
* 17: BiroYena8:Kixa * 17: BiroYena8:Kixa
*/ */
GeneratedLong( "long", "Copy-friendly, 14 characters, contains symbols.", // GeneratedLong( "long", "Long Password", "BiroYena8:Kixa", "Copy-friendly, 14 characters, contains symbols", //
ImmutableList.of( new MPTemplate( "CvcvnoCvcvCvcv" ), new MPTemplate( "CvcvCvcvnoCvcv" ), ImmutableList.of( new MPTemplate( "CvcvnoCvcvCvcv" ), new MPTemplate( "CvcvCvcvnoCvcv" ),
new MPTemplate( "CvcvCvcvCvcvno" ), new MPTemplate( "CvccnoCvcvCvcv" ), new MPTemplate( "CvcvCvcvCvcvno" ), new MPTemplate( "CvccnoCvcvCvcv" ),
new MPTemplate( "CvccCvcvnoCvcv" ), new MPTemplate( "CvccCvcvCvcvno" ), new MPTemplate( "CvccCvcvnoCvcv" ), new MPTemplate( "CvccCvcvCvcvno" ),
@@ -66,7 +66,7 @@ public enum MPResultType {
/** /**
* 18: BirSuj0- * 18: BirSuj0-
*/ */
GeneratedMedium( "medium", "Copy-friendly, 8 characters, contains symbols.", // GeneratedMedium( "medium", "Medium Password", "BirSuj0-", "Copy-friendly, 8 characters, contains symbols", //
ImmutableList.of( new MPTemplate( "CvcnoCvc" ), ImmutableList.of( new MPTemplate( "CvcnoCvc" ),
new MPTemplate( "CvcCvcno" ) ), // new MPTemplate( "CvcCvcno" ) ), //
MPResultTypeClass.Template, 0x2 ), MPResultTypeClass.Template, 0x2 ),
@@ -74,14 +74,14 @@ public enum MPResultType {
/** /**
* 19: Bir8 * 19: Bir8
*/ */
GeneratedShort( "short", "Copy-friendly, 4 characters, no symbols.", // GeneratedShort( "short", "Short Password", "Bir8", "Copy-friendly, 4 characters, no symbols", //
ImmutableList.of( new MPTemplate( "Cvcn" ) ), // ImmutableList.of( new MPTemplate( "Cvcn" ) ), //
MPResultTypeClass.Template, 0x3 ), MPResultTypeClass.Template, 0x3 ),
/** /**
* 20: pO98MoD0 * 20: pO98MoD0
*/ */
GeneratedBasic( "basic", "8 characters, no symbols.", // GeneratedBasic( "basic", "Basic Password", "pO98MoD0", "8 characters, no symbols", //
ImmutableList.of( new MPTemplate( "aaanaaan" ), ImmutableList.of( new MPTemplate( "aaanaaan" ),
new MPTemplate( "aannaaan" ), new MPTemplate( "aannaaan" ),
new MPTemplate( "aaannaaa" ) ), // new MPTemplate( "aaannaaa" ) ), //
@@ -90,60 +90,67 @@ public enum MPResultType {
/** /**
* 21: 2798 * 21: 2798
*/ */
GeneratedPIN( "pin", "4 numbers.", // GeneratedPIN( "pin", "PIN Code", "2798", "4 numbers", //
ImmutableList.of( new MPTemplate( "nnnn" ) ), // ImmutableList.of( new MPTemplate( "nnnn" ) ), //
MPResultTypeClass.Template, 0x5 ), MPResultTypeClass.Template, 0x5 ),
/** /**
* 30: birsujano * 30: birsujano
*/ */
GeneratedName( "name", "9 letter name.", // GeneratedName( "name", "Name", "birsujano", "9 letter name", //
ImmutableList.of( new MPTemplate( "cvccvcvcv" ) ), // ImmutableList.of( new MPTemplate( "cvccvcvcv" ) ), //
MPResultTypeClass.Template, 0xE ), MPResultTypeClass.Template, 0xE ),
/** /**
* 31: bir yennoquce fefi * 31: bir yennoquce fefi
*/ */
GeneratedPhrase( "phrase", "20 character sentence.", // GeneratedPhrase( "phrase", "Phrase", "bir yennoquce fefi", "20 character sentence", //
ImmutableList.of( new MPTemplate( "cvcc cvc cvccvcv cvc" ), ImmutableList.of( new MPTemplate( "cvcc cvc cvccvcv cvc" ),
new MPTemplate( "cvc cvccvcvcv cvcv" ), new MPTemplate( "cvc cvccvcvcv cvcv" ),
new MPTemplate( "cv cvccv cvc cvcvccv" ) ), // new MPTemplate( "cv cvccv cvc cvcvccv" ) ), //
MPResultTypeClass.Template, 0xF ), MPResultTypeClass.Template, 0xF ),
/** /**
* 1056: Custom saved password. * 1056: Custom saved value.
*/ */
StoredPersonal( "personal", "AES-encrypted, exportable.", // StoredPersonal( "personal", "Saved", null, "AES-encrypted, exportable", //
ImmutableList.<MPTemplate>of(), // ImmutableList.<MPTemplate>of(), //
MPResultTypeClass.Stateful, 0x0, MPSiteFeature.ExportContent ), MPResultTypeClass.Stateful, 0x0, MPSiteFeature.ExportContent ),
/** /**
* 2081: Custom saved password that should not be exported from the device. * 2081: Custom saved value that should not be exported from the device.
*/ */
StoredDevicePrivate( "device", "AES-encrypted, not exported.", // StoredDevicePrivate( "device", "Private", null, "AES-encrypted, not exported", //
ImmutableList.<MPTemplate>of(), // ImmutableList.<MPTemplate>of(), //
MPResultTypeClass.Stateful, 0x1, MPSiteFeature.DevicePrivate ), MPResultTypeClass.Stateful, 0x1, MPSiteFeature.DevicePrivate ),
/** /**
* 4160: Derive a unique binary key. * 4160: Derive a unique binary key.
*/ */
DeriveKey( "key", "Encryption key.", // DeriveKey( "key", "Binary Key", null, "Encryption key", //
ImmutableList.<MPTemplate>of(), // ImmutableList.<MPTemplate>of(), //
MPResultTypeClass.Derive, 0x0, MPSiteFeature.Alternative ); MPResultTypeClass.Derive, 0x0, MPSiteFeature.Alternative );
static final Logger logger = Logger.get( MPResultType.class ); static final Logger logger = Logger.get( MPResultType.class );
private final String shortName; private final String shortName;
private final String longName;
@Nullable
private final String sample;
private final String description; private final String description;
private final List<MPTemplate> templates; private final List<MPTemplate> templates;
private final MPResultTypeClass typeClass; private final MPResultTypeClass typeClass;
private final int typeIndex; private final int typeIndex;
private final ImmutableSet<MPSiteFeature> typeFeatures; private final ImmutableSet<MPSiteFeature> typeFeatures;
MPResultType(final String shortName, final String description, final List<MPTemplate> templates, MPResultType(final String shortName, final String longName, @Nullable final String sample, final String description,
final List<MPTemplate> templates,
final MPResultTypeClass typeClass, final int typeIndex, final MPSiteFeature... typeFeatures) { final MPResultTypeClass typeClass, final int typeIndex, final MPSiteFeature... typeFeatures) {
this.shortName = shortName; this.shortName = shortName;
this.longName = longName;
this.sample = sample;
this.description = description; this.description = description;
this.templates = templates; this.templates = templates;
this.typeClass = typeClass; this.typeClass = typeClass;
@@ -160,6 +167,15 @@ public enum MPResultType {
return shortName; return shortName;
} }
public String getLongName() {
return longName;
}
@Nullable
public String getSample() {
return sample;
}
public String getDescription() { public String getDescription() {
return description; return description;

View File

@@ -27,6 +27,7 @@ import com.lyndir.masterpassword.*;
import java.nio.*; import java.nio.*;
import java.nio.charset.*; import java.nio.charset.*;
import java.util.Arrays; import java.util.Arrays;
import javax.annotation.Nonnull;
import javax.annotation.Nullable; import javax.annotation.Nullable;
@@ -122,46 +123,55 @@ public class MPAlgorithmV0 extends MPAlgorithm {
// Configuration // Configuration
@Nonnull
@Override @Override
public Version version() { public Version version() {
return MPAlgorithm.Version.V0; return MPAlgorithm.Version.V0;
} }
@Nonnull
@Override @Override
public UnsignedInteger mpw_default_counter() { public UnsignedInteger mpw_default_counter() {
return UnsignedInteger.ONE; return UnsignedInteger.ONE;
} }
@Nonnull
@Override @Override
public MPResultType mpw_default_result_type() { public MPResultType mpw_default_result_type() {
return MPResultType.GeneratedLong; return MPResultType.GeneratedLong;
} }
@Nonnull
@Override @Override
public MPResultType mpw_default_login_type() { public MPResultType mpw_default_login_type() {
return MPResultType.GeneratedName; return MPResultType.GeneratedName;
} }
@Nonnull
@Override @Override
public MPResultType mpw_default_answer_type() { public MPResultType mpw_default_answer_type() {
return MPResultType.GeneratedPhrase; return MPResultType.GeneratedPhrase;
} }
@Nonnull
@Override @Override
public Charset mpw_charset() { public Charset mpw_charset() {
return Charsets.UTF_8; return Charsets.UTF_8;
} }
@Nonnull
@Override @Override
public ByteOrder mpw_byteOrder() { public ByteOrder mpw_byteOrder() {
return ByteOrder.BIG_ENDIAN; return ByteOrder.BIG_ENDIAN;
} }
@Nonnull
@Override @Override
public MessageDigests mpw_hash() { public MessageDigests mpw_hash() {
return MessageDigests.SHA256; return MessageDigests.SHA256;
} }
@Nonnull
@Override @Override
public MessageAuthenticationDigests mpw_digest() { public MessageAuthenticationDigests mpw_digest() {
return MessageAuthenticationDigests.HmacSHA256; return MessageAuthenticationDigests.HmacSHA256;
@@ -211,16 +221,19 @@ public class MPAlgorithmV0 extends MPAlgorithm {
// Utilities // Utilities
@Nonnull
@Override @Override
public byte[] toBytes(final int number) { public byte[] toBytes(final int number) {
return ByteBuffer.allocate( Integer.SIZE / Byte.SIZE ).order( mpw_byteOrder() ).putInt( number ).array(); return ByteBuffer.allocate( Integer.SIZE / Byte.SIZE ).order( mpw_byteOrder() ).putInt( number ).array();
} }
@Nonnull
@Override @Override
public byte[] toBytes(final UnsignedInteger number) { public byte[] toBytes(final UnsignedInteger number) {
return ByteBuffer.allocate( Integer.SIZE / Byte.SIZE ).order( mpw_byteOrder() ).putInt( number.intValue() ).array(); return ByteBuffer.allocate( Integer.SIZE / Byte.SIZE ).order( mpw_byteOrder() ).putInt( number.intValue() ).array();
} }
@Nonnull
@Override @Override
public byte[] toBytes(final char[] characters) { public byte[] toBytes(final char[] characters) {
ByteBuffer byteBuffer = mpw_charset().encode( CharBuffer.wrap( characters ) ); ByteBuffer byteBuffer = mpw_charset().encode( CharBuffer.wrap( characters ) );
@@ -232,6 +245,7 @@ public class MPAlgorithmV0 extends MPAlgorithm {
return bytes; return bytes;
} }
@Nonnull
@Override @Override
public byte[] toID(final byte[] bytes) { public byte[] toID(final byte[] bytes) {
return mpw_hash().of( bytes ); return mpw_hash().of( bytes );

View File

@@ -19,6 +19,7 @@
package com.lyndir.masterpassword.impl; package com.lyndir.masterpassword.impl;
import com.lyndir.masterpassword.*; import com.lyndir.masterpassword.*;
import javax.annotation.Nonnull;
/** /**
@@ -29,6 +30,7 @@ public class MPAlgorithmV1 extends MPAlgorithmV0 {
// Configuration // Configuration
@Nonnull
@Override @Override
public Version version() { public Version version() {
return MPAlgorithm.Version.V1; return MPAlgorithm.Version.V1;

View File

@@ -19,6 +19,7 @@
package com.lyndir.masterpassword.impl; package com.lyndir.masterpassword.impl;
import com.lyndir.masterpassword.MPAlgorithm; import com.lyndir.masterpassword.MPAlgorithm;
import javax.annotation.Nonnull;
/** /**
@@ -29,6 +30,7 @@ public class MPAlgorithmV2 extends MPAlgorithmV1 {
// Configuration // Configuration
@Nonnull
@Override @Override
public Version version() { public Version version() {
return MPAlgorithm.Version.V2; return MPAlgorithm.Version.V2;

View File

@@ -19,6 +19,7 @@
package com.lyndir.masterpassword.impl; package com.lyndir.masterpassword.impl;
import com.lyndir.masterpassword.MPAlgorithm; import com.lyndir.masterpassword.MPAlgorithm;
import javax.annotation.Nonnull;
/** /**
@@ -29,6 +30,7 @@ public class MPAlgorithmV3 extends MPAlgorithmV2 {
// Configuration // Configuration
@Nonnull
@Override @Override
public Version version() { public Version version() {
return MPAlgorithm.Version.V3; return MPAlgorithm.Version.V3;

View File

@@ -18,14 +18,14 @@
package com.lyndir.masterpassword.impl; package com.lyndir.masterpassword.impl;
import static com.lyndir.lhunath.opal.system.util.ObjectUtils.*;
import com.google.common.base.Joiner; import com.google.common.base.Joiner;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.io.ByteStreams; import com.google.common.io.ByteStreams;
import com.lyndir.lhunath.opal.system.logging.Logger; import com.lyndir.lhunath.opal.system.logging.Logger;
import java.io.*; import java.io.*;
import java.util.Locale; import java.util.*;
import java.util.function.Predicate;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
import javax.annotation.Nullable; import javax.annotation.Nullable;
@@ -43,30 +43,35 @@ public final class Native {
private static final String NATIVES_PATH = "lib"; private static final String NATIVES_PATH = "lib";
@SuppressWarnings({ "HardcodedFileSeparator", "LoadLibraryWithNonConstantString" }) @SuppressWarnings({ "HardcodedFileSeparator", "LoadLibraryWithNonConstantString" })
public static void load(final Class<?> context, final String name) { public static boolean load(final Class<?> context, final String name) {
// Try to load the library using the native system. // Try to load the library using the native system.
try { try {
System.loadLibrary( name ); System.loadLibrary( name );
return; return true;
} }
catch (@SuppressWarnings("ErrorNotRethrown") final UnsatisfiedLinkError ignored) { catch (@SuppressWarnings("ErrorNotRethrown") final UnsatisfiedLinkError ignored) {
} }
// Try to find and open a stream to the packaged library resource. // Try to find and open a stream to the packaged library resource.
try {
String library = System.mapLibraryName( name ); String library = System.mapLibraryName( name );
int libraryDot = library.lastIndexOf( EXTENSION_SEPARATOR ); int libraryDot = library.lastIndexOf( EXTENSION_SEPARATOR );
String libraryName = (libraryDot > 0)? library.substring( 0, libraryDot ): library; String libraryName = (libraryDot > 0)? library.substring( 0, libraryDot ): library;
String libraryExtension = (libraryDot > 0)? library.substring( libraryDot ): ".lib"; String libraryExtension = (libraryDot > 0)? library.substring( libraryDot ): ".lib";
String libraryResource = getLibraryResource( library );
@Nullable
File libraryFile = null;
Set<String> libraryResources = getLibraryResources( library );
for (final String libraryResource : libraryResources) {
try {
InputStream libraryStream = context.getResourceAsStream( libraryResource ); InputStream libraryStream = context.getResourceAsStream( libraryResource );
if (libraryStream == null) if (libraryStream == null) {
throw new IllegalStateException( logger.dbg( "No resource for library: %s", libraryResource );
"Library: " + name + " (" + libraryResource + "), not found in class loader for: " + context ); continue;
}
// Write the library resource to a temporary file. // Write the library resource to a temporary file.
File libraryFile = File.createTempFile( libraryName, libraryExtension ); libraryFile = File.createTempFile( libraryName, libraryExtension );
FileOutputStream libraryFileStream = new FileOutputStream( libraryFile ); FileOutputStream libraryFileStream = new FileOutputStream( libraryFile );
try { try {
libraryFile.deleteOnExit(); libraryFile.deleteOnExit();
@@ -79,35 +84,120 @@ public final class Native {
// Load the library from the temporary file. // Load the library from the temporary file.
System.load( libraryFile.getAbsolutePath() ); System.load( libraryFile.getAbsolutePath() );
return true;
} }
catch (final IOException e) { catch (@SuppressWarnings("ErrorNotRethrown") final IOException | UnsatisfiedLinkError e) {
throw new IllegalStateException( "Couldn't extract library: " + name, e ); logger.dbg( e, "Couldn't load library: %s", libraryResource );
if (libraryFile != null)
if (libraryFile.exists() && !libraryFile.delete())
logger.wrn( "Couldn't clean up library file: %s", libraryFile );
libraryFile = null;
} }
} }
return false;
}
@Nonnull @Nonnull
private static String getLibraryResource(final String library) { private static Set<String> getLibraryResources(final String library) {
String system = ifNotNullElse( System.getProperty( "os.name" ), "linux" ).toLowerCase( Locale.ROOT );
String architecture = ifNotNullElse( System.getProperty( "os.arch" ), "x86" ).toLowerCase( Locale.ROOT );
// Standardize system naming in accordance with masterpassword-core. // Standardize system naming in accordance with masterpassword-core.
if (system.contains( "windows" )) Sys system = Sys.findCurrent();
system = "windows";
else if (system.contains( "mac os x" ) || system.contains( "darwin" ) || system.contains( "osx" ))
system = "macos";
else
system = "linux";
// Standardize architecture naming in accordance with masterpassword-core. // Standardize architecture naming in accordance with masterpassword-core.
if (ImmutableList.of( "arm", "arm-v7", "armv7", "arm32" ).contains( architecture )) Collection<Arch> architectures = new LinkedHashSet<>();
architecture = "arm"; architectures.add( Arch.findCurrent() );
else if (architecture.startsWith( "arm" )) architectures.addAll( Arrays.asList( Arch.values() ) );
architecture = "arm64";
else if (ImmutableList.of( "x86_64", "amd64", "x64", "x86-64" ).contains( architecture ))
architecture = "x86_64";
else
architecture = "x86";
return Joiner.on( RESOURCE_SEPARATOR ).join( "", NATIVES_PATH, system, architecture, library ); ImmutableSet.Builder<String> resources = ImmutableSet.builder();
for (final Arch arch : architectures)
resources.add( Joiner.on( RESOURCE_SEPARATOR ).join( "", NATIVES_PATH, system, arch, library ) );
return resources.build();
}
private enum Sys implements Predicate<String> {
windows {
@Override
public boolean test(final String system) {
return system.contains( "windows" );
}
},
macos {
@Override
public boolean test(final String system) {
return system.contains( "mac os x" ) || system.contains( "darwin" ) || system.contains( "osx" );
}
},
linux {
@Override
public boolean test(final String system) {
return system.contains( "linux" );
}
};
@Nonnull
public static Sys findCurrent() {
return find( System.getProperty( "os.name" ) );
}
@Nonnull
public static Sys find(@Nullable String name) {
if (name != null) {
name = name.toLowerCase( Locale.ROOT );
for (final Sys sys : values())
if (sys.test( name ))
return sys;
}
return linux;
}
}
private enum Arch implements Predicate<String> {
arm {
@Override
public boolean test(final String architecture) {
return ImmutableList.of( "arm", "arm-v7", "armv7", "arm32" ).contains( architecture );
}
},
arm64 {
@Override
public boolean test(final String architecture) {
return architecture.startsWith( "arm" ) && !arm.test( architecture );
}
},
x86_64 {
@Override
public boolean test(final String architecture) {
return ImmutableList.of( "x86_64", "amd64", "x64", "x86-64" ).contains( architecture );
}
},
x86 {
@Override
public boolean test(final String architecture) {
return ImmutableList.of( "x86", "i386", "i686" ).contains( architecture );
}
};
@Nonnull
public static Arch findCurrent() {
return find( System.getProperty( "os.arch" ) );
}
@Nonnull
public static Arch find(@Nullable String name) {
if (name != null) {
name = name.toLowerCase( Locale.ROOT );
for (final Arch arch : values())
if (arch.test( name ))
return arch;
}
return x86;
}
} }
} }

View File

@@ -0,0 +1,49 @@
package com.lyndir.masterpassword.util;
import java.util.function.Consumer;
import java.util.function.Function;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
/**
* @author lhunath, 2018-07-25
*/
public final class Utilities {
@Nullable
public static <T, R> R ifNotNull(@Nullable final T value, final Function<T, R> consumer) {
if (value == null)
return null;
return consumer.apply( value );
}
@Nonnull
public static <T> T ifNotNullElse(@Nullable final T value, @Nonnull final T nullValue) {
if (value == null)
return nullValue;
return value;
}
public static String ifNotNullOrEmptyElse(@Nullable final String value, @Nonnull final String emptyValue) {
if ((value == null) || value.isEmpty())
return emptyValue;
return value;
}
@Nonnull
public static <T, R> R ifNotNullElse(@Nullable final T value, final Function<T, R> consumer, @Nonnull final R nullValue) {
if (value == null)
return nullValue;
return consumer.apply( value );
}
public static <T> void ifNotNullDo(@Nullable final T value, final Consumer<T> consumer) {
if (value != null)
consumer.accept( value );
}
}

View File

@@ -0,0 +1,7 @@
/**
* @author lhunath, 2018-07-25
*/
@ParametersAreNonnullByDefault
package com.lyndir.masterpassword.util;
import javax.annotation.ParametersAreNonnullByDefault;

View File

@@ -5,32 +5,37 @@ plugins {
} }
description = 'Master Password GUI' description = 'Master Password GUI'
mainClassName = 'com.lyndir.masterpassword.gui.GUI' mainClassName = 'com.lyndir.masterpassword.gui.MasterPassword'
dependencies { dependencies {
implementation group: 'com.lyndir.lhunath.opal', name: 'opal-system', version: '1.7-p2' implementation group: 'com.lyndir.lhunath.opal', name: 'opal-system', version: '1.7-p2'
implementation group: 'ch.qos.logback', name: 'logback-classic', version: '1.1.2' implementation group: 'ch.qos.logback', name: 'logback-classic', version: '1.1.2'
implementation group: 'com.yuvimasory', name: 'orange-extensions', version: '1.3.0' implementation group: 'com.yuvimasory', name: 'orange-extensions', version: '1.3.0'
implementation group: 'com.github.tulskiy', name: 'jkeymaster', version: '1.2'
compile project( ':masterpassword-model' ) compile project( ':masterpassword-model' )
} }
// release with: STORE_PW=$(mpw masterpassword.keystore) KEY_PW_ANDROID=$(mpw masterpassword-android) gradle masterpassword-gui:shadowJar // release with: STORE_PW=$(mpw masterpassword.keystore) KEY_PW_DESKTOP=$(mpw masterpassword-desktop) gradle clean masterpassword-gui:shadowJar
shadowJar.doLast { shadowJar {
manifest {
attributes 'Implementation-Title': description
attributes 'Implementation-Version': version
}
doLast {
if (System.getenv( 'KEY_PW_DESKTOP' ) != null) if (System.getenv( 'KEY_PW_DESKTOP' ) != null)
ant.signjar( ant.signjar( jar: archivePath,
jar: archivePath,
alias: 'masterpassword-desktop', alias: 'masterpassword-desktop',
keystore: 'masterpassword.keystore', keystore: 'masterpassword.keystore',
storepass: System.getenv( 'STORE_PW' ), storepass: System.getenv( 'STORE_PW' ),
keypass: System.getenv( 'KEY_PW_DESKTOP' ), keypass: System.getenv( 'KEY_PW_DESKTOP' ),
preservelastmodified: 'true', preservelastmodified: 'true',
destdir: '.' signedJar: "${rootDir}/../public/site/${project.name}-${project.version}.jar" )
) }
} }
run { run {
// I don't fully understand why this is necessary, but without it -Dmp.log.level is lost. // I don't fully understand why this is necessary, but without it -Dmp.log.level is lost.
systemProperties = System.properties //systemProperties = System.properties
} }

View File

@@ -1,143 +0,0 @@
//==============================================================================
// This file is part of Master Password.
// Copyright (c) 2011-2017, Maarten Billemont.
//
// Master Password is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Master Password is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You can find a copy of the GNU General Public License in the
// LICENSE file. Alternatively, see <http://www.gnu.org/licenses/>.
//==============================================================================
package com.lyndir.masterpassword.gui;
import static com.lyndir.lhunath.opal.system.util.StringUtils.*;
import com.google.common.base.Charsets;
import com.google.common.io.ByteSource;
import com.lyndir.lhunath.opal.system.logging.Logger;
import com.lyndir.lhunath.opal.system.util.TypeUtils;
import com.lyndir.masterpassword.gui.view.PasswordFrame;
import com.lyndir.masterpassword.gui.view.UnlockFrame;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.InvocationTargetException;
import java.net.*;
import java.util.Enumeration;
import java.util.Optional;
import java.util.jar.*;
import javax.swing.*;
/**
* <p> <i>Jun 10, 2008</i> </p>
*
* @author mbillemo
*/
public class GUI implements UnlockFrame.SignInCallback {
@SuppressWarnings("UnusedDeclaration")
private static final Logger logger = Logger.get( GUI.class );
private final UnlockFrame unlockFrame = new UnlockFrame( this );
private PasswordFrame<?, ?> passwordFrame;
public static void main(final String... args) {
Thread.setDefaultUncaughtExceptionHandler(
(t, e) -> logger.err( e, "Uncaught: %s", e.getLocalizedMessage() ) );
if (Config.get().checkForUpdates())
checkUpdate();
// Try and set the system look & feel, if available.
try {
UIManager.setLookAndFeel( UIManager.getSystemLookAndFeelClassName() );
}
catch (final UnsupportedLookAndFeelException | ClassNotFoundException | InstantiationException | IllegalAccessException ignored) {
}
create().open();
}
private static GUI create() {
try {
// AppleGUI adds support for macOS features.
Optional<Class<GUI>> appleGUI = TypeUtils.loadClass( "com.lyndir.masterpassword.gui.platform.mac.AppleGUI" );
if (appleGUI.isPresent())
return appleGUI.get().getConstructor().newInstance();
}
catch (@SuppressWarnings("ErrorNotRethrown") final LinkageError ignored) {
}
catch (final IllegalAccessException | InstantiationException | NoSuchMethodException | InvocationTargetException e) {
throw logger.bug( e );
}
// Use platform-independent GUI.
return new GUI();
}
private static void checkUpdate() {
try {
Enumeration<URL> manifestURLs = Thread.currentThread().getContextClassLoader().getResources( JarFile.MANIFEST_NAME );
while (manifestURLs.hasMoreElements())
try (InputStream manifestStream = manifestURLs.nextElement().openStream()) {
Attributes attributes = new Manifest( manifestStream ).getMainAttributes();
if (!GUI.class.getCanonicalName().equals( attributes.getValue( Attributes.Name.MAIN_CLASS ) ))
continue;
String manifestRevision = attributes.getValue( Attributes.Name.IMPLEMENTATION_VERSION );
String upstreamRevision = new ByteSource() {
@Override
public InputStream openStream()
throws IOException {
URL url = URI.create( "https://masterpassword.app/masterpassword-gui.jar.rev" ).toURL();
URLConnection conn = url.openConnection();
conn.addRequestProperty( "User-Agent", "masterpassword-gui" );
return conn.getInputStream();
}
}.asCharSource( Charsets.UTF_8 ).readFirstLine();
if ((manifestRevision != null) && (upstreamRevision != null) && !manifestRevision.equalsIgnoreCase(
upstreamRevision )) {
logger.inf( "Local Revision: <%s>", manifestRevision );
logger.inf( "Upstream Revision: <%s>", upstreamRevision );
logger.wrn( "You are not running the current official version. Please update from:%n%s",
"https://masterpassword.app/masterpassword-gui.jar" );
JOptionPane.showMessageDialog( null,
strf( "A new version of Master Password is available.%n "
+ "Please download the latest version from %s",
"https://masterpassword.app" ),
"Update Available", JOptionPane.WARNING_MESSAGE );
}
}
catch (final IOException e) {
logger.wrn( e, "Couldn't check for version update." );
}
}
catch (final IOException e) {
logger.wrn( e, "Couldn't inspect JAR." );
}
}
protected void open() {
SwingUtilities.invokeLater( () -> {
if (passwordFrame == null)
unlockFrame.setVisible( true );
else
passwordFrame.setVisible( true );
} );
}
@Override
public void signedIn(final PasswordFrame<?, ?> passwordFrame) {
this.passwordFrame = passwordFrame;
open();
}
}

View File

@@ -19,22 +19,22 @@
package com.lyndir.masterpassword.gui; package com.lyndir.masterpassword.gui;
import com.lyndir.lhunath.opal.system.util.ConversionUtils; import com.lyndir.lhunath.opal.system.util.ConversionUtils;
import com.lyndir.masterpassword.model.MPConstants; import com.lyndir.masterpassword.model.MPModelConstants;
/** /**
* @author lhunath, 2014-08-31 * @author lhunath, 2014-08-31
*/ */
@SuppressWarnings("CallToSystemGetenv") @SuppressWarnings("CallToSystemGetenv")
public class Config { public class MPConfig {
private static final Config instance = new Config(); private static final MPConfig instance = new MPConfig();
public static Config get() { public static MPConfig get() {
return instance; return instance;
} }
public boolean checkForUpdates() { public boolean checkForUpdates() {
return ConversionUtils.toBoolean( System.getenv( MPConstants.env_checkUpdates ) ).orElse( true ); return ConversionUtils.toBoolean( System.getenv( MPModelConstants.env_checkUpdates ) ).orElse( true );
} }
} }

View File

@@ -0,0 +1,14 @@
package com.lyndir.masterpassword.gui;
import java.awt.event.InputEvent;
import java.awt.event.KeyEvent;
import javax.swing.*;
/**
* @author lhunath, 2018-07-31
*/
public final class MPGuiConstants {
public static final KeyStroke ui_hotkey = KeyStroke.getKeyStroke( KeyEvent.VK_P, InputEvent.CTRL_DOWN_MASK | InputEvent.META_DOWN_MASK );
}

View File

@@ -0,0 +1,157 @@
//==============================================================================
// This file is part of Master Password.
// Copyright (c) 2011-2017, Maarten Billemont.
//
// Master Password is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Master Password is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You can find a copy of the GNU General Public License in the
// LICENSE file. Alternatively, see <http://www.gnu.org/licenses/>.
//==============================================================================
package com.lyndir.masterpassword.gui;
import static com.lyndir.lhunath.opal.system.util.StringUtils.*;
import com.google.common.base.Charsets;
import com.google.common.io.ByteSource;
import com.lyndir.lhunath.opal.system.logging.Logger;
import com.lyndir.lhunath.opal.system.util.ObjectUtils;
import com.lyndir.masterpassword.gui.util.*;
import com.lyndir.masterpassword.gui.view.MasterPasswordFrame;
import com.lyndir.masterpassword.model.MPUser;
import com.tulskiy.keymaster.common.Provider;
import java.awt.*;
import java.io.IOException;
import java.io.InputStream;
import java.net.*;
import java.util.Collection;
import java.util.concurrent.CopyOnWriteArraySet;
import javax.annotation.Nullable;
import javax.swing.*;
/**
* <p> <i>Jun 10, 2008</i> </p>
*
* @author mbillemo
*/
public final class MasterPassword {
@SuppressWarnings("UnusedDeclaration")
private static final Logger logger = Logger.get( MasterPassword.class );
private static final MasterPassword instance = new MasterPassword();
private final Collection<Listener> listeners = new CopyOnWriteArraySet<>();
@Nullable
private MasterPasswordFrame frame;
@Nullable
private MPUser<?> activeUser;
public static MasterPassword get() {
return instance;
}
public void addListener(final Listener listener) {
if (listeners.add( listener ))
listener.onUserSelected( activeUser );
}
public void removeListener(final Listener listener) {
listeners.remove( listener );
}
public void activateUser(final MPUser<?> user) {
if (ObjectUtils.equals( activeUser, user ))
return;
activeUser = user;
for (final Listener listener : listeners)
listener.onUserSelected( activeUser );
}
@Nullable
public String version() {
return MasterPassword.class.getPackage().getImplementationVersion();
}
public void open() {
Res.ui( () -> {
if (frame == null)
frame = new MasterPasswordFrame();
frame.setAlwaysOnTop( true );
frame.setVisible( true );
frame.setExtendedState( Frame.NORMAL );
Platform.get().requestForeground();
frame.setAlwaysOnTop( false );
} );
}
public void checkUpdate() {
try {
String implementationVersion = version();
String latestVersion = new ByteSource() {
@Override
public InputStream openStream()
throws IOException {
URL url = URI.create( "https://masterpassword.app/masterpassword-gui.jar.rev" ).toURL();
URLConnection conn = url.openConnection();
conn.addRequestProperty( "User-Agent", "masterpassword-gui" );
return conn.getInputStream();
}
}.asCharSource( Charsets.UTF_8 ).readFirstLine();
if ((implementationVersion != null) && !implementationVersion.equalsIgnoreCase( latestVersion )) {
logger.inf( "Implementation: <%s>", implementationVersion );
logger.inf( "Latest : <%s>", latestVersion );
logger.wrn( "You are not running the current official version. Please update from:%n%s",
"https://masterpassword.app/masterpassword-gui.jar" );
JOptionPane.showMessageDialog( null, Components.linkLabel( strf(
"A new version of Master Password is available."
+ "<p>Please download the latest version from <a href='https://masterpassword.app'>https://masterpassword.app</a>." ) ),
"Update Available", JOptionPane.INFORMATION_MESSAGE );
}
}
catch (final IOException e) {
logger.wrn( e, "Couldn't check for version update." );
}
}
public static void main(final String... args) {
//Thread.setDefaultUncaughtExceptionHandler(
// (t, e) -> logger.bug( e, "Uncaught: %s", e.getLocalizedMessage() ) );
// Try and set the system look & feel, if available.
try {
UIManager.setLookAndFeel( UIManager.getSystemLookAndFeelClassName() );
Platform.get().installAppForegroundHandler( get()::open );
Platform.get().installAppReopenHandler( get()::open );
Provider.getCurrentProvider( true ).register( MPGuiConstants.ui_hotkey, hotKey -> get().open() );
}
catch (final UnsupportedLookAndFeelException | ClassNotFoundException | InstantiationException | IllegalAccessException ignored) {
}
// Create a platform-specific GUI and open it.
get().open();
// Check online to see if this version has been superseded.
if (MPConfig.get().checkForUpdates())
get().checkUpdate();
}
@SuppressWarnings("InterfaceMayBeAnnotatedFunctional")
public interface Listener {
void onUserSelected(@Nullable MPUser<?> user);
}
}

View File

@@ -1,356 +0,0 @@
//==============================================================================
// This file is part of Master Password.
// Copyright (c) 2011-2017, Maarten Billemont.
//
// Master Password is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Master Password is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You can find a copy of the GNU General Public License in the
// LICENSE file. Alternatively, see <http://www.gnu.org/licenses/>.
//==============================================================================
package com.lyndir.masterpassword.gui;
import static com.lyndir.lhunath.opal.system.util.ObjectUtils.*;
import static com.lyndir.lhunath.opal.system.util.StringUtils.*;
import com.google.common.collect.Maps;
import com.google.common.io.Resources;
import com.google.common.util.concurrent.JdkFutureAdapters;
import com.google.common.util.concurrent.ListenableFuture;
import com.lyndir.lhunath.opal.system.logging.Logger;
import com.lyndir.masterpassword.MPIdenticon;
import java.awt.*;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.image.ImageObserver;
import java.io.IOException;
import java.lang.ref.SoftReference;
import java.net.URL;
import java.util.Map;
import java.util.WeakHashMap;
import java.util.concurrent.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.swing.*;
import org.jetbrains.annotations.NonNls;
/**
* @author lhunath, 2014-06-11
*/
@SuppressWarnings({ "HardcodedFileSeparator", "MethodReturnAlwaysConstant", "SpellCheckingInspection" })
public abstract class Res {
private static final int AVATAR_COUNT = 19;
private static final Map<Window, ScheduledExecutorService> jobExecutorByWindow = new WeakHashMap<>();
private static final Executor immediateUiExecutor = new SwingExecutorService( true );
private static final Executor laterUiExecutor = new SwingExecutorService( false );
private static final Logger logger = Logger.get( Res.class );
private static final Colors colors = new Colors();
public static Future<?> job(final Window host, final Runnable job) {
return job( host, job, 0, TimeUnit.MILLISECONDS );
}
public static Future<?> job(final Window host, final Runnable job, final long delay, final TimeUnit timeUnit) {
return jobExecutor( host ).schedule( () -> {
try {
job.run();
}
catch (final Throwable t) {
logger.err( t, "Unexpected: %s", t.getLocalizedMessage() );
}
}, delay, timeUnit );
}
public static <V> ListenableFuture<V> job(final Window host, final Callable<V> job) {
return job( host, job, 0, TimeUnit.MILLISECONDS );
}
public static <V> ListenableFuture<V> job(final Window host, final Callable<V> job, final long delay, final TimeUnit timeUnit) {
ScheduledExecutorService executor = jobExecutor( host );
return JdkFutureAdapters.listenInPoolThread( executor.schedule( job::call, delay, timeUnit ), executor );
}
public static Executor uiExecutor(final boolean immediate) {
return immediate? immediateUiExecutor: laterUiExecutor;
}
public static ScheduledExecutorService jobExecutor(final Window host) {
ScheduledExecutorService executor = jobExecutorByWindow.get( host );
if (executor == null) {
jobExecutorByWindow.put( host, executor = Executors.newSingleThreadScheduledExecutor() );
host.addWindowListener( new WindowAdapter() {
@Override
public void windowClosed(final WindowEvent e) {
ExecutorService executor = jobExecutorByWindow.remove( host );
if (executor != null)
executor.shutdownNow();
}
} );
}
return executor;
}
public static Icon iconAdd() {
return new RetinaIcon( Resources.getResource( "media/icon_add@2x.png" ) );
}
public static Icon iconDelete() {
return new RetinaIcon( Resources.getResource( "media/icon_delete@2x.png" ) );
}
public static Icon iconQuestion() {
return new RetinaIcon( Resources.getResource( "media/icon_question@2x.png" ) );
}
public static Icon avatar(final int index) {
return new RetinaIcon( Resources.getResource( strf( "media/avatar-%d@2x.png", index % avatars() ) ) );
}
public static int avatars() {
return AVATAR_COUNT;
}
public static Font emoticonsFont() {
return emoticonsRegular();
}
public static Font controlFont() {
return arimoRegular();
}
public static Font valueFont() {
return sourceSansProRegular();
}
public static Font bigValueFont() {
return sourceSansProBlack();
}
public static Font emoticonsRegular() {
return font( "fonts/Emoticons-Regular.otf" );
}
public static Font sourceCodeProRegular() {
return font( "fonts/SourceCodePro-Regular.otf" );
}
public static Font sourceCodeProBlack() {
return font( "fonts/SourceCodePro-Bold.otf" );
}
public static Font sourceSansProRegular() {
return font( "fonts/SourceSansPro-Regular.otf" );
}
public static Font sourceSansProBlack() {
return font( "fonts/SourceSansPro-Bold.otf" );
}
public static Font exoBold() {
return font( "fonts/Exo2.0-Bold.otf" );
}
public static Font exoExtraBold() {
return font( "fonts/Exo2.0-ExtraBold.otf" );
}
public static Font exoRegular() {
return font( "fonts/Exo2.0-Regular.otf" );
}
public static Font exoThin() {
return font( "fonts/Exo2.0-Thin.otf" );
}
public static Font arimoBold() {
return font( "fonts/Arimo-Bold.ttf" );
}
public static Font arimoBoldItalic() {
return font( "fonts/Arimo-BoldItalic.ttf" );
}
public static Font arimoItalic() {
return font( "fonts/Arimo-Italic.ttf" );
}
public static Font arimoRegular() {
return font( "fonts/Arimo-Regular.ttf" );
}
private static Font font(@NonNls final String fontResourceName) {
Map<String, SoftReference<Font>> fontsByResourceName = Maps.newHashMap();
SoftReference<Font> fontRef = fontsByResourceName.get( fontResourceName );
Font font = (fontRef == null)? null: fontRef.get();
if (font == null)
try {
fontsByResourceName.put( fontResourceName, new SoftReference<>(
font = Font.createFont( Font.TRUETYPE_FONT, Resources.getResource( fontResourceName ).openStream() ) ) );
}
catch (final FontFormatException | IOException e) {
throw logger.bug( e );
}
return font;
}
public static Colors colors() {
return colors;
}
private static final class RetinaIcon extends ImageIcon {
private static final Pattern scalePattern = Pattern.compile( ".*@(\\d+)x.[^.]+$" );
private static final long serialVersionUID = 1L;
private final float scale;
private RetinaIcon(final URL url) {
super( url );
Matcher scaleMatcher = scalePattern.matcher( url.getPath() );
scale = scaleMatcher.matches()? Float.parseFloat( scaleMatcher.group( 1 ) ): 1;
}
//private static URL retinaURL(final URL url) {
// try {
// final boolean[] isRetina = new boolean[1];
// new apple.awt.CImage.HiDPIScaledImage(1,1, BufferedImage.TYPE_INT_ARGB) {
// @Override
// public void drawIntoImage(BufferedImage image, float v) {
// isRetina[0] = v > 1;
// }
// };
// return isRetina[0];
// } catch (Throwable e) {
// e.printStackTrace();
// return url;
// }
//}
@Override
public int getIconWidth() {
return (int) (super.getIconWidth() / scale);
}
@Override
public int getIconHeight() {
return (int) (super.getIconHeight() / scale);
}
@Override
public synchronized void paintIcon(final Component c, final Graphics g, final int x, final int y) {
ImageObserver observer = ifNotNullElse( getImageObserver(), c );
Image image = getImage();
int width = image.getWidth( observer );
int height = image.getHeight( observer );
Graphics2D g2d = (Graphics2D) g.create( x, y, width, height );
g2d.scale( 1 / scale, 1 / scale );
g2d.drawImage( image, 0, 0, observer );
g2d.scale( 1, 1 );
g2d.dispose();
}
}
public static class Colors {
private final Color frameBg = Color.decode( "#5A5D6B" );
private final Color controlBg = Color.decode( "#ECECEC" );
private final Color controlBorder = Color.decode( "#BFBFBF" );
public Color frameBg() {
return frameBg;
}
public Color controlBg() {
return controlBg;
}
public Color controlBorder() {
return controlBorder;
}
public Color fromIdenticonColor(final MPIdenticon.Color identiconColor, final BackgroundMode backgroundMode) {
switch (identiconColor) {
case RED:
switch (backgroundMode) {
case DARK:
return Color.decode( "#dc322f" );
case LIGHT:
return Color.decode( "#dc322f" );
}
break;
case GREEN:
switch (backgroundMode) {
case DARK:
return Color.decode( "#859900" );
case LIGHT:
return Color.decode( "#859900" );
}
break;
case YELLOW:
switch (backgroundMode) {
case DARK:
return Color.decode( "#b58900" );
case LIGHT:
return Color.decode( "#b58900" );
}
break;
case BLUE:
switch (backgroundMode) {
case DARK:
return Color.decode( "#268bd2" );
case LIGHT:
return Color.decode( "#268bd2" );
}
break;
case MAGENTA:
switch (backgroundMode) {
case DARK:
return Color.decode( "#d33682" );
case LIGHT:
return Color.decode( "#d33682" );
}
break;
case CYAN:
switch (backgroundMode) {
case DARK:
return Color.decode( "#2aa198" );
case LIGHT:
return Color.decode( "#2aa198" );
}
break;
case MONO:
switch (backgroundMode) {
case DARK:
return Color.decode( "#93a1a1" );
case LIGHT:
return Color.decode( "#586e75" );
}
break;
}
throw new IllegalArgumentException( strf( "Color: %s or mode: %s not supported: ", identiconColor, backgroundMode ) );
}
public enum BackgroundMode {
DARK, LIGHT
}
}
}

View File

@@ -31,17 +31,7 @@ import javax.annotation.Nullable;
*/ */
public class MPIncognitoQuestion extends MPBasicQuestion { public class MPIncognitoQuestion extends MPBasicQuestion {
private final MPIncognitoSite site;
public MPIncognitoQuestion(final MPIncognitoSite site, final String keyword, @Nullable final MPResultType type) { public MPIncognitoQuestion(final MPIncognitoSite site, final String keyword, @Nullable final MPResultType type) {
super( keyword, ifNotNullElse( type, site.getAlgorithm().mpw_default_answer_type() ) ); super( site, keyword, ifNotNullElse( type, site.getAlgorithm().mpw_default_answer_type() ) );
this.site = site;
}
@Nonnull
@Override
public MPIncognitoSite getSite() {
return site;
} }
} }

View File

@@ -29,25 +29,21 @@ import javax.annotation.Nullable;
/** /**
* @author lhunath, 14-12-16 * @author lhunath, 14-12-16
*/ */
public class MPIncognitoSite extends MPBasicSite<MPIncognitoQuestion> { public class MPIncognitoSite extends MPBasicSite<MPIncognitoUser, MPIncognitoQuestion> {
private final MPIncognitoUser user; public MPIncognitoSite(final MPIncognitoUser user, final String siteName) {
this( user, siteName, null, null, null, null );
public MPIncognitoSite(final MPIncognitoUser user, final String name) {
this( user, name, null, null, null, null );
} }
public MPIncognitoSite(final MPIncognitoUser user, final String name, public MPIncognitoSite(final MPIncognitoUser user, final String siteName,
@Nullable final MPAlgorithm algorithm, @Nullable final UnsignedInteger counter, @Nullable final MPAlgorithm algorithm, @Nullable final UnsignedInteger counter,
@Nullable final MPResultType resultType, @Nullable final MPResultType loginType) { @Nullable final MPResultType resultType, @Nullable final MPResultType loginType) {
super( name, (algorithm == null)? user.getAlgorithm(): algorithm, counter, resultType, loginType ); super( user, siteName, (algorithm == null)? user.getAlgorithm(): algorithm, counter, resultType, loginType );
this.user = user;
} }
@Nonnull @Nonnull
@Override @Override
public MPIncognitoUser getUser() { public MPIncognitoQuestion addQuestion(final String keyword) {
return user; return addQuestion( new MPIncognitoQuestion( this, keyword, null ) );
} }
} }

View File

@@ -20,6 +20,7 @@ package com.lyndir.masterpassword.gui.model;
import com.lyndir.masterpassword.MPAlgorithm; import com.lyndir.masterpassword.MPAlgorithm;
import com.lyndir.masterpassword.model.impl.MPBasicUser; import com.lyndir.masterpassword.model.impl.MPBasicUser;
import javax.annotation.Nonnull;
import javax.annotation.Nullable; import javax.annotation.Nullable;
@@ -37,4 +38,10 @@ public class MPIncognitoUser extends MPBasicUser<MPIncognitoSite> {
public byte[] getKeyID() { public byte[] getKeyID() {
return null; return null;
} }
@Nonnull
@Override
public MPIncognitoSite addSite(final String siteName) {
return addSite( new MPIncognitoSite( this, siteName ) );
}
} }

View File

@@ -0,0 +1,15 @@
package com.lyndir.masterpassword.gui.model;
import com.lyndir.masterpassword.model.MPSite;
import com.lyndir.masterpassword.model.impl.MPBasicQuestion;
/**
* @author lhunath, 2018-07-27
*/
public class MPNewQuestion extends MPBasicQuestion {
public MPNewQuestion(final MPSite<?> site, final String keyword) {
super( site, keyword, site.getAlgorithm().mpw_default_answer_type() );
}
}

View File

@@ -0,0 +1,32 @@
package com.lyndir.masterpassword.gui.model;
import com.lyndir.masterpassword.model.*;
import com.lyndir.masterpassword.model.impl.MPBasicSite;
import javax.annotation.Nonnull;
/**
* @author lhunath, 2018-07-27
*/
public class MPNewSite extends MPBasicSite<MPUser<?>, MPQuestion> {
public MPNewSite(final MPUser<?> user, final String siteName) {
super( user, siteName );
}
@Nonnull
@Override
public MPQuestion addQuestion(final String keyword) {
throw new UnsupportedOperationException( "Cannot add a question to a site that hasn't been created yet." );
}
public <S extends MPSite<?>> S addTo(final MPUser<S> user) {
S site = user.addSite( getSiteName() );
site.setAlgorithm( getAlgorithm() );
site.setCounter( getCounter() );
site.setLoginType( getLoginType() );
site.setResultType( getResultType() );
return site;
}
}

View File

@@ -1,57 +0,0 @@
//==============================================================================
// This file is part of Master Password.
// Copyright (c) 2011-2017, Maarten Billemont.
//
// Master Password is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Master Password is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You can find a copy of the GNU General Public License in the
// LICENSE file. Alternatively, see <http://www.gnu.org/licenses/>.
//==============================================================================
package com.lyndir.masterpassword.gui.platform.mac;
import com.apple.eawt.*;
import com.google.common.base.Preconditions;
import com.lyndir.masterpassword.gui.GUI;
/**
* @author lhunath, 2014-06-10
*/
public class AppleGUI extends GUI {
static Application application;
static {
application = Preconditions.checkNotNull( Application.getApplication(), "Not an Apple Java application." );
}
public AppleGUI() {
application.addAppEventListener( new ApplicationListener() );
}
private class ApplicationListener implements AppForegroundListener, AppReOpenedListener {
@Override
public void appMovedToBackground(final AppEvent.AppForegroundEvent arg0) {
}
@Override
public void appRaisedToForeground(final AppEvent.AppForegroundEvent arg0) {
open();
}
@Override
public void appReOpened(final AppEvent.AppReOpenedEvent arg0) {
open();
}
}
}

View File

@@ -0,0 +1,160 @@
package com.lyndir.masterpassword.gui.util;
import static com.google.common.base.Preconditions.*;
import com.google.common.base.Predicates;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import com.lyndir.lhunath.opal.system.logging.Logger;
import java.util.*;
import java.util.function.Consumer;
import javax.annotation.Nullable;
import javax.swing.*;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
/**
* @author lhunath, 2018-07-19
*/
@SuppressWarnings("serial")
public class CollectionListModel<E> extends AbstractListModel<E>
implements ComboBoxModel<E>, ListSelectionListener, Selectable<E, CollectionListModel<E>> {
private static final Logger logger = Logger.get( CollectionListModel.class );
private final List<E> model = new LinkedList<>();
@Nullable
private JList<E> list;
@Nullable
private E selectedItem;
@Nullable
private Consumer<E> selectionConsumer;
@SafeVarargs
public CollectionListModel(final E... elements) {
this( Arrays.asList( elements ) );
}
public CollectionListModel(final Collection<? extends E> elements) {
model.addAll( elements );
selectedItem = getElementAt( 0 );
fireIntervalAdded( this, 0, model.size() );
}
@Override
public synchronized int getSize() {
return model.size();
}
@Nullable
@Override
public synchronized E getElementAt(final int index) {
return (index < model.size())? model.get( index ): null;
}
/**
* Replace this model's contents with the objects from the new model collection.
*
* This operation will mutate the internal model to reflect the given model.
* The given model will remain untouched and independent from this object.
*/
@SuppressWarnings({ "Guava", "AssignmentToForLoopParameter" })
public synchronized void set(final Iterable<? extends E> elements) {
ListIterator<E> oldIt = model.listIterator();
for (int from = 0; oldIt.hasNext(); ++from) {
int to = Iterables.indexOf( elements, Predicates.equalTo( oldIt.next() ) );
if (to != from) {
oldIt.remove();
fireIntervalRemoved( this, from, from );
--from;
}
}
int to = 0;
for (final E newSite : elements) {
if ((to >= model.size()) || !Objects.equals( model.get( to ), newSite )) {
model.add( to, newSite );
fireIntervalAdded( this, to, to );
}
++to;
}
if ((selectedItem == null) || !model.contains( selectedItem ))
selectItem( getElementAt( 0 ) );
}
@SafeVarargs
public final synchronized void set(final E... elements) {
set( ImmutableList.copyOf( elements ) );
}
@Override
@Deprecated
@SuppressWarnings("unchecked")
public synchronized void setSelectedItem(@Nullable final Object/* E */ newSelectedItem) {
selectItem( (E) newSelectedItem );
}
public synchronized CollectionListModel<E> selectItem(@Nullable final E newSelectedItem) {
if (Objects.equals( selectedItem, newSelectedItem ))
return this;
selectedItem = newSelectedItem;
fireContentsChanged( this, -1, -1 );
//noinspection ObjectEquality
if ((list != null) && (list.getModel() == this))
list.setSelectedValue( selectedItem, true );
if (selectionConsumer != null)
selectionConsumer.accept( selectedItem );
return this;
}
@Nullable
@Override
public synchronized E getSelectedItem() {
return selectedItem;
}
public synchronized void registerList(final JList<E> list) {
// TODO: This class should probably implement ListSelectionModel instead.
if (this.list != null)
this.list.removeListSelectionListener( this );
this.list = list;
this.list.addListSelectionListener( this );
this.list.setModel( this );
}
@Override
public synchronized CollectionListModel<E> selection(@Nullable final Consumer<E> selectionConsumer) {
this.selectionConsumer = selectionConsumer;
if (selectionConsumer != null)
selectionConsumer.accept( selectedItem );
return this;
}
@Override
public synchronized CollectionListModel<E> selection(@Nullable final E selectedItem, @Nullable final Consumer<E> selectionConsumer) {
this.selectionConsumer = null;
selectItem( selectedItem );
return selection( selectionConsumer );
}
@Override
public synchronized void valueChanged(final ListSelectionEvent event) {
//noinspection ObjectEquality
if (!event.getValueIsAdjusting() && (event.getSource() == list) && (checkNotNull( list ).getModel() == this)) {
selectedItem = list.getSelectedValue();
if (selectionConsumer != null)
selectionConsumer.accept( selectedItem );
}
}
}

View File

@@ -18,24 +18,51 @@
package com.lyndir.masterpassword.gui.util; package com.lyndir.masterpassword.gui.util;
import com.lyndir.masterpassword.gui.Res; import com.google.common.base.Strings;
import com.lyndir.lhunath.opal.system.logging.Logger;
import java.awt.*; import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.File;
import java.net.URISyntaxException;
import java.util.Arrays;
import java.util.Collection;
import java.util.function.Consumer;
import java.util.function.Function;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import javax.swing.*; import javax.swing.*;
import javax.swing.border.Border; import javax.swing.border.Border;
import javax.swing.border.CompoundBorder; import javax.swing.border.CompoundBorder;
import javax.swing.event.HyperlinkEvent;
import javax.swing.text.*;
import org.jetbrains.annotations.NonNls;
/** /**
* @author lhunath, 2014-06-08 * @author lhunath, 2014-06-08
*/ */
@SuppressWarnings({ "SerializableStoresNonSerializable", "serial" })
public abstract class Components { public abstract class Components {
private static final float CONTROL_TEXT_SIZE = 12f; private static final Logger logger = Logger.get( Components.class );
public static GradientPanel boxLayout(final int axis, final Component... components) { public static final int TEXT_SIZE_HEADING = 19;
GradientPanel container = gradientPanel( null, null ); public static final int TEXT_SIZE_CONTROL = 13;
// container.setBackground( Color.red ); public static final int SIZE_MARGIN = 12;
public static final int SIZE_PADDING = 8;
public static GradientPanel panel(final Component... components) {
GradientPanel panel = panel( BoxLayout.LINE_AXIS, null, components );
panel.setLayout( new OverlayLayout( panel ) );
return panel;
}
public static GradientPanel panel(final int axis, final Component... components) {
return panel( axis, null, components );
}
public static GradientPanel panel(final int axis, @Nullable final Color background, final Component... components) {
GradientPanel container = panel( null, background );
container.setLayout( new BoxLayout( container, axis ) ); container.setLayout( new BoxLayout( container, axis ) );
for (final Component component : components) for (final Component component : components)
container.add( component ); container.add( component );
@@ -43,46 +70,142 @@ public abstract class Components {
return container; return container;
} }
public static GradientPanel borderPanel(final JComponent component, @Nullable final Border border) { public static GradientPanel borderPanel(final int axis, final Component... components) {
return borderPanel( component, border, null ); return borderPanel( marginBorder(), null, axis, components );
} }
public static GradientPanel borderPanel(final JComponent component, @Nullable final Border border, @Nullable final Color background) { public static GradientPanel borderPanel(@Nullable final Border border, final int axis, final Component... components) {
GradientPanel box = boxLayout( BoxLayout.LINE_AXIS, component ); return borderPanel( border, null, axis, components );
}
public static GradientPanel borderPanel(@Nullable final Color background, final int axis, final Component... components) {
return borderPanel( marginBorder(), background, axis, components );
}
public static GradientPanel borderPanel(@Nullable final Border border, @Nullable final Color background, final int axis,
final Component... components) {
GradientPanel box = panel( axis, background, components );
if (border != null) if (border != null)
box.setBorder( border ); box.setBorder( border );
if (background != null)
box.setBackground( background );
return box; return box;
} }
public static GradientPanel gradientPanel(@Nullable final LayoutManager layout, @Nullable final Color color) { public static GradientPanel panel(@Nullable final LayoutManager layout) {
return new GradientPanel( layout, color ) { return panel( layout, null );
{
setOpaque( color != null );
setBackground( null );
setAlignmentX( LEFT_ALIGNMENT );
setAlignmentY( BOTTOM_ALIGNMENT );
} }
};
public static GradientPanel panel(@Nullable final LayoutManager layout, @Nullable final Color color) {
return new GradientPanel( layout, color );
}
public static int showDialog(@Nullable final Component owner, @Nullable final String title, final JOptionPane pane) {
JDialog dialog = pane.createDialog( owner, title );
dialog.setMinimumSize( new Dimension( 520, 0 ) );
dialog.setModalityType( Dialog.ModalityType.DOCUMENT_MODAL );
showDialog( dialog );
Object selectedValue = pane.getValue();
if (selectedValue == null)
return JOptionPane.CLOSED_OPTION;
Object[] options = pane.getOptions();
if (options == null)
return (selectedValue instanceof Integer)? (Integer) selectedValue: JOptionPane.CLOSED_OPTION;
try {
int option = Arrays.binarySearch( options, selectedValue );
return (option < 0)? JOptionPane.CLOSED_OPTION: option;
}
catch (final ClassCastException ignored) {
return JOptionPane.CLOSED_OPTION;
}
}
@Nullable
public static File showLoadDialog(@Nullable final Component owner, final String title) {
return showFileDialog( owner, title, FileDialog.LOAD, null );
}
@Nullable
public static File showSaveDialog(@Nullable final Component owner, final String title, final String fileName) {
return showFileDialog( owner, title, FileDialog.SAVE, fileName );
}
@Nullable
private static File showFileDialog(@Nullable final Component owner, final String title,
final int mode, @Nullable final String fileName) {
FileDialog fileDialog = new FileDialog( JOptionPane.getFrameForComponent( owner ), title, mode );
fileDialog.setFile( fileName );
fileDialog.setLocationRelativeTo( owner );
fileDialog.setLocationByPlatform( true );
fileDialog.setVisible( true );
File[] selectedFiles = fileDialog.getFiles();
return ((selectedFiles != null) && (selectedFiles.length > 0))? selectedFiles[0]: null;
}
public static JDialog showDialog(@Nullable final Component owner, @Nullable final String title, final Container content) {
JDialog dialog = new JDialog( (owner != null)? SwingUtilities.windowForComponent( owner ): null,
title, Dialog.ModalityType.DOCUMENT_MODAL );
dialog.setMinimumSize( new Dimension( 320, 0 ) );
dialog.setLocationRelativeTo( owner );
dialog.setContentPane( content );
return showDialog( dialog );
}
private static JDialog showDialog(final JDialog dialog) {
// OpenJDK does not correctly implement this setting in native code.
dialog.getRootPane().putClientProperty( "apple.awt.documentModalSheet", Boolean.TRUE );
dialog.getRootPane().putClientProperty( "Window.style", "small" );
dialog.pack();
dialog.setLocationByPlatform( true );
dialog.setVisible( true );
return dialog;
} }
public static JTextField textField() { public static JTextField textField() {
return new JTextField() { return textField( null );
}
public static JTextField textField(@Nullable final Document document) {
return new JTextField( document, null, 0 ) {
{ {
setBorder( BorderFactory.createCompoundBorder( BorderFactory.createLineBorder( Res.colors().controlBorder(), 1, true ), setBorder( BorderFactory.createCompoundBorder( BorderFactory.createLineBorder( Res.colors().controlBorder(), 1, true ),
BorderFactory.createEmptyBorder( 4, 4, 4, 4 ) ) ); BorderFactory.createEmptyBorder( 4, 4, 4, 4 ) ) );
setFont( Res.valueFont().deriveFont( CONTROL_TEXT_SIZE ) ); setFont( Res.fonts().valueFont( TEXT_SIZE_CONTROL ) );
setAlignmentX( LEFT_ALIGNMENT ); setAlignmentX( LEFT_ALIGNMENT );
setAlignmentY( BOTTOM_ALIGNMENT );
} }
@Override @Override
public Dimension getMaximumSize() { public Dimension getMaximumSize() {
return new Dimension( Integer.MAX_VALUE, getPreferredSize().height ); return new Dimension( Integer.MAX_VALUE, Integer.MAX_VALUE );
}
};
}
public static JTextField textField(@Nullable final String text, @Nullable final Consumer<String> change) {
return textField( new DocumentModel( new PlainDocument() ).selection( text, change ).getDocument() );
}
public static JTextArea textArea() {
return new JTextArea() {
{
setBorder( BorderFactory.createCompoundBorder( BorderFactory.createLineBorder( Res.colors().controlBorder(), 1, true ),
BorderFactory.createEmptyBorder( 4, 4, 4, 4 ) ) );
setFont( Res.fonts().valueFont( TEXT_SIZE_CONTROL ) );
setAlignmentX( LEFT_ALIGNMENT );
setLineWrap( true );
setRows( 3 );
}
@Override
public Dimension getMaximumSize() {
return new Dimension( Integer.MAX_VALUE, Integer.MAX_VALUE );
} }
}; };
} }
@@ -93,7 +216,6 @@ public abstract class Components {
setBorder( BorderFactory.createCompoundBorder( BorderFactory.createLineBorder( Res.colors().controlBorder(), 1, true ), setBorder( BorderFactory.createCompoundBorder( BorderFactory.createLineBorder( Res.colors().controlBorder(), 1, true ),
BorderFactory.createEmptyBorder( 4, 4, 4, 4 ) ) ); BorderFactory.createEmptyBorder( 4, 4, 4, 4 ) ) );
setAlignmentX( LEFT_ALIGNMENT ); setAlignmentX( LEFT_ALIGNMENT );
setAlignmentY( BOTTOM_ALIGNMENT );
} }
@Override @Override
@@ -103,49 +225,180 @@ public abstract class Components {
}; };
} }
public static JButton button(final String label) { public static <E> JList<E> list(final ListModel<E> model, final Function<E, String> valueTransformer) {
return new JButton( label ) { return new JList<E>( model ) {
{ {
setFont( Res.controlFont().deriveFont( CONTROL_TEXT_SIZE ) );
setAlignmentX( LEFT_ALIGNMENT ); setAlignmentX( LEFT_ALIGNMENT );
setAlignmentY( BOTTOM_ALIGNMENT ); setFont( Res.fonts().valueFont( TEXT_SIZE_CONTROL ) );
setCellRenderer( new DefaultListCellRenderer() {
@Override
@SuppressWarnings({ "unchecked", "SerializableStoresNonSerializable" })
public Component getListCellRendererComponent(final JList<?> list, final Object value, final int index,
final boolean isSelected, final boolean cellHasFocus) {
String label = valueTransformer.apply( (E) value );
super.getListCellRendererComponent(
list, Strings.isNullOrEmpty( label )? " ": label, index, isSelected, cellHasFocus );
setBorder( BorderFactory.createEmptyBorder( 2, 4, 2, 4 ) );
return this;
}
} );
Dimension cellSize = getCellRenderer().getListCellRendererComponent( this, null, 0, false, false ).getPreferredSize();
setFixedCellWidth( cellSize.width );
setFixedCellHeight( cellSize.height );
if (model instanceof CollectionListModel)
((CollectionListModel<E>) model).registerList( this );
} }
@Override @Override
public Dimension getMaximumSize() { public Dimension getMaximumSize() {
return new Dimension( 20, getPreferredSize().height ); return new Dimension( Integer.MAX_VALUE, Integer.MAX_VALUE );
} }
}; };
} }
public static Component stud() { public static JScrollPane scrollPane(final Component child) {
Dimension studDimension = new Dimension( 8, 8 ); return new JScrollPane( child ) {
{
setBorder( BorderFactory.createLineBorder( Res.colors().controlBorder(), 1, true ) );
setAlignmentX( LEFT_ALIGNMENT );
}
};
}
public static JButton button(final String label, @Nullable final ActionListener actionListener) {
return button( new AbstractAction( label ) {
@Override
public void actionPerformed(final ActionEvent e) {
if (actionListener != null)
actionListener.actionPerformed( e );
}
@Override
public boolean isEnabled() {
return actionListener != null;
}
} );
}
public static JButton button(final Icon icon, @Nullable final ActionListener actionListener, @Nullable String toolTip) {
JButton iconButton = button( new AbstractAction( null, icon ) {
@Override
public void actionPerformed(final ActionEvent e) {
if (actionListener != null)
actionListener.actionPerformed( e );
}
@Override
public boolean isEnabled() {
return actionListener != null;
}
} );
iconButton.setToolTipText( toolTip );
iconButton.setFocusable( false );
return iconButton;
}
public static JButton button(final Action action) {
return new JButton( action ) {
{
setAlignmentX( LEFT_ALIGNMENT );
if (getText() == null) {
setContentAreaFilled( false );
setBorderPainted( false );
setOpaque( false );
}
}
};
}
public static Component strut() {
return strut( SIZE_PADDING );
}
public static Component strut(final int size) {
Dimension studDimension = new Dimension( size, size );
Box.Filler rigidArea = new Box.Filler( studDimension, studDimension, studDimension ); Box.Filler rigidArea = new Box.Filler( studDimension, studDimension, studDimension );
rigidArea.setAlignmentX( Component.LEFT_ALIGNMENT ); rigidArea.setAlignmentX( Component.LEFT_ALIGNMENT );
rigidArea.setAlignmentY( Component.BOTTOM_ALIGNMENT );
rigidArea.setBackground( Color.red ); rigidArea.setBackground( Color.red );
return rigidArea; return rigidArea;
} }
public static int margin() {
return SIZE_MARGIN;
}
public static Border marginBorder() {
return marginBorder( margin() );
}
public static Border marginBorder(final int size) {
return BorderFactory.createEmptyBorder( size, size, size, size );
}
public static JSpinner spinner(final SpinnerModel model) { public static JSpinner spinner(final SpinnerModel model) {
return new JSpinner( model ) { return new JSpinner( model ) {
{ {
CompoundBorder editorBorder = BorderFactory.createCompoundBorder( CompoundBorder editorBorder = BorderFactory.createCompoundBorder(
BorderFactory.createLineBorder( Res.colors().controlBorder(), 1, true ), BorderFactory.createLineBorder( Res.colors().controlBorder(), 1, true ),
BorderFactory.createEmptyBorder( 4, 4, 4, 4 ) ); BorderFactory.createEmptyBorder( 4, 4, 4, 4 ) );
DefaultFormatterFactory formatterFactory = new DefaultFormatterFactory();
if (model instanceof UnsignedIntegerModel)
formatterFactory.setDefaultFormatter( ((UnsignedIntegerModel) model).getFormatter() );
((DefaultEditor) getEditor()).getTextField().setFormatterFactory( formatterFactory );
((DefaultEditor) getEditor()).getTextField().setBorder( editorBorder ); ((DefaultEditor) getEditor()).getTextField().setBorder( editorBorder );
setAlignmentX( LEFT_ALIGNMENT ); setAlignmentX( LEFT_ALIGNMENT );
setAlignmentY( BOTTOM_ALIGNMENT );
setBorder( null ); setBorder( null );
} }
};
}
public static JLabel heading() {
return heading( " " );
}
public static JLabel heading(final int horizontalAlignment) {
return heading( " ", horizontalAlignment );
}
public static JLabel heading(@Nullable final String heading) {
return heading( heading, SwingConstants.CENTER );
}
/**
* @param horizontalAlignment One of the following constants
* defined in {@code SwingConstants}:
* {@code LEFT},
* {@code CENTER},
* {@code RIGHT},
* {@code LEADING} or
* {@code TRAILING}.
*/
public static JLabel heading(@Nullable final String heading, final int horizontalAlignment) {
return new JLabel( heading, horizontalAlignment ) {
{
setFont( getFont().deriveFont( Font.BOLD, TEXT_SIZE_HEADING ) );
setAlignmentX( LEFT_ALIGNMENT );
}
@Override @Override
public Dimension getMaximumSize() { public Dimension getMaximumSize() {
return new Dimension( 20, getPreferredSize().height ); return new Dimension( Integer.MAX_VALUE, getPreferredSize().height );
} }
}; };
} }
public static JLabel label() {
return label( " " );
}
public static JLabel label(final int horizontalAlignment) {
return label( " ", horizontalAlignment );
}
public static JLabel label(@Nullable final String label) { public static JLabel label(@Nullable final String label) {
return label( label, SwingConstants.LEADING ); return label( label, SwingConstants.LEADING );
} }
@@ -162,9 +415,7 @@ public abstract class Components {
public static JLabel label(@Nullable final String label, final int horizontalAlignment) { public static JLabel label(@Nullable final String label, final int horizontalAlignment) {
return new JLabel( label, horizontalAlignment ) { return new JLabel( label, horizontalAlignment ) {
{ {
setFont( Res.controlFont().deriveFont( CONTROL_TEXT_SIZE ) );
setAlignmentX( LEFT_ALIGNMENT ); setAlignmentX( LEFT_ALIGNMENT );
setAlignmentY( BOTTOM_ALIGNMENT );
} }
@Override @Override
@@ -175,32 +426,62 @@ public abstract class Components {
} }
public static JCheckBox checkBox(final String label) { public static JCheckBox checkBox(final String label) {
return checkBox( label, false, null );
}
public static JCheckBox checkBox(final String label, final boolean selected, @Nullable final Consumer<Boolean> selectionConsumer) {
return new JCheckBox( label ) { return new JCheckBox( label ) {
{ {
setFont( Res.controlFont().deriveFont( CONTROL_TEXT_SIZE ) );
setBackground( null ); setBackground( null );
setAlignmentX( LEFT_ALIGNMENT ); setAlignmentX( LEFT_ALIGNMENT );
setAlignmentY( BOTTOM_ALIGNMENT ); setSelected( selected );
if (selectionConsumer != null)
addItemListener( e -> selectionConsumer.accept( isSelected() ) );
} }
}; };
} }
@SafeVarargs @SafeVarargs
public static <V> JComboBox<V> comboBox(final V... values) { public static <E> JComboBox<E> comboBox(final Function<E, String> valueTransformer, final E... values) {
return comboBox( new DefaultComboBoxModel<>( values ) ); return comboBox( new DefaultComboBoxModel<>( values ), valueTransformer );
} }
public static <M> JComboBox<M> comboBox(final ComboBoxModel<M> model) { public static <E> JComboBox<E> comboBox(final E[] values, final Function<E, String> valueTransformer,
return new JComboBox<M>( model ) { @Nullable final E selectedItem, @Nullable final Consumer<E> selectionConsumer) {
return comboBox( new CollectionListModel<>( values ).selection( selectedItem, selectionConsumer ), valueTransformer );
}
public static <E> JComboBox<E> comboBox(final Collection<E> values, final Function<E, String> valueTransformer,
@Nullable final Consumer<E> selectionConsumer) {
return comboBox( new CollectionListModel<>( values ).selection( selectionConsumer ), valueTransformer );
}
public static <E> JComboBox<E> comboBox(final Collection<E> values, final Function<E, String> valueTransformer,
@Nullable final E selectedItem, @Nullable final Consumer<E> selectionConsumer) {
return comboBox( new CollectionListModel<>( values ).selection( selectedItem, selectionConsumer ), valueTransformer );
}
public static <E> JComboBox<E> comboBox(final ComboBoxModel<E> model, final Function<E, String> valueTransformer) {
return new JComboBox<E>( model ) {
{ {
// CompoundBorder editorBorder = BorderFactory.createCompoundBorder( setFont( Res.fonts().valueFont( TEXT_SIZE_CONTROL ) );
// BorderFactory.createLineBorder( Res.colors().controlBorder(), 1, true ), setBorder( BorderFactory.createEmptyBorder( 4, 0, 4, 0 ) );
// BorderFactory.createEmptyBorder( 4, 4, 4, 4 ) ); setRenderer( new DefaultListCellRenderer() {
// ((JComponent) ((BasicComboBoxEditor) getEditor()).getEditorComponent()).setBorder(editorBorder); @Override
setFont( Res.controlFont().deriveFont( CONTROL_TEXT_SIZE ) ); @SuppressWarnings({ "unchecked", "SerializableStoresNonSerializable" })
public Component getListCellRendererComponent(final JList<?> list, final Object value, final int index,
final boolean isSelected, final boolean cellHasFocus) {
String label = valueTransformer.apply( (E) value );
super.getListCellRendererComponent(
list, Strings.isNullOrEmpty( label )? " ": label, index, isSelected, cellHasFocus );
setBorder( BorderFactory.createEmptyBorder( 0, 4, 0, 4 ) );
return this;
}
} );
putClientProperty( "JComboBox.isPopDown", Boolean.TRUE );
setAlignmentX( LEFT_ALIGNMENT ); setAlignmentX( LEFT_ALIGNMENT );
setAlignmentY( BOTTOM_ALIGNMENT );
// setBorder(null);
} }
@Override @Override
@@ -210,6 +491,24 @@ public abstract class Components {
}; };
} }
public static JEditorPane linkLabel(@NonNls final String html) {
return new JEditorPane( "text/html", "<html><body style='width:640;font-family:sans-serif'>" + html ) {
{
setOpaque( false );
setEditable( false );
addHyperlinkListener( event -> {
if (event.getEventType() == HyperlinkEvent.EventType.ACTIVATED)
try {
Platform.get().open( event.getURL().toURI() );
}
catch (final URISyntaxException e) {
logger.err( e, "After triggering hyperlink: %s", event );
}
} );
}
};
}
public static class GradientPanel extends JPanel { public static class GradientPanel extends JPanel {
@Nullable @Nullable
@@ -218,10 +517,26 @@ public abstract class Components {
@Nullable @Nullable
private GradientPaint paint; private GradientPaint paint;
protected GradientPanel(@Nullable final LayoutManager layout, @Nullable final Color gradientColor) { public GradientPanel() {
this( null, null );
}
public GradientPanel(@Nullable final Color gradientColor) {
this( null, gradientColor );
}
public GradientPanel(@Nullable final LayoutManager layout) {
this( layout, null );
}
public GradientPanel(@Nullable final LayoutManager layout, @Nullable final Color gradientColor) {
super( layout ); super( layout );
this.gradientColor = gradientColor; if (getLayout() == null)
setLayout( new BoxLayout( this, BoxLayout.PAGE_AXIS ) );
setGradientColor( gradientColor );
setBackground( null ); setBackground( null );
setAlignmentX( LEFT_ALIGNMENT );
} }
@Nullable @Nullable
@@ -231,17 +546,30 @@ public abstract class Components {
public void setGradientColor(@Nullable final Color gradientColor) { public void setGradientColor(@Nullable final Color gradientColor) {
this.gradientColor = gradientColor; this.gradientColor = gradientColor;
revalidate(); updatePaint();
} }
@Override @Override
public void doLayout() { public void setBackground(@Nullable final Color bg) {
super.doLayout(); super.setBackground( bg );
setOpaque( bg != null );
if (gradientColor != null) {
paint = new GradientPaint( new Point( 0, 0 ), gradientColor, new Point( getWidth(), getHeight() ), gradientColor.darker() );
repaint();
} }
@Override
public void setBounds(final int x, final int y, final int width, final int height) {
super.setBounds( x, y, width, height );
updatePaint();
}
private void updatePaint() {
if (gradientColor == null) {
paint = null;
return;
}
paint = new GradientPaint( new Point( 0, 0 ), gradientColor,
new Point( getWidth(), getHeight() ), gradientColor.darker() );
repaint();
} }
@Override @Override

View File

@@ -0,0 +1,27 @@
package com.lyndir.masterpassword.gui.util;
import java.util.function.Consumer;
import javax.annotation.Nullable;
/**
* @author lhunath, 2018-08-23
*/
public class ConsumingTrigger<T> implements Consumer<T> {
private final Runnable trigger;
@Nullable
private T value;
public ConsumingTrigger(final Runnable trigger) {
this.trigger = trigger;
}
@Override
public void accept(final T t) {
value = t;
trigger.run();
}
}

View File

@@ -0,0 +1,98 @@
package com.lyndir.masterpassword.gui.util;
import com.lyndir.lhunath.opal.system.logging.Logger;
import java.util.function.Consumer;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.text.BadLocationException;
import javax.swing.text.Document;
/**
* @author lhunath, 2018-08-24
*/
public class DocumentModel implements Selectable<String, DocumentModel> {
private static final Logger logger = Logger.get( DocumentModel.class );
private final Document document;
@Nullable
private DocumentListener documentListener;
public DocumentModel(final Document document) {
this.document = document;
}
@Nonnull
public Document getDocument() {
return document;
}
@Nullable
public String getText() {
try {
return (document.getLength() > 0)? document.getText( 0, document.getLength() ): null;
}
catch (final BadLocationException e) {
logger.wrn( "While getting text for model", e );
return null;
}
}
public void setText(@Nullable final String text) {
try {
if (document.getLength() > 0)
document.remove( 0, document.getLength() );
if (text != null)
document.insertString( 0, text, null );
}
catch (final BadLocationException e) {
logger.err( "While setting text for model", e );
}
}
@Override
public DocumentModel selection(@Nullable final Consumer<String> selectionConsumer) {
if (documentListener != null)
document.removeDocumentListener( documentListener );
if (selectionConsumer != null)
document.addDocumentListener( documentListener = new DocumentListener() {
@Override
public void insertUpdate(final DocumentEvent e) {
trigger();
}
@Override
public void removeUpdate(final DocumentEvent e) {
trigger();
}
@Override
public void changedUpdate(final DocumentEvent e) {
trigger();
}
private void trigger() {
selectionConsumer.accept( getText() );
}
} );
return this;
}
@Override
public DocumentModel selection(@Nullable final String selectedItem, @Nullable final Consumer<String> selectionConsumer) {
setText( selectedItem );
selection( selectionConsumer );
if (selectionConsumer != null)
selectionConsumer.accept( selectedItem );
return this;
}
}

View File

@@ -0,0 +1,52 @@
package com.lyndir.masterpassword.gui.util;
import com.lyndir.lhunath.opal.system.logging.Logger;
import com.lyndir.lhunath.opal.system.util.TypeUtils;
import com.lyndir.masterpassword.gui.util.platform.BasePlatform;
import com.lyndir.masterpassword.gui.util.platform.IPlatform;
import java.lang.reflect.InvocationTargetException;
import java.util.Optional;
import javax.annotation.Nullable;
/**
* @author lhunath, 2018-07-29
*/
public final class Platform {
private static final Logger logger = Logger.get( Platform.class );
private static final IPlatform activePlatform;
static {
IPlatform tryPlatform;
if (null != (tryPlatform = construct( "com.lyndir.masterpassword.gui.util.platform.JDK9Platform" )))
activePlatform = tryPlatform;
else if (null != (tryPlatform = construct( "com.lyndir.masterpassword.gui.util.platform.ApplePlatform" )))
activePlatform = tryPlatform;
else
activePlatform = new BasePlatform();
}
@Nullable
private static <T> T construct(final String typeName) {
try {
// AppleGUI adds support for macOS features.
Optional<Class<T>> gui = TypeUtils.loadClass( typeName );
if (gui.isPresent())
return gui.get().getConstructor().newInstance();
}
catch (@SuppressWarnings("ErrorNotRethrown") final LinkageError ignored) {
}
catch (final IllegalAccessException | InstantiationException | NoSuchMethodException | InvocationTargetException e) {
throw logger.bug( e );
}
return null;
}
public static IPlatform get() {
return activePlatform;
}
}

View File

@@ -0,0 +1,349 @@
//==============================================================================
// This file is part of Master Password.
// Copyright (c) 2011-2017, Maarten Billemont.
//
// Master Password is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Master Password is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You can find a copy of the GNU General Public License in the
// LICENSE file. Alternatively, see <http://www.gnu.org/licenses/>.
//==============================================================================
package com.lyndir.masterpassword.gui.util;
import static com.lyndir.lhunath.opal.system.util.StringUtils.*;
import com.google.common.io.Resources;
import com.google.common.util.concurrent.*;
import com.lyndir.lhunath.opal.system.logging.Logger;
import com.lyndir.masterpassword.MPIdenticon;
import java.awt.*;
import java.io.IOException;
import java.util.concurrent.*;
import java.util.function.Consumer;
import javax.annotation.Nullable;
import javax.swing.*;
import org.jetbrains.annotations.NonNls;
import org.joda.time.*;
import org.joda.time.format.DateTimeFormat;
/**
* @author lhunath, 2014-06-11
*/
@SuppressWarnings({ "HardcodedFileSeparator", "MethodReturnAlwaysConstant", "SpellCheckingInspection", "serial" })
public abstract class Res {
private static final int AVATAR_COUNT = 19;
private static final ListeningScheduledExecutorService jobExecutor = MoreExecutors.listeningDecorator(
Executors.newSingleThreadScheduledExecutor() );
private static final Executor immediateUiExecutor = new SwingExecutorService( true );
private static final Executor laterUiExecutor = new SwingExecutorService( false );
private static final Logger logger = Logger.get( Res.class );
private static final Icons icons = new Icons();
private static final Fonts fonts = new Fonts();
private static final Colors colors = new Colors();
public static Future<?> job(final Runnable job) {
return job( job, 0, TimeUnit.MILLISECONDS );
}
public static Future<?> job(final Runnable job, final long delay, final TimeUnit timeUnit) {
return jobExecutor.schedule( () -> {
try {
job.run();
}
catch (final Throwable t) {
logger.err( t, "Unexpected: %s", t.getLocalizedMessage() );
}
}, delay, timeUnit );
}
public static <V> ListenableFuture<V> job(final Callable<V> job) {
return job( job, 0, TimeUnit.MILLISECONDS );
}
public static <V> ListenableFuture<V> job(final Callable<V> job, final long delay, final TimeUnit timeUnit) {
return jobExecutor.schedule( job, delay, timeUnit );
}
public static void ui(final Runnable job) {
ui( true, job );
}
public static <V> void ui(final ListenableFuture<V> future, final Consumer<V> job) {
Futures.addCallback( future, new FailableCallback<V>( logger ) {
@Override
public void onSuccess(@Nullable final V result) {
job.accept( result );
}
}, uiExecutor() );
}
public static void ui(final boolean immediate, final Runnable job) {
uiExecutor( immediate ).execute( job );
}
public static Executor uiExecutor() {
return uiExecutor( true );
}
public static Executor uiExecutor(final boolean immediate) {
return immediate? immediateUiExecutor: laterUiExecutor;
}
public static Icons icons() {
return icons;
}
public static Fonts fonts() {
return fonts;
}
public static Colors colors() {
return colors;
}
public static String format(final ReadableInstant instant) {
return DateTimeFormat.mediumDateTime().print( new DateTime( instant, DateTimeZone.getDefault() ) );
}
public static final class Icons {
public Icon add() {
return icon( "media/icon_add.png" );
}
public Icon delete() {
return icon( "media/icon_delete.png" );
}
public Icon question() {
return icon( "media/icon_question.png" );
}
public Icon user() {
return icon( "media/icon_user.png" );
}
public Icon lock() {
return icon( "media/icon_lock.png" );
}
public Icon reset() {
return icon( "media/icon_reset.png" );
}
public Icon import_() {
return icon( "media/icon_import.png" );
}
public Icon help() {
return icon( "media/icon_help.png" );
}
public Icon export() {
return icon( "media/icon_export.png" );
}
public Icon settings() {
return icon( "media/icon_settings.png" );
}
public Icon edit() {
return icon( "media/icon_edit.png" );
}
public Icon key() {
return icon( "media/icon_key.png" );
}
public Icon avatar(final int index) {
return icon( strf( "media/avatar-%d.png", index % avatars() ) );
}
public int avatars() {
return AVATAR_COUNT;
}
private static Icon icon(@NonNls final String resourceName) {
return new ImageIcon( Toolkit.getDefaultToolkit().getImage( Res.class.getClassLoader().getResource( resourceName ) ) );
}
}
public static final class Fonts {
public Font emoticonsFont(final int size) {
return MPFont.emoticonsRegular.get( size );
}
public Font controlFont(final int size) {
return MPFont.exoRegular.get( size );
}
public Font valueFont(final int size) {
return MPFont.sourceSansProRegular.get( size );
}
public Font bigValueFont(final int size) {
return MPFont.sourceSansProBlack.get( size );
}
private enum MPFont {
emoticonsRegular( "Emoticons", "fonts/Emoticons-Regular.otf" ),
sourceCodeProRegular( "Source Code Pro", "fonts/SourceCodePro-Regular.otf" ),
sourceCodeProBlack( "Source Code Pro Bold", "fonts/SourceCodePro-Bold.otf" ),
sourceSansProRegular( "Source Sans Pro", "fonts/SourceSansPro-Regular.otf" ),
sourceSansProBlack( "Source Sans Pro Bold", "fonts/SourceSansPro-Bold.otf" ),
exoBold( "Exo 2.0 Bold", "fonts/Exo2.0-Bold.otf" ),
exoExtraBold( "Exo 2.0 Extra Bold", "fonts/Exo2.0-ExtraBold.otf" ),
exoRegular( "Exo 2.0", "fonts/Exo2.0-Regular.otf" ),
exoThin( "Exo 2.0 Thin", "fonts/Exo2.0-Thin.otf" ),
arimoBold( "Arimo Bold", "fonts/Arimo-Bold.ttf" ),
arimoBoldItalic( "Arimo Bold Italic", "fonts/Arimo-BoldItalic.ttf" ),
arimoItalic( "Arimo Italic", "fonts/Arimo-Italic.ttf" ),
arimoRegular( "Arimo", "fonts/Arimo-Regular.ttf" );
private final String fontName;
private final String resourceName;
private boolean registered;
MPFont(final String fontName, final String resourceName) {
this.fontName = fontName;
this.resourceName = resourceName;
}
Font get(final int size) {
return get( Font.PLAIN, size );
}
Font get(final int style, final int size) {
if (!registered)
register();
return new Font( fontName, style, size );
}
private void register() {
try {
Font font = Font.createFont( Font.TRUETYPE_FONT, Resources.getResource( resourceName ).openStream() );
GraphicsEnvironment.getLocalGraphicsEnvironment().registerFont( font );
registered = true;
}
catch (final FontFormatException | IOException e) {
throw logger.bug( e );
}
}
}
}
public static final class Colors {
private final Color transparent = new Color( 0, 0, 0, 0 );
private final Color frameBg = Color.decode( "#5A5D6B" );
private final Color controlBg = SystemColor.window;
private final Color controlBorder = Color.decode( "#BFBFBF" );
private final Color highlightFg = SystemColor.controlHighlight;
private final Color errorFg = Color.decode( "#FF3333" );
public Color transparent() {
return transparent;
}
public Color frameBg() {
return frameBg;
}
public Color controlBg() {
return controlBg;
}
public Color controlBorder() {
return controlBorder;
}
public Color highlightFg() {
return highlightFg;
}
public Color errorFg() {
return errorFg;
}
public Color fromIdenticonColor(final MPIdenticon.Color identiconColor, final BackgroundMode backgroundMode) {
switch (identiconColor) {
case RED:
switch (backgroundMode) {
case DARK:
return Color.decode( "#dc322f" );
case LIGHT:
return Color.decode( "#dc322f" );
}
break;
case GREEN:
switch (backgroundMode) {
case DARK:
return Color.decode( "#859900" );
case LIGHT:
return Color.decode( "#859900" );
}
break;
case YELLOW:
switch (backgroundMode) {
case DARK:
return Color.decode( "#b58900" );
case LIGHT:
return Color.decode( "#b58900" );
}
break;
case BLUE:
switch (backgroundMode) {
case DARK:
return Color.decode( "#268bd2" );
case LIGHT:
return Color.decode( "#268bd2" );
}
break;
case MAGENTA:
switch (backgroundMode) {
case DARK:
return Color.decode( "#d33682" );
case LIGHT:
return Color.decode( "#d33682" );
}
break;
case CYAN:
switch (backgroundMode) {
case DARK:
return Color.decode( "#2aa198" );
case LIGHT:
return Color.decode( "#2aa198" );
}
break;
case MONO:
switch (backgroundMode) {
case DARK:
return Color.decode( "#93a1a1" );
case LIGHT:
return Color.decode( "#586e75" );
}
break;
}
throw new IllegalArgumentException( strf( "Color: %s or mode: %s not supported: ", identiconColor, backgroundMode ) );
}
public enum BackgroundMode {
DARK, LIGHT
}
}
}

View File

@@ -0,0 +1,15 @@
package com.lyndir.masterpassword.gui.util;
import java.util.function.Consumer;
import javax.annotation.Nullable;
/**
* @author lhunath, 2018-07-26
*/
public interface Selectable<E, T> {
T selection(@Nullable Consumer<E> selectionConsumer);
T selection(@Nullable E selectedItem, @Nullable Consumer<E> selectionConsumer);
}

View File

@@ -1,4 +1,4 @@
package com.lyndir.masterpassword.gui; package com.lyndir.masterpassword.gui.util;
import static com.lyndir.lhunath.opal.system.util.ObjectUtils.*; import static com.lyndir.lhunath.opal.system.util.ObjectUtils.*;
@@ -29,11 +29,11 @@ public class SwingExecutorService extends AbstractExecutorService {
@Override @Override
public void shutdown() { public void shutdown() {
synchronized (pendingCommands) {
shutdown = true; shutdown = true;
synchronized (pendingCommands) {
if (pendingCommands.isEmpty()) if (pendingCommands.isEmpty())
terminated.offer( true ); terminated.add( true );
} }
} }
@@ -49,8 +49,10 @@ public class SwingExecutorService extends AbstractExecutorService {
@Override @Override
public boolean isShutdown() { public boolean isShutdown() {
synchronized (pendingCommands) {
return shutdown; return shutdown;
} }
}
@Override @Override
public boolean isTerminated() { public boolean isTerminated() {
@@ -65,10 +67,10 @@ public class SwingExecutorService extends AbstractExecutorService {
@Override @Override
public void execute(@NotNull final Runnable command) { public void execute(@NotNull final Runnable command) {
synchronized (pendingCommands) {
if (shutdown) if (shutdown)
throw new RejectedExecutionException( "Executor is shut down." ); throw new RejectedExecutionException( "Executor is shut down." );
synchronized (pendingCommands) {
pendingCommands.add( command ); pendingCommands.add( command );
} }
@@ -85,7 +87,7 @@ public class SwingExecutorService extends AbstractExecutorService {
pendingCommands.remove( command ); pendingCommands.remove( command );
if (shutdown && pendingCommands.isEmpty()) if (shutdown && pendingCommands.isEmpty())
terminated.offer( true ); terminated.add( true );
} }
} }
} }

View File

@@ -19,15 +19,21 @@
package com.lyndir.masterpassword.gui.util; package com.lyndir.masterpassword.gui.util;
import com.google.common.primitives.UnsignedInteger; import com.google.common.primitives.UnsignedInteger;
import java.text.ParseException;
import java.util.function.Consumer;
import javax.annotation.Nullable;
import javax.swing.*; import javax.swing.*;
import javax.swing.event.ChangeListener;
/** /**
* @author lhunath, 2016-10-29 * @author lhunath, 2016-10-29
*/ */
public class UnsignedIntegerModel extends SpinnerNumberModel { @SuppressWarnings("serial")
public class UnsignedIntegerModel extends SpinnerNumberModel implements Selectable<UnsignedInteger, UnsignedIntegerModel> {
private static final long serialVersionUID = 1L; @Nullable
private ChangeListener changeListener;
public UnsignedIntegerModel() { public UnsignedIntegerModel() {
this( UnsignedInteger.ZERO, UnsignedInteger.ZERO, UnsignedInteger.MAX_VALUE, UnsignedInteger.ONE ); this( UnsignedInteger.ZERO, UnsignedInteger.ZERO, UnsignedInteger.MAX_VALUE, UnsignedInteger.ONE );
@@ -55,4 +61,80 @@ public class UnsignedIntegerModel extends SpinnerNumberModel {
public UnsignedInteger getNumber() { public UnsignedInteger getNumber() {
return (UnsignedInteger) super.getNumber(); return (UnsignedInteger) super.getNumber();
} }
@Override
public UnsignedInteger getMinimum() {
return (UnsignedInteger) super.getMinimum();
}
@Override
public UnsignedInteger getMaximum() {
return (UnsignedInteger) super.getMaximum();
}
@Override
public UnsignedInteger getStepSize() {
return (UnsignedInteger) super.getStepSize();
}
@Override
public UnsignedInteger getNextValue() {
if ((getMaximum() == null) || (getMaximum().compareTo( getNumber() ) > 0))
return getNumber().plus( getStepSize() );
return getMaximum();
}
@Override
public UnsignedInteger getPreviousValue() {
if ((getMinimum() == null) || (getMinimum().compareTo( getNumber() ) < 0))
return getNumber().minus( getStepSize() );
return getMinimum();
}
@Override
public UnsignedIntegerModel selection(@Nullable final Consumer<UnsignedInteger> selectionConsumer) {
if (changeListener != null) {
removeChangeListener( changeListener );
changeListener = null;
}
if (selectionConsumer != null) {
addChangeListener( changeListener = e -> selectionConsumer.accept( getNumber() ) );
selectionConsumer.accept( getNumber() );
}
return this;
}
@Override
public UnsignedIntegerModel selection(@Nullable final UnsignedInteger selectedItem,
@Nullable final Consumer<UnsignedInteger> selectionConsumer) {
if (changeListener != null) {
removeChangeListener( changeListener );
changeListener = null;
}
setValue( (selectedItem != null)? selectedItem: getMinimum() );
return selection( selectionConsumer );
}
public JFormattedTextField.AbstractFormatter getFormatter() {
return new JFormattedTextField.AbstractFormatter() {
@Override
@Nullable
public Object stringToValue(@Nullable final String text)
throws ParseException {
return (text != null)? UnsignedInteger.valueOf( text ): null;
}
@Override
@Nullable
public String valueToString(final Object value)
throws ParseException {
return (value != null)? value.toString(): null;
}
};
}
} }

View File

@@ -0,0 +1,69 @@
package com.lyndir.masterpassword.gui.util.platform;
import com.apple.eawt.*;
import com.apple.eio.FileManager;
import com.google.common.base.Preconditions;
import com.lyndir.lhunath.opal.system.logging.Logger;
import java.io.*;
import java.net.URI;
/**
* @author lhunath, 2018-07-29
*/
public class ApplePlatform implements IPlatform {
private static final Logger logger = Logger.get( ApplePlatform.class );
private static final Application application = Preconditions.checkNotNull(
Application.getApplication(), "Not an Apple Java application." );
@Override
public boolean installAppForegroundHandler(final Runnable handler) {
application.addAppEventListener( new AppForegroundListener() {
@Override
public void appMovedToBackground(final AppEvent.AppForegroundEvent e) {
}
@Override
public void appRaisedToForeground(final AppEvent.AppForegroundEvent e) {
handler.run();
}
} );
return true;
}
@Override
public boolean installAppReopenHandler(final Runnable handler) {
application.addAppEventListener( (AppReOpenedListener) e -> handler.run() );
return true;
}
@Override
public boolean requestForeground() {
application.requestForeground( true );
return true;
}
@Override
public boolean show(final File file) {
try {
return FileManager.revealInFinder( file );
}
catch (final FileNotFoundException e) {
logger.err( e, "While showing: %s", file );
return false;
}
}
@Override
public boolean open(final URI url) {
try {
FileManager.openURL( url.toString() );
return true;
}
catch (final IOException e) {
logger.err( e, "While opening: %s", url );
return false;
}
}
}

View File

@@ -0,0 +1,36 @@
package com.lyndir.masterpassword.gui.util.platform;
import java.io.File;
import java.net.URI;
/**
* @author lhunath, 2018-07-29
*/
public class BasePlatform implements IPlatform {
@Override
public boolean installAppForegroundHandler(final Runnable handler) {
return false;
}
@Override
public boolean installAppReopenHandler(final Runnable handler) {
return false;
}
@Override
public boolean requestForeground() {
return false;
}
@Override
public boolean show(final File file) {
return false;
}
@Override
public boolean open(final URI url) {
return false;
}
}

View File

@@ -0,0 +1,22 @@
package com.lyndir.masterpassword.gui.util.platform;
import java.io.File;
import java.net.URI;
import java.net.URL;
/**
* @author lhunath, 2018-07-29
*/
public interface IPlatform {
boolean installAppForegroundHandler(Runnable handler);
boolean installAppReopenHandler(Runnable handler);
boolean requestForeground();
boolean show(File file);
boolean open(URI url);
}

View File

@@ -0,0 +1,67 @@
package com.lyndir.masterpassword.gui.util.platform;
import com.lyndir.lhunath.opal.system.logging.Logger;
import java.awt.*;
import java.awt.desktop.*;
import java.io.File;
import java.io.IOException;
import java.net.URI;
/**
* @author lhunath, 2018-07-29
*/
@SuppressWarnings("Since15")
public class JDK9Platform implements IPlatform {
private static final Logger logger = Logger.get( JDK9Platform.class );
private static final Desktop desktop = Desktop.getDesktop();
@Override
public boolean installAppForegroundHandler(final Runnable handler) {
desktop.addAppEventListener( new AppForegroundListener() {
@Override
public void appRaisedToForeground(final AppForegroundEvent e) {
handler.run();
}
@Override
public void appMovedToBackground(final AppForegroundEvent e) {
}
} );
return true;
}
@Override
public boolean installAppReopenHandler(final Runnable handler) {
desktop.addAppEventListener( (AppReopenedListener) e -> handler.run() );
return true;
}
@Override
public boolean requestForeground() {
desktop.requestForeground( true );
return true;
}
@Override
public boolean show(final File file) {
if (!file.exists())
return false;
desktop.browseFileDirectory( file );
return true;
}
@Override
public boolean open(final URI url) {
try {
desktop.browse( url );
return true;
}
catch (final IOException e) {
logger.err( e, "While opening: %s", url );
return false;
}
}
}

View File

@@ -20,6 +20,6 @@
* @author lhunath, 2018-04-26 * @author lhunath, 2018-04-26
*/ */
@ParametersAreNonnullByDefault @ParametersAreNonnullByDefault
package com.lyndir.masterpassword.gui.platform.mac; package com.lyndir.masterpassword.gui.util.platform;
import javax.annotation.ParametersAreNonnullByDefault; import javax.annotation.ParametersAreNonnullByDefault;

View File

@@ -1,84 +0,0 @@
//==============================================================================
// This file is part of Master Password.
// Copyright (c) 2011-2017, Maarten Billemont.
//
// Master Password is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Master Password is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You can find a copy of the GNU General Public License in the
// LICENSE file. Alternatively, see <http://www.gnu.org/licenses/>.
//==============================================================================
package com.lyndir.masterpassword.gui.view;
import com.google.common.collect.ImmutableList;
import com.lyndir.masterpassword.gui.Res;
import com.lyndir.masterpassword.gui.util.Components;
import com.lyndir.masterpassword.model.MPUser;
import java.awt.*;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.swing.*;
/**
* @author lhunath, 2014-06-11
*/
public abstract class AuthenticationPanel<U extends MPUser<?>> extends Components.GradientPanel {
protected final UnlockFrame unlockFrame;
protected final JLabel avatarLabel;
protected AuthenticationPanel(final UnlockFrame unlockFrame) {
super( null, null );
this.unlockFrame = unlockFrame;
setLayout( new BoxLayout( this, BoxLayout.PAGE_AXIS ) );
// Avatar
add( Box.createVerticalGlue() );
add( avatarLabel = new JLabel( Res.avatar( 0 ) ) {
@Override
public Dimension getMaximumSize() {
return new Dimension( Integer.MAX_VALUE, Integer.MAX_VALUE );
}
} );
add( Box.createVerticalGlue() );
avatarLabel.setToolTipText( "The avatar for your user. Click to change it." );
}
protected void updateUser(final boolean repack) {
unlockFrame.updateUser( getSelectedUser() );
validate();
if (repack)
unlockFrame.repack();
}
@Nullable
protected abstract U getSelectedUser();
@Nonnull
public abstract char[] getMasterPassword();
@Nullable
public Component getFocusComponent() {
return null;
}
public Iterable<? extends JButton> getButtons() {
return ImmutableList.of();
}
public abstract void reset();
public abstract PasswordFrame<?, ?> newPasswordFrame();
}

View File

@@ -0,0 +1,70 @@
package com.lyndir.masterpassword.gui.view;
import static com.lyndir.masterpassword.util.Utilities.*;
import com.google.common.collect.ImmutableSortedSet;
import com.lyndir.masterpassword.gui.MasterPassword;
import com.lyndir.masterpassword.gui.util.*;
import com.lyndir.masterpassword.model.MPUser;
import com.lyndir.masterpassword.model.impl.MPFileUser;
import com.lyndir.masterpassword.model.impl.MPFileUserManager;
import java.awt.*;
import javax.annotation.Nullable;
import javax.swing.*;
/**
* @author lhunath, 2018-07-14
*/
@SuppressWarnings("serial")
public class FilesPanel extends JPanel implements MPFileUserManager.Listener, MasterPassword.Listener {
private final JButton avatarButton = Components.button( Res.icons().avatar( 0 ), event -> setAvatar(),
"Click to change the user's avatar." );
private final CollectionListModel<MPUser<?>> usersModel =
new CollectionListModel<MPUser<?>>( MPFileUserManager.get().getFiles() ).selection( MasterPassword.get()::activateUser );
protected FilesPanel() {
setOpaque( false );
setBackground( Res.colors().transparent() );
setLayout( new BoxLayout( this, BoxLayout.PAGE_AXIS ) );
// -
add( Box.createVerticalGlue() );
// Avatar
add( avatarButton );
avatarButton.setHorizontalAlignment( SwingConstants.CENTER );
avatarButton.setMaximumSize( new Dimension( Integer.MAX_VALUE, 0 ) );
// -
add( Components.strut( Components.margin() ) );
// User Selection
add( Components.comboBox( usersModel, user -> ifNotNull( user, MPUser::getFullName ) ) );
MPFileUserManager.get().addListener( this );
MasterPassword.get().addListener( this );
}
private void setAvatar() {
MPUser<?> selectedUser = usersModel.getSelectedItem();
if (selectedUser == null)
return;
selectedUser.setAvatar( (selectedUser.getAvatar() + 1) % Res.icons().avatars() );
avatarButton.setIcon( Res.icons().avatar( selectedUser.getAvatar() ) );
}
@Override
public void onFilesUpdated(final ImmutableSortedSet<MPFileUser> files) {
usersModel.set( files );
}
@Override
public void onUserSelected(@Nullable final MPUser<?> user) {
usersModel.selectItem( user );
avatarButton.setIcon( Res.icons().avatar( (user == null)? 0: user.getAvatar() ) );
}
}

View File

@@ -1,127 +0,0 @@
//==============================================================================
// This file is part of Master Password.
// Copyright (c) 2011-2017, Maarten Billemont.
//
// Master Password is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Master Password is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You can find a copy of the GNU General Public License in the
// LICENSE file. Alternatively, see <http://www.gnu.org/licenses/>.
//==============================================================================
package com.lyndir.masterpassword.gui.view;
import com.google.common.base.Preconditions;
import com.google.common.primitives.UnsignedInteger;
import com.lyndir.masterpassword.MPAlgorithm;
import com.lyndir.masterpassword.MPResultType;
import com.lyndir.masterpassword.gui.Res;
import com.lyndir.masterpassword.gui.model.MPIncognitoSite;
import com.lyndir.masterpassword.gui.model.MPIncognitoUser;
import com.lyndir.masterpassword.gui.util.Components;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.swing.*;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
/**
* @author lhunath, 2014-06-11
*/
@SuppressWarnings({ "serial", "MagicNumber" })
public class IncognitoAuthenticationPanel extends AuthenticationPanel<MPIncognitoUser> implements DocumentListener, ActionListener {
private final JTextField fullNameField;
private final JPasswordField masterPasswordField;
public IncognitoAuthenticationPanel(final UnlockFrame unlockFrame) {
// Full Name
super( unlockFrame );
add( Components.stud() );
JLabel fullNameLabel = Components.label( "Full Name:" );
add( fullNameLabel );
fullNameField = Components.textField();
fullNameField.setFont( Res.valueFont().deriveFont( 12f ) );
fullNameField.getDocument().addDocumentListener( this );
fullNameField.addActionListener( this );
add( fullNameField );
add( Components.stud() );
// Master Password
JLabel masterPasswordLabel = Components.label( "Master Password:" );
add( masterPasswordLabel );
masterPasswordField = Components.passwordField();
masterPasswordField.addActionListener( this );
masterPasswordField.getDocument().addDocumentListener( this );
add( masterPasswordField );
}
@Override
public Component getFocusComponent() {
return fullNameField;
}
@Override
public void reset() {
masterPasswordField.setText( "" );
}
@Override
public PasswordFrame<MPIncognitoUser, ?> newPasswordFrame() {
return new PasswordFrame<MPIncognitoUser, MPIncognitoSite>( Preconditions.checkNotNull( getSelectedUser() ) ) {
@Override
protected MPIncognitoSite createSite(final MPIncognitoUser user, final String siteName, final UnsignedInteger siteCounter,
final MPResultType resultType, final MPAlgorithm algorithm) {
return new MPIncognitoSite( user, siteName, algorithm, siteCounter, resultType, null );
}
};
}
@Nullable
@Override
protected MPIncognitoUser getSelectedUser() {
return new MPIncognitoUser( fullNameField.getText() );
}
@Nonnull
@Override
public char[] getMasterPassword() {
return masterPasswordField.getPassword();
}
@Override
public void insertUpdate(final DocumentEvent e) {
updateUser( false );
}
@Override
public void removeUpdate(final DocumentEvent e) {
updateUser( false );
}
@Override
public void changedUpdate(final DocumentEvent e) {
updateUser( false );
}
@Override
public void actionPerformed(final ActionEvent e) {
updateUser( false );
unlockFrame.trySignIn( fullNameField, masterPasswordField );
}
}

View File

@@ -0,0 +1,58 @@
package com.lyndir.masterpassword.gui.view;
import com.lyndir.lhunath.opal.system.logging.Logger;
import com.lyndir.masterpassword.gui.util.Components;
import com.lyndir.masterpassword.gui.util.Res;
import com.lyndir.masterpassword.model.impl.MPFileUserManager;
import java.awt.*;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import javax.swing.*;
import javax.swing.border.BevelBorder;
/**
* @author lhunath, 2018-07-14
*/
@SuppressWarnings("serial")
public class MasterPasswordFrame extends JFrame {
private static final Logger logger = Logger.get( MasterPasswordFrame.class );
private final UserContentPanel userContent;
@SuppressWarnings("MagicNumber")
public MasterPasswordFrame() {
super( "Master Password" );
JPanel root, userPanel;
setContentPane( root = Components.borderPanel( Res.colors().frameBg(), BoxLayout.PAGE_AXIS ) );
root.add( new FilesPanel() );
root.add( Components.strut() );
root.add( userPanel = Components.panel( new BorderLayout( 0, 0 ) ) );
userPanel.add( Components.borderPanel(
BorderFactory.createBevelBorder( BevelBorder.RAISED, Res.colors().controlBorder(), Res.colors().frameBg() ),
Res.colors().controlBg(), BoxLayout.PAGE_AXIS, userContent = new UserContentPanel() ), BorderLayout.CENTER );
userPanel.add( userContent.getUserToolbar(), BorderLayout.LINE_START );
userPanel.add( userContent.getSiteToolbar(), BorderLayout.LINE_END );
addComponentListener( new ComponentHandler() );
setPreferredSize( new Dimension( 800, 560 ) );
setDefaultCloseOperation( DISPOSE_ON_CLOSE );
pack();
setLocationRelativeTo( null );
setLocationByPlatform( true );
}
private class ComponentHandler extends ComponentAdapter {
@Override
public void componentShown(final ComponentEvent e) {
MPFileUserManager.get().reload();
userContent.transferFocus();
}
}
}

View File

@@ -1,248 +0,0 @@
//==============================================================================
// This file is part of Master Password.
// Copyright (c) 2011-2017, Maarten Billemont.
//
// Master Password is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Master Password is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You can find a copy of the GNU General Public License in the
// LICENSE file. Alternatively, see <http://www.gnu.org/licenses/>.
//==============================================================================
package com.lyndir.masterpassword.gui.view;
import static com.lyndir.lhunath.opal.system.util.StringUtils.*;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import com.google.common.primitives.UnsignedInteger;
import com.lyndir.lhunath.opal.system.logging.Logger;
import com.lyndir.masterpassword.MPAlgorithm;
import com.lyndir.masterpassword.MPResultType;
import com.lyndir.masterpassword.gui.Res;
import com.lyndir.masterpassword.gui.util.Components;
import com.lyndir.masterpassword.model.MPUser;
import com.lyndir.masterpassword.model.impl.*;
import java.awt.*;
import java.awt.event.*;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.swing.*;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.plaf.metal.MetalComboBoxEditor;
/**
* @author lhunath, 2014-06-11
*/
public class ModelAuthenticationPanel extends AuthenticationPanel<MPFileUser> implements ItemListener, ActionListener, DocumentListener {
@SuppressWarnings("UnusedDeclaration")
private static final Logger logger = Logger.get( ModelAuthenticationPanel.class );
private static final long serialVersionUID = 1L;
private final JComboBox<MPFileUser> userField;
private final JLabel masterPasswordLabel;
private final JPasswordField masterPasswordField;
public ModelAuthenticationPanel(final UnlockFrame unlockFrame) {
super( unlockFrame );
add( Components.stud() );
// Avatar
avatarLabel.addMouseListener( new MouseAdapter() {
@Override
public void mouseClicked(final MouseEvent e) {
MPFileUser selectedUser = getSelectedUser();
if (selectedUser != null) {
selectedUser.setAvatar( selectedUser.getAvatar() + 1 );
updateUser( false );
}
}
} );
// User
JLabel userLabel = Components.label( "User:" );
add( userLabel );
userField = Components.comboBox( readConfigUsers() );
userField.setFont( Res.valueFont().deriveFont( userField.getFont().getSize2D() ) );
userField.addItemListener( this );
userField.addActionListener( this );
userField.setRenderer( new DefaultListCellRenderer() {
private static final long serialVersionUID = 1L;
@Override
@SuppressWarnings("unchecked")
public Component getListCellRendererComponent(final JList<?> list, final Object value, final int index,
final boolean isSelected, final boolean cellHasFocus) {
String userValue = ((MPUser<MPFileSite>) value).getFullName();
return super.getListCellRendererComponent( list, userValue, index, isSelected, cellHasFocus );
}
} );
userField.setEditor( new MetalComboBoxEditor() {
@Override
protected JTextField createEditorComponent() {
JTextField editorComponents = Components.textField();
editorComponents.setForeground( Color.red );
return editorComponents;
}
} );
add( userField );
add( Components.stud() );
// Master Password
masterPasswordLabel = Components.label( "Master Password:" );
add( masterPasswordLabel );
masterPasswordField = Components.passwordField();
masterPasswordField.addActionListener( this );
masterPasswordField.getDocument().addDocumentListener( this );
add( masterPasswordField );
}
@Override
public Component getFocusComponent() {
return masterPasswordField.isVisible()? masterPasswordField: null;
}
@Override
protected void updateUser(boolean repack) {
MPFileUser selectedUser = getSelectedUser();
if (selectedUser != null) {
avatarLabel.setIcon( Res.avatar( selectedUser.getAvatar() ) );
boolean showPasswordField = !selectedUser.isMasterKeyAvailable(); // TODO: is this the same as keySaved()?
if (masterPasswordField.isVisible() != showPasswordField) {
masterPasswordLabel.setVisible( showPasswordField );
masterPasswordField.setVisible( showPasswordField );
repack = true;
}
}
super.updateUser( repack );
}
@Nullable
@Override
protected MPFileUser getSelectedUser() {
int selectedIndex = userField.getSelectedIndex();
if (selectedIndex < 0)
return null;
return userField.getModel().getElementAt( selectedIndex );
}
@Nonnull
@Override
public char[] getMasterPassword() {
return masterPasswordField.getPassword();
}
@Override
public Iterable<? extends JButton> getButtons() {
return ImmutableList.of( new JButton( Res.iconAdd() ) {
{
addActionListener( new ActionListener() {
@Override
public void actionPerformed(final ActionEvent e) {
String fullName = JOptionPane.showInputDialog( ModelAuthenticationPanel.this, //
"Enter your full name, ensuring it is correctly spelled and capitalized:",
"New User", JOptionPane.QUESTION_MESSAGE );
MPFileUserManager.get().addUser( new MPFileUser( fullName ) );
userField.setModel( new DefaultComboBoxModel<>( readConfigUsers() ) );
updateUser( true );
}
} );
setToolTipText( "Add a new user to the list." );
}
}, new JButton( Res.iconDelete() ) {
{
addActionListener( new ActionListener() {
@Override
public void actionPerformed(final ActionEvent e) {
MPFileUser deleteUser = getSelectedUser();
if (deleteUser == null)
return;
if (JOptionPane.showConfirmDialog( ModelAuthenticationPanel.this, //
strf( "Are you sure you want to delete the user and sites remembered for:%n%s.",
deleteUser.getFullName() ), //
"Delete User", JOptionPane.OK_CANCEL_OPTION, JOptionPane.QUESTION_MESSAGE )
== JOptionPane.CANCEL_OPTION)
return;
MPFileUserManager.get().deleteUser( deleteUser );
userField.setModel( new DefaultComboBoxModel<>( readConfigUsers() ) );
updateUser( true );
}
} );
setToolTipText( "Delete the selected user." );
}
}, new JButton( Res.iconQuestion() ) {
{
addActionListener( e -> JOptionPane.showMessageDialog(
ModelAuthenticationPanel.this, //
strf( "Reads users and sites from the directory at:%n%s",
MPFileUserManager.get().getPath().getAbsolutePath() ), //
"Help", JOptionPane.INFORMATION_MESSAGE ) );
setToolTipText( "More information." );
}
} );
}
@Override
public void reset() {
masterPasswordField.setText( "" );
}
@Override
public PasswordFrame<MPFileUser, MPFileSite> newPasswordFrame() {
return new PasswordFrame<MPFileUser, MPFileSite>( Preconditions.checkNotNull( getSelectedUser() ) ) {
@Override
protected MPFileSite createSite(final MPFileUser user, final String siteName, final UnsignedInteger siteCounter,
final MPResultType resultType,
final MPAlgorithm algorithm) {
return new MPFileSite( user, siteName, algorithm, siteCounter, resultType );
}
};
}
private static MPFileUser[] readConfigUsers() {
return MPFileUserManager.get().getUsers().toArray( new MPFileUser[0] );
}
@Override
public void itemStateChanged(final ItemEvent e) {
updateUser( false );
}
@Override
public void actionPerformed(final ActionEvent e) {
updateUser( false );
unlockFrame.trySignIn( userField );
}
@Override
public void insertUpdate(final DocumentEvent e) {
updateUser( false );
}
@Override
public void removeUpdate(final DocumentEvent e) {
updateUser( false );
}
@Override
public void changedUpdate(final DocumentEvent e) {
updateUser( false );
}
}

View File

@@ -1,279 +0,0 @@
//==============================================================================
// This file is part of Master Password.
// Copyright (c) 2011-2017, Maarten Billemont.
//
// Master Password is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Master Password is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You can find a copy of the GNU General Public License in the
// LICENSE file. Alternatively, see <http://www.gnu.org/licenses/>.
//==============================================================================
package com.lyndir.masterpassword.gui.view;
import static com.lyndir.lhunath.opal.system.util.ObjectUtils.*;
import static com.lyndir.lhunath.opal.system.util.StringUtils.*;
import com.google.common.collect.Iterables;
import com.google.common.primitives.UnsignedInteger;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.lyndir.lhunath.opal.system.logging.Logger;
import com.lyndir.masterpassword.*;
import com.lyndir.masterpassword.gui.Res;
import com.lyndir.masterpassword.gui.util.*;
import com.lyndir.masterpassword.model.MPSite;
import com.lyndir.masterpassword.model.MPUser;
import com.lyndir.masterpassword.model.impl.MPFileSite;
import com.lyndir.masterpassword.model.impl.MPFileUser;
import java.awt.*;
import java.awt.datatransfer.StringSelection;
import java.awt.datatransfer.Transferable;
import java.awt.event.WindowEvent;
import java.util.Collection;
import java.util.stream.Collectors;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.swing.*;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
/**
* @author lhunath, 2014-06-08
*/
public abstract class PasswordFrame<U extends MPUser<S>, S extends MPSite<?>> extends JFrame implements DocumentListener {
private static final Logger logger = Logger.get( PasswordFrame.class );
@SuppressWarnings("FieldCanBeLocal")
private final Components.GradientPanel root;
private final JTextField siteNameField;
private final JButton siteActionButton;
private final JComboBox<MPAlgorithm.Version> siteVersionField;
private final JSpinner siteCounterField;
private final UnsignedIntegerModel siteCounterModel;
private final JComboBox<MPResultType> resultTypeField;
private final JPasswordField passwordField;
private final JLabel tipLabel;
private final JCheckBox maskPasswordField;
private final char passwordEchoChar;
private final Font passwordEchoFont;
private final U user;
@Nullable
private S currentSite;
private boolean updatingUI;
@SuppressWarnings("MagicNumber")
protected PasswordFrame(final U user) {
super( "Master Password" );
this.user = user;
setDefaultCloseOperation( DISPOSE_ON_CLOSE );
setContentPane( root = Components.gradientPanel( new FlowLayout(), Res.colors().frameBg() ) );
root.setLayout( new BoxLayout( root, BoxLayout.PAGE_AXIS ) );
root.setBorder( BorderFactory.createEmptyBorder( 20, 20, 20, 20 ) );
// Site
JPanel sitePanel = Components.boxLayout( BoxLayout.PAGE_AXIS );
sitePanel.setOpaque( true );
sitePanel.setBackground( Res.colors().controlBg() );
sitePanel.setBorder( BorderFactory.createEmptyBorder( 20, 20, 20, 20 ) );
root.add( Components.borderPanel( sitePanel, BorderFactory.createRaisedBevelBorder(), Res.colors().frameBg() ) );
// User
sitePanel.add( Components.label( strf( "Generating passwords for: %s", user.getFullName() ), SwingConstants.CENTER ) );
sitePanel.add( Components.stud() );
// Site Name
sitePanel.add( Components.label( "Site Name:" ) );
JComponent siteControls = Components.boxLayout( BoxLayout.LINE_AXIS, //
siteNameField = Components.textField(), Components.stud(),
siteActionButton = Components.button( "Add Site" ) );
siteNameField.getDocument().addDocumentListener( this );
siteNameField.addActionListener(
e -> Futures.addCallback( updatePassword( true ), new FailableCallback<String>( logger ) {
@Override
public void onSuccess(@Nullable final String sitePassword) {
if (sitePassword == null)
return;
if (currentSite instanceof MPFileSite)
((MPFileSite) currentSite).use();
Transferable clipboardContents = new StringSelection( sitePassword );
Toolkit.getDefaultToolkit().getSystemClipboard().setContents( clipboardContents, null );
dispatchEvent( new WindowEvent( PasswordFrame.this, WindowEvent.WINDOW_CLOSING ) );
}
}, Res.uiExecutor( false ) ) );
siteActionButton.addActionListener(
e -> {
if (currentSite == null)
return;
if (currentSite instanceof MPFileSite)
this.user.deleteSite( currentSite );
else
this.user.addSite( currentSite );
siteNameField.requestFocus();
updatePassword( true );
} );
sitePanel.add( siteControls );
sitePanel.add( Components.stud() );
// Site Type & Counter
siteCounterModel = new UnsignedIntegerModel( UnsignedInteger.ONE, UnsignedInteger.ONE );
MPResultType[] types = Iterables.toArray( MPResultType.forClass( MPResultTypeClass.Template ), MPResultType.class );
JComponent siteSettings = Components.boxLayout( BoxLayout.LINE_AXIS, //
resultTypeField = Components.comboBox( types ), //
Components.stud(), //
siteVersionField = Components.comboBox( MPAlgorithm.Version.values() ), //
Components.stud(), //
siteCounterField = Components.spinner( siteCounterModel ) );
sitePanel.add( siteSettings );
resultTypeField.setFont( Res.valueFont().deriveFont( resultTypeField.getFont().getSize2D() ) );
resultTypeField.setSelectedItem( user.getAlgorithm().mpw_default_result_type() );
resultTypeField.addItemListener( e -> updatePassword( true ) );
siteVersionField.setFont( Res.valueFont().deriveFont( siteVersionField.getFont().getSize2D() ) );
siteVersionField.setAlignmentX( RIGHT_ALIGNMENT );
siteVersionField.setSelectedItem( user.getAlgorithm() );
siteVersionField.addItemListener( e -> updatePassword( true ) );
siteCounterField.setFont( Res.valueFont().deriveFont( siteCounterField.getFont().getSize2D() ) );
siteCounterField.setAlignmentX( RIGHT_ALIGNMENT );
siteCounterField.addChangeListener( e -> updatePassword( true ) );
// Mask
maskPasswordField = Components.checkBox( "Hide Password" );
maskPasswordField.setAlignmentX( Component.CENTER_ALIGNMENT );
maskPasswordField.setSelected( true );
maskPasswordField.addItemListener( e -> updateMask() );
// Password
passwordField = Components.passwordField();
passwordField.setAlignmentX( Component.CENTER_ALIGNMENT );
passwordField.setHorizontalAlignment( SwingConstants.CENTER );
passwordField.putClientProperty( "JPasswordField.cutCopyAllowed", true );
passwordField.setEditable( false );
passwordField.setBackground( null );
passwordField.setBorder( null );
passwordEchoChar = passwordField.getEchoChar();
passwordEchoFont = passwordField.getFont().deriveFont( 40f );
updateMask();
// Tip
tipLabel = Components.label( " ", SwingConstants.CENTER );
tipLabel.setAlignmentX( Component.CENTER_ALIGNMENT );
JPanel passwordContainer = Components.boxLayout( BoxLayout.PAGE_AXIS, maskPasswordField, Box.createGlue(), passwordField,
Box.createGlue(), tipLabel );
passwordContainer.setOpaque( true );
passwordContainer.setBackground( Color.white );
passwordContainer.setBorder( BorderFactory.createEmptyBorder( 8, 8, 8, 8 ) );
root.add( Box.createVerticalStrut( 8 ) );
root.add( Components.borderPanel( passwordContainer, BorderFactory.createLoweredSoftBevelBorder(), Res.colors().frameBg() ),
BorderLayout.SOUTH );
pack();
setMinimumSize( new Dimension( Math.max( 600, getPreferredSize().width ), Math.max( 300, getPreferredSize().height ) ) );
pack();
setLocationByPlatform( true );
setLocationRelativeTo( null );
}
@SuppressWarnings("MagicNumber")
private void updateMask() {
passwordField.setEchoChar( maskPasswordField.isSelected()? passwordEchoChar: (char) 0 );
passwordField.setFont( maskPasswordField.isSelected()? passwordEchoFont: Res.bigValueFont().deriveFont( 40f ) );
}
@Nonnull
private ListenableFuture<String> updatePassword(final boolean allowNameCompletion) {
String siteNameQuery = siteNameField.getText();
if (updatingUI)
return Futures.immediateCancelledFuture();
if ((siteNameQuery == null) || siteNameQuery.isEmpty() || !user.isMasterKeyAvailable()) {
siteActionButton.setVisible( false );
tipLabel.setText( null );
passwordField.setText( null );
return Futures.immediateCancelledFuture();
}
MPResultType resultType = resultTypeField.getModel().getElementAt( resultTypeField.getSelectedIndex() );
MPAlgorithm siteAlgorithm = siteVersionField.getItemAt( siteVersionField.getSelectedIndex() ).getAlgorithm();
UnsignedInteger siteCounter = siteCounterModel.getNumber();
Collection<S> siteResults = user.findSites( siteNameQuery );
if (!allowNameCompletion)
siteResults = siteResults.stream().filter(
siteResult -> (siteResult != null) && siteNameQuery.equals( siteResult.getName() ) ).collect( Collectors.toList() );
S site = ifNotNullElse( Iterables.getFirst( siteResults, null ),
createSite( user, siteNameQuery, siteCounter, resultType, siteAlgorithm ) );
if ((currentSite != null) && currentSite.getName().equals( site.getName() )) {
site.setResultType( resultType );
site.setAlgorithm( siteAlgorithm );
site.setCounter( siteCounter );
}
ListenableFuture<String> passwordFuture = Res.job( this, () ->
site.getResult( MPKeyPurpose.Authentication, null, null ) );
SwingUtilities.invokeLater( () -> {
updatingUI = true;
currentSite = site;
siteActionButton.setVisible( user instanceof MPFileUser );
if (currentSite instanceof MPFileSite)
siteActionButton.setText( "Delete Site" );
else
siteActionButton.setText( "Add Site" );
resultTypeField.setSelectedItem( currentSite.getResultType() );
siteVersionField.setSelectedItem( currentSite.getAlgorithm().version() );
siteCounterField.setValue( currentSite.getCounter() );
siteNameField.setText( currentSite.getName() );
if (siteNameField.getText().startsWith( siteNameQuery ))
siteNameField.select( siteNameQuery.length(), siteNameField.getText().length() );
passwordField.setText( null );
tipLabel.setText( "Getting password..." );
Futures.addCallback( passwordFuture, new FailableCallback<String>( logger ) {
@Override
public void onSuccess(@Nullable final String sitePassword) {
if (sitePassword != null)
tipLabel.setText( "Press [Enter] to copy the password. Then paste it into the password field." );
passwordField.setText( sitePassword );
updatingUI = false;
}
}, Res.uiExecutor( true ) );
} );
return passwordFuture;
}
protected abstract S createSite(U user, String siteName, UnsignedInteger siteCounter, MPResultType resultType, MPAlgorithm algorithm);
@Override
public void insertUpdate(final DocumentEvent e) {
updatePassword( true );
}
@Override
public void removeUpdate(final DocumentEvent e) {
updatePassword( false );
}
@Override
public void changedUpdate(final DocumentEvent e) {
updatePassword( true );
}
}

View File

@@ -1,216 +0,0 @@
//==============================================================================
// This file is part of Master Password.
// Copyright (c) 2011-2017, Maarten Billemont.
//
// Master Password is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Master Password is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You can find a copy of the GNU General Public License in the
// LICENSE file. Alternatively, see <http://www.gnu.org/licenses/>.
//==============================================================================
package com.lyndir.masterpassword.gui.view;
import static com.lyndir.lhunath.opal.system.util.ObjectUtils.*;
import static com.lyndir.lhunath.opal.system.util.StringUtils.*;
import com.lyndir.masterpassword.MPAlgorithmException;
import com.lyndir.masterpassword.MPIdenticon;
import com.lyndir.masterpassword.gui.Res;
import com.lyndir.masterpassword.gui.util.Components;
import com.lyndir.masterpassword.model.*;
import java.awt.*;
import java.awt.event.*;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import javax.annotation.Nullable;
import javax.swing.*;
/**
* @author lhunath, 2014-06-08
*/
@SuppressWarnings({ "MagicNumber", "serial" })
public class UnlockFrame extends JFrame {
private final SignInCallback signInCallback;
private final Components.GradientPanel root;
private final JLabel identiconLabel;
private final JButton signInButton;
private final JPanel authenticationContainer;
private AuthenticationPanel<?> authenticationPanel;
private Future<?> identiconFuture;
private boolean incognito;
@Nullable
private MPUser<? extends MPSite<?>> user;
public UnlockFrame(final SignInCallback signInCallback) {
super( "Unlock Master Password" );
this.signInCallback = signInCallback;
setDefaultCloseOperation( DISPOSE_ON_CLOSE );
addWindowFocusListener( new WindowAdapter() {
@Override
public void windowGainedFocus(final WindowEvent e) {
root.setGradientColor( Res.colors().frameBg() );
}
@Override
public void windowLostFocus(final WindowEvent e) {
root.setGradientColor( Color.RED );
}
} );
// Sign In
JPanel signInBox = Components.boxLayout( BoxLayout.LINE_AXIS, Box.createGlue(), signInButton = Components.button( "Sign In" ),
Box.createGlue() );
signInBox.setBackground( null );
setContentPane( root = Components.gradientPanel( new FlowLayout(), Res.colors().frameBg() ) );
root.setLayout( new BoxLayout( root, BoxLayout.PAGE_AXIS ) );
root.setBorder( BorderFactory.createEmptyBorder( 20, 20, 20, 20 ) );
root.add( Components.borderPanel( authenticationContainer = Components.boxLayout( BoxLayout.PAGE_AXIS ),
BorderFactory.createRaisedBevelBorder(), Res.colors().frameBg() ) );
root.add( Box.createVerticalStrut( 8 ) );
root.add( identiconLabel = Components.label( " ", SwingConstants.CENTER ) );
root.add( Box.createVerticalStrut( 8 ) );
root.add( signInBox );
authenticationContainer.setOpaque( true );
authenticationContainer.setBackground( Res.colors().controlBg() );
authenticationContainer.setBorder( BorderFactory.createEmptyBorder( 20, 20, 20, 20 ) );
identiconLabel.setFont( Res.emoticonsFont().deriveFont( 14.f ) );
identiconLabel.setToolTipText(
strf( "A representation of your identity across all Master Password apps.%nIt should always be the same." ) );
signInButton.addActionListener( new AbstractAction() {
@Override
public void actionPerformed(final ActionEvent e) {
trySignIn();
}
} );
createAuthenticationPanel();
setLocationByPlatform( true );
setLocationRelativeTo( null );
}
protected void repack() {
pack();
setMinimumSize( new Dimension( Math.max( 300, getPreferredSize().width ), Math.max( 300, getPreferredSize().height ) ) );
pack();
}
private void createAuthenticationPanel() {
authenticationContainer.removeAll();
if (incognito) {
authenticationPanel = new IncognitoAuthenticationPanel( this );
} else {
authenticationPanel = new ModelAuthenticationPanel( this );
}
authenticationPanel.updateUser( false );
authenticationContainer.add( authenticationPanel );
authenticationContainer.add( Components.stud() );
JCheckBox incognitoCheckBox = Components.checkBox( "Incognito" );
incognitoCheckBox.setToolTipText( "Log in without saving any information." );
incognitoCheckBox.setSelected( incognito );
incognitoCheckBox.addItemListener( e -> {
incognito = incognitoCheckBox.isSelected();
SwingUtilities.invokeLater( this::createAuthenticationPanel );
} );
JComponent toolsPanel = Components.boxLayout( BoxLayout.LINE_AXIS, incognitoCheckBox, Box.createGlue() );
authenticationContainer.add( toolsPanel );
for (final JButton button : authenticationPanel.getButtons()) {
toolsPanel.add( button );
button.setBorder( BorderFactory.createEmptyBorder() );
button.setMargin( new Insets( 0, 0, 0, 0 ) );
button.setAlignmentX( RIGHT_ALIGNMENT );
button.setContentAreaFilled( false );
}
checkSignIn();
validate();
repack();
SwingUtilities.invokeLater( () -> ifNotNullElse( authenticationPanel.getFocusComponent(), signInButton ).requestFocusInWindow() );
}
void updateUser(@Nullable final MPUser<? extends MPSite<?>> user) {
this.user = user;
checkSignIn();
}
boolean checkSignIn() {
if (identiconFuture != null)
identiconFuture.cancel( false );
identiconFuture = Res.job( this, () -> SwingUtilities.invokeLater( () -> {
String fullName = (user == null)? "": user.getFullName();
char[] masterPassword = authenticationPanel.getMasterPassword();
if (fullName.isEmpty() || (masterPassword.length == 0)) {
identiconLabel.setText( " " );
return;
}
MPIdenticon identicon = new MPIdenticon( fullName, masterPassword );
identiconLabel.setText( identicon.getText() );
identiconLabel.setForeground(
Res.colors().fromIdenticonColor( identicon.getColor(), Res.Colors.BackgroundMode.DARK ) );
} ), 300, TimeUnit.MILLISECONDS );
String fullName = (user == null)? "": user.getFullName();
char[] masterPassword = authenticationPanel.getMasterPassword();
boolean enabled = !fullName.isEmpty() && (masterPassword.length > 0);
signInButton.setEnabled( enabled );
return enabled;
}
void trySignIn(final JComponent... signInComponents) {
if ((user == null) || !checkSignIn())
return;
for (final JComponent signInComponent : signInComponents)
signInComponent.setEnabled( false );
signInButton.setEnabled( false );
signInButton.setText( "Signing In..." );
Res.job( this, () -> {
try {
user.authenticate( authenticationPanel.getMasterPassword() );
SwingUtilities.invokeLater( () -> {
signInCallback.signedIn( authenticationPanel.newPasswordFrame() );
dispose();
} );
}
catch (final MPIncorrectMasterPasswordException | MPAlgorithmException e) {
SwingUtilities.invokeLater( () -> {
JOptionPane.showMessageDialog( null, e.getLocalizedMessage(), "Sign In Failed", JOptionPane.ERROR_MESSAGE );
authenticationPanel.reset();
signInButton.setText( "Sign In" );
for (final JComponent signInComponent : signInComponents)
signInComponent.setEnabled( true );
checkSignIn();
} );
}
} );
}
@FunctionalInterface
public interface SignInCallback {
void signedIn(PasswordFrame<?, ?> passwordFrame);
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

View File

@@ -25,7 +25,7 @@ import org.joda.time.format.ISODateTimeFormat;
/** /**
* @author lhunath, 2016-10-29 * @author lhunath, 2016-10-29
*/ */
public final class MPConstants { public final class MPModelConstants {
/* Environment */ /* Environment */
@@ -33,6 +33,7 @@ public final class MPConstants {
* mpw: default path to look for run configuration files if the platform default is not desired. * mpw: default path to look for run configuration files if the platform default is not desired.
*/ */
public static final String env_rcDir = "MPW_RCDIR"; public static final String env_rcDir = "MPW_RCDIR";
/** /**
* mpw: permit automatic update checks. * mpw: permit automatic update checks.
*/ */

View File

@@ -0,0 +1,157 @@
package com.lyndir.masterpassword.model;
import static com.lyndir.lhunath.opal.system.util.StringUtils.strf;
import java.util.*;
import java.util.function.Function;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import org.jetbrains.annotations.NotNull;
/**
* @author lhunath, 2018-09-11
*/
public class MPQuery {
@Nonnull
private final String query;
public MPQuery(@Nullable final String query) {
this.query = (query != null)? query: "";
}
@Nonnull
public String getQuery() {
return query;
}
/**
* @return {@code true} if this query is contained wholly inside the given {@code key}.
*/
@Nonnull
public <T extends Comparable<? super T>> Optional<Result<T>> find(final T option, final Function<T, CharSequence> keyForOption) {
CharSequence key = keyForOption.apply( option );
Result<T> result = Result.noneOf( option, key );
if (query.isEmpty())
return Optional.of( result );
if (key.length() == 0)
return Optional.empty();
// Consume query and key characters until one of them runs out, recording any matches against the result's key.
int q = 0, k = 0;
while ((q < query.length()) && (k < key.length())) {
if (query.charAt( q ) == key.charAt( k )) {
result.keyMatchedAt( k );
++q;
}
++k;
}
// If query is consumed, the result is a hit.
return (q >= query.length())? Optional.of( result ): Optional.empty();
}
public static class Result<T extends Comparable<? super T>> implements Comparable<Result<T>> {
private final T option;
private final CharSequence key;
private final boolean[] keyMatches;
Result(final T option, final CharSequence key) {
this.option = option;
this.key = key;
keyMatches = new boolean[key.length()];
}
public static <T extends Comparable<? super T>> Result<T> noneOf(final T option, final CharSequence key) {
return new Result<>( option, key );
}
public static <T extends Comparable<? super T>> Result<T> allOf(final T option, final CharSequence key) {
Result<T> result = noneOf( option, key );
Arrays.fill( result.keyMatches, true );
return result;
}
@Nonnull
public T getOption() {
return option;
}
@Nonnull
public CharSequence getKey() {
return key;
}
public String getKeyAsHTML() {
return getKeyAsHTML( "u" );
}
@SuppressWarnings({ "MagicCharacter", "HardcodedFileSeparator" })
public String getKeyAsHTML(final String mark) {
String closeMark = mark.contains( " " )? mark.substring( 0, mark.indexOf( ' ' ) ): mark;
StringBuilder html = new StringBuilder();
boolean marked = false;
for (int i = 0; i < key.length(); ++i) {
if (keyMatches[i] && !marked) {
html.append( '<' ).append( mark ).append( '>' );
marked = true;
} else if (!keyMatches[i] && marked) {
html.append( '<' ).append( '/' ).append( closeMark ).append( '>' );
marked = false;
}
html.append( key.charAt( i ) );
}
if (marked)
html.append( '<' ).append( '/' ).append( closeMark ).append( '>' );
return html.toString();
}
public boolean[] getKeyMatches() {
return keyMatches.clone();
}
public boolean isExact() {
for (final boolean keyMatch : keyMatches)
if (!keyMatch)
return false;
return true;
}
private void keyMatchedAt(final int k) {
keyMatches[k] = true;
}
@Override
public int compareTo(@NotNull final Result<T> o) {
return getOption().compareTo( o.getOption() );
}
@Override
public boolean equals(final Object o) {
if (!(o instanceof Result))
return false;
Result<?> r = (Result<?>) o;
return Objects.equals( option, r.option ) && Objects.equals( key, r.key ) && Arrays.equals( keyMatches, r.keyMatches );
}
@Override
public int hashCode() {
return getOption().hashCode();
}
@Override
public String toString() {
return strf( "{Result: %s}", key );
}
}
}

View File

@@ -40,6 +40,13 @@ public interface MPQuestion extends Comparable<MPQuestion> {
void setType(MPResultType type); void setType(MPResultType type);
@Nonnull
default String getAnswer()
throws MPKeyUnavailableException, MPAlgorithmException {
return getAnswer( null );
}
@Nonnull @Nonnull
String getAnswer(@Nullable String state) String getAnswer(@Nullable String state)
throws MPKeyUnavailableException, MPAlgorithmException; throws MPKeyUnavailableException, MPAlgorithmException;

View File

@@ -18,6 +18,7 @@
package com.lyndir.masterpassword.model; package com.lyndir.masterpassword.model;
import com.google.common.collect.ImmutableCollection;
import com.google.common.primitives.UnsignedInteger; import com.google.common.primitives.UnsignedInteger;
import com.lyndir.masterpassword.*; import com.lyndir.masterpassword.*;
import java.util.Collection; import java.util.Collection;
@@ -33,9 +34,7 @@ public interface MPSite<Q extends MPQuestion> extends Comparable<MPSite<?>> {
// - Meta // - Meta
@Nonnull @Nonnull
String getName(); String getSiteName();
void setName(String name);
// - Algorithm // - Algorithm
@@ -59,11 +58,48 @@ public interface MPSite<Q extends MPQuestion> extends Comparable<MPSite<?>> {
void setLoginType(@Nullable MPResultType loginType); void setLoginType(@Nullable MPResultType loginType);
@Nonnull @Nullable
default String getResult()
throws MPKeyUnavailableException, MPAlgorithmException {
return getResult( MPKeyPurpose.Authentication );
}
@Nullable
default String getResult(final MPKeyPurpose keyPurpose)
throws MPKeyUnavailableException, MPAlgorithmException {
return getResult( keyPurpose, null );
}
@Nullable
default String getResult(final MPKeyPurpose keyPurpose, @Nullable final String keyContext)
throws MPKeyUnavailableException, MPAlgorithmException {
return getResult( keyPurpose, keyContext, null );
}
@Nullable
String getResult(MPKeyPurpose keyPurpose, @Nullable String keyContext, @Nullable String state) String getResult(MPKeyPurpose keyPurpose, @Nullable String keyContext, @Nullable String state)
throws MPKeyUnavailableException, MPAlgorithmException; throws MPKeyUnavailableException, MPAlgorithmException;
/**
* @see MPMasterKey#siteResult(String, MPAlgorithm, UnsignedInteger, MPKeyPurpose, String, MPResultType, String)
*/
@Nullable
String getResult(MPKeyPurpose keyPurpose, @Nullable String keyContext,
@Nullable UnsignedInteger counter, MPResultType type, @Nullable String state)
throws MPKeyUnavailableException, MPAlgorithmException;
@Nonnull @Nonnull
String getState(MPKeyPurpose keyPurpose, @Nullable String keyContext,
@Nullable UnsignedInteger counter, MPResultType type, String state)
throws MPKeyUnavailableException, MPAlgorithmException;
@Nullable
default String getLogin()
throws MPKeyUnavailableException, MPAlgorithmException {
return getLogin( null );
}
@Nullable
String getLogin(@Nullable String state) String getLogin(@Nullable String state)
throws MPKeyUnavailableException, MPAlgorithmException; throws MPKeyUnavailableException, MPAlgorithmException;
@@ -72,10 +108,17 @@ public interface MPSite<Q extends MPQuestion> extends Comparable<MPSite<?>> {
@Nonnull @Nonnull
MPUser<?> getUser(); MPUser<?> getUser();
void addQuestion(Q question); @Nonnull
Q addQuestion(String keyword);
void deleteQuestion(Q question); @Nonnull
Q addQuestion(Q question);
boolean deleteQuestion(Q question);
@Nonnull @Nonnull
Collection<Q> getQuestions(); Collection<Q> getQuestions();
@Nonnull
ImmutableCollection<MPQuery.Result<Q>> findQuestions(MPQuery query);
} }

View File

@@ -18,6 +18,7 @@
package com.lyndir.masterpassword.model; package com.lyndir.masterpassword.model;
import com.google.common.collect.ImmutableCollection;
import com.lyndir.masterpassword.*; import com.lyndir.masterpassword.*;
import java.util.Collection; import java.util.Collection;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
@@ -45,6 +46,11 @@ public interface MPUser<S extends MPSite<?>> extends Comparable<MPUser<?>> {
void setAlgorithm(MPAlgorithm algorithm); void setAlgorithm(MPAlgorithm algorithm);
@Nullable
default MPResultType getDefaultType() {
return null;
}
@Nullable @Nullable
byte[] getKeyID(); byte[] getKeyID();
@@ -54,12 +60,11 @@ public interface MPUser<S extends MPSite<?>> extends Comparable<MPUser<?>> {
/** /**
* Performs an authentication attempt against the keyID for this user. * Performs an authentication attempt against the keyID for this user.
* *
* Note: If a keyID is not set, authentication will always succeed and the keyID will be set to match the given master password.
*
* @param masterPassword The password to authenticate with. * @param masterPassword The password to authenticate with.
* You cannot re-use this array after passing it in, authentication will destroy its contents.
* *
* @throws MPIncorrectMasterPasswordException If authentication fails due to the given master password not matching the user's keyID. * @throws MPIncorrectMasterPasswordException If authentication fails due to the given master password not matching the user's keyID.
* @apiNote If a keyID is not set, authentication will always succeed and the keyID will be set to match the given master password.
* <b>This method destroys the contents of the {@code masterPassword} array.</b>
*/ */
void authenticate(char[] masterPassword) void authenticate(char[] masterPassword)
throws MPIncorrectMasterPasswordException, MPAlgorithmException; throws MPIncorrectMasterPasswordException, MPAlgorithmException;
@@ -67,15 +72,26 @@ public interface MPUser<S extends MPSite<?>> extends Comparable<MPUser<?>> {
/** /**
* Performs an authentication attempt against the keyID for this user. * Performs an authentication attempt against the keyID for this user.
* *
* Note: If a keyID is not set, authentication will always succeed and the keyID will be set to match the given key.
*
* @param masterKey The master key to authenticate with. * @param masterKey The master key to authenticate with.
* *
* @throws MPIncorrectMasterPasswordException If authentication fails due to the given master password not matching the user's keyID. * @throws MPIncorrectMasterPasswordException If authentication fails due to the given master password not matching the user's keyID.
* @apiNote If a keyID is not set, authentication will always succeed and the keyID will be set to match the given key.
*/ */
void authenticate(MPMasterKey masterKey) void authenticate(MPMasterKey masterKey)
throws MPIncorrectMasterPasswordException, MPKeyUnavailableException, MPAlgorithmException; throws MPIncorrectMasterPasswordException, MPKeyUnavailableException, MPAlgorithmException;
/**
* Clear all authentication tokens and secrets from memory, effectively logging the user out.
*/
void invalidate();
/**
* Wipe the key ID, allowing the user to {@link #authenticate(char[])} with any master password.
*
* Note: Authenticating with a different master password will cause all of the user's results to change.
*/
void reset();
boolean isMasterKeyAvailable(); boolean isMasterKeyAvailable();
@Nonnull @Nonnull
@@ -84,13 +100,30 @@ public interface MPUser<S extends MPSite<?>> extends Comparable<MPUser<?>> {
// - Relations // - Relations
void addSite(S site); @Nonnull
S addSite(String siteName);
void deleteSite(S site); @Nonnull
S addSite(S site);
boolean deleteSite(MPSite<?> site);
@Nonnull @Nonnull
Collection<S> getSites(); Collection<S> getSites();
@Nonnull @Nonnull
Collection<S> findSites(String query); ImmutableCollection<MPQuery.Result<S>> findSites(MPQuery query);
void addListener(Listener listener);
void removeListener(Listener listener);
interface Listener {
void onUserUpdated(MPUser<?> user);
void onUserAuthenticated(MPUser<?> user);
void onUserInvalidated(MPUser<?> user);
}
} }

View File

@@ -1,53 +0,0 @@
//==============================================================================
// This file is part of Master Password.
// Copyright (c) 2011-2017, Maarten Billemont.
//
// Master Password is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Master Password is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You can find a copy of the GNU General Public License in the
// LICENSE file. Alternatively, see <http://www.gnu.org/licenses/>.
//==============================================================================
package com.lyndir.masterpassword.model;
import com.google.common.collect.*;
import java.util.Collection;
import java.util.Map;
/**
* @author lhunath, 14-12-05
*/
public abstract class MPUserManager<U extends MPUser<?>> {
private final Map<String, U> usersByName = Maps.newHashMap();
protected MPUserManager(final Iterable<U> users) {
for (final U user : users)
usersByName.put( user.getFullName(), user );
}
public Collection<U> getUsers() {
return ImmutableSortedSet.copyOf( usersByName.values() );
}
public U getUserNamed(final String fullName) {
return usersByName.get( fullName );
}
public void addUser(final U user) {
usersByName.put( user.getFullName(), user );
}
public void deleteUser(final U user) {
usersByName.remove( user.getFullName() );
}
}

Some files were not shown because too many files have changed in this diff Show More