2
0

Compare commits

...

97 Commits

Author SHA1 Message Date
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
Maarten Billemont
954c4f8d63 Java improvements.
UI threading improvements.
Save user/site changes to file.
Ordering of user / site fixes.
Add questions to JSON output.
Bring JSON output format in line with C.
2018-07-09 01:13:25 -04:00
Maarten Billemont
529f1feace Omit empty _mpw_ext & unneeded escaping. 2018-07-09 01:12:28 -04:00
Maarten Billemont
5cdff6f155 Some fixes to search paths. 2018-07-08 20:50:32 -04:00
Maarten Billemont
81358c16f9 Omit questions if none present and fix parsing sites with no questions. 2018-07-08 20:49:42 -04:00
Maarten Billemont
4a555748cd UTC times were parsed in as local time by mktime. 2018-07-08 20:49:31 -04:00
Maarten Billemont
698566a914 Update stale GitHub references with new GitLab home. 2018-07-05 17:14:50 -04:00
Maarten Billemont
64a69856ac Fix libsodium include paths. 2018-07-04 14:14:49 -04:00
Maarten Billemont
f76de9520c Merge branch 'master' of gitlab.com:MasterPassword/MasterPassword 2018-07-04 12:17:04 -04:00
Maarten Billemont
4b3662bbe9 Support for x86 Linux. 2018-07-04 12:16:37 -04:00
Maarten Billemont
b25130f4d2 Tweak include paths & add clean support to android libs. 2018-07-03 11:21:48 -04:00
Maarten Billemont
d6617563fc Clean libs when cleaning build. 2018-07-01 21:20:56 -04:00
Maarten Billemont
8d24ec3250 Merge branch 'patch-1' into 'master'
Update README.md Fix broken link

See merge request MasterPassword/MasterPassword!249
2018-07-01 20:31:14 +00:00
Jakob Kukla
bbcc250a5c Update README.md
Fix broken link to native CLI instructions
2018-07-01 18:04:19 +00:00
Maarten Billemont
4abb50ad9b Fix lipo regression in build script. 2018-06-30 23:54:35 -04:00
Maarten Billemont
30dac64d5d Support for building on Linux. 2018-06-30 23:35:29 -04:00
Maarten Billemont
e4e2aaad95 Ensure GitLab CI does clean builds. 2018-06-30 20:53:29 -04:00
Maarten Billemont
835acf45eb Improve check for platform-specific GUI support & url access for CF needs user-agent. 2018-06-30 15:24:58 -04:00
Maarten Billemont
c3f6796833 Pre-built library files for different platforms. 2018-06-30 12:35:40 -04:00
Maarten Billemont
86f4e8ec06 Fix gradle build. 2018-06-30 11:57:36 -04:00
Maarten Billemont
0dddcef28e libsodium and masterpassword-core must be linked statically + windows build configuration fixes. 2018-06-27 02:54:31 -04:00
Maarten Billemont
cc583c789d Merge branch 'master' of gitlab.com:MasterPassword/MasterPassword 2018-06-25 13:22:36 -04:00
Maarten Billemont
42d78da74e Fix some bugs in the new mpw_strings & mpw_strncasecmp. 2018-06-25 13:19:26 -04:00
Maarten Billemont
b5040a7786 Harmonize standardized platform naming between Native class and gradle. 2018-06-25 12:25:38 -04:00
Maarten Billemont
b1698ee339 C type fixes. 2018-06-25 12:25:17 -04:00
Maarten Billemont
8ffc0ae350 Execute gradle scripts with bash. 2018-06-25 10:34:56 -04:00
Maarten Billemont
8276d2f4e5 Build dependencies with a task & all windows archs. 2018-06-25 02:02:51 -04:00
Maarten Billemont
11cf86bc73 Use brew version of libtoolize. 2018-06-24 16:48:22 -04:00
Maarten Billemont
5084511404 Merge branch 'master' of gitlab.com:MasterPassword/MasterPassword 2018-06-24 16:31:22 -04:00
Maarten Billemont
fffec56d4e Build support for Microsoft Windows. 2018-06-24 16:20:42 -04:00
Maarten Billemont
9a0828c1eb Bump site for Android release. 2018-06-21 10:31:10 -04:00
140 changed files with 4895 additions and 2581 deletions

28
.dockerignore Normal file
View File

@@ -0,0 +1,28 @@
# OS-Specific junk.
.DS_Store
Thumbs.db
# IntelliJ
.idea
*.iml
*.ipr
*.iws
# Xcode IDE
xcuserdata/
DerivedData/
# Generated
/platform-darwin/Resources/Media/Images.xcassets/
/platform-darwin/Podfile.lock
/platform-darwin/Pods/
# Gradle
build
.gradle
local.properties
/gradle/builds
/platform-android/.externalNativeBuild
# Git
.git

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

@@ -6,12 +6,13 @@ build_project:
stage: build stage: build
script: script:
- "( brew bundle )" - "( brew bundle )"
- "( ./lib/bin/build_libsodium-macos )" - "( ./lib/bin/build_libsodium-macos clean && ./lib/bin/build_libsodium-macos )"
- "( ./lib/bin/build_libjson-c-macos clean && ./lib/bin/build_libjson-c-macos )"
- "( cd ./platform-independent/c/cli && ./clean && targets=all ./build && ./mpw-tests && ./mpw-cli-tests )" - "( cd ./platform-independent/c/cli && ./clean && targets=all ./build && ./mpw-tests && ./mpw-cli-tests )"
- "( cd ./gradle && ./gradlew --info clean test )" - "( cd ./gradle && ./gradlew --stacktrace clean test )"
- "( xcodebuild -workspace platform-darwin/MasterPassword.xcworkspace -configuration 'Test' -scheme 'MasterPassword iOS' -sdk iphonesimulator clean build )" - "( xcodebuild -workspace platform-darwin/MasterPassword.xcworkspace -configuration 'Test' -scheme 'MasterPassword iOS' -sdk iphonesimulator clean build )"
- "( 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,5 +1,6 @@
brew "libsodium" brew "libsodium"
brew "json-c" brew "json-c"
brew "libtool"
brew "automake" brew "automake"
brew "autoconf" brew "autoconf"

12
Dockerfile Normal file
View File

@@ -0,0 +1,12 @@
FROM debian:stable-slim
# For i386
#FROM i386/debian:stable-slim
#ENTRYPOINT ["linux32", "--"]
# 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 cd /mpw/gradle && ./gradlew -i clean build

View File

@@ -122,7 +122,7 @@ The `master-password` on the other hand, is only a simple phrase, which means it
5. I have another question. 5. I have another question.
Please don't hesitate to [get in touch](#support), we're more than happy to answer all your Master Password questions. Any problems or suggestions can be reported [as GitHub issues](https://github.com/Lyndir/MasterPassword/issues). Please don't hesitate to [get in touch](#support), we're more than happy to answer all your Master Password questions. Any problems or suggestions can be reported [as GitLab issues](https://gitlab.com/MasterPassword/MasterPassword/issues/).
@@ -187,7 +187,7 @@ Note that in order to build the Android application, you will need to have the A
Go into the `platform-independent/cli-c` directory and run `./build`. The native command-line client will then be built. Go into the `platform-independent/cli-c` directory and run `./build`. The native command-line client will then be built.
For detailed instructions, see [the native CLI instructions](platform-independent/cli-c/README.md). For detailed instructions, see [the native CLI instructions](platform-independent/c/README.md).
## Support ## Support

View File

@@ -1,9 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectCodeStyleSettingsManager">
<option name="PER_PROJECT_SETTINGS">
<value />
</option>
<option name="PREFERRED_PROJECT_CODE_STYLE" value="Lhunath" />
</component>
</project>

View File

@@ -1,7 +0,0 @@
<component name="CopyrightManager">
<copyright>
<option name="keyword" value="Copyright|License|WARRANTY" />
<option name="myName" value="GPLv3" />
<option name="notice" value="This file is part of &amp;#36;project.name.&#10;Copyright (c) &amp;#36;today.year.&#10;&#10;&amp;#36;project.name is free software: you can redistribute it and/or modify&#10;it under the terms of the GNU General Public License as published by&#10;the Free Software Foundation, either version 3 of the License, or&#10;(at your option) any later version.&#10;&#10;&amp;#36;project.name is distributed in the hope that it will be useful,&#10;but WITHOUT ANY WARRANTY; without even the implied warranty of&#10;MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the&#10;GNU General Public License for more details.&#10;&#10;You can find a copy of the GNU General Public License in the&#10;LICENSE file. Alternatively, see &lt;http://www.gnu.org/licenses/&gt;." />
</copyright>
</component>

View File

@@ -1,13 +0,0 @@
<component name="CopyrightManager">
<settings>
<module2copyright>
<element module="masterpassword" copyright="Master Password" />
</module2copyright>
<LanguageOptions name="__TEMPLATE__">
<option name="block" value="false" />
<option name="separateBefore" value="true" />
<option name="separateAfter" value="true" />
<option name="filler" value="=" />
</LanguageOptions>
</settings>
</component>

View File

@@ -1,9 +0,0 @@
<component name="InspectionProjectProfileManager">
<settings>
<option name="projectProfile" value="Lhunath" />
<option name="useProjectProfile" value="false" />
<option name="PROJECT_PROFILE" value="Lhunath" />
<option name="USE_PROJECT_PROFILE" value="false" />
<version value="1.0" />
</settings>
</component>

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

@@ -1,45 +1,43 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<project version="4"> <project version="4">
<component name="FrameworkDetectionExcludesConfiguration">
<type id="jpa" />
<type id="web" />
</component>
<component name="MavenProjectsManager">
<option name="originalFiles">
<list>
<option value="$PROJECT_DIR$/../../opal/pom.xml" />
<option value="$PROJECT_DIR$/../../pom.xml" />
</list>
</option>
</component>
<component name="NullableNotNullManager"> <component name="NullableNotNullManager">
<option name="myDefaultNullable" value="javax.annotation.Nullable" /> <option name="myDefaultNullable" value="javax.annotation.Nullable" />
<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$/classes" /> <output url="file://$PROJECT_DIR$/build/classes" />
</component> </component>
<component name="ThriftCompiler"> <component name="ProjectType">
<compilers /> <option name="id" value="Android" />
</component> </component>
</project> </project>

View File

@@ -1,28 +0,0 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Android" type="AndroidRunConfigurationType" factoryName="Android App">
<module name="android" />
<option name="DEPLOY" value="true" />
<option name="ARTIFACT_NAME" value="" />
<option name="PM_INSTALL_OPTIONS" value="" />
<option name="ACTIVITY_EXTRA_FLAGS" value="" />
<option name="MODE" value="default_activity" />
<option name="PREFERRED_AVD" value="" />
<option name="CLEAR_LOGCAT" value="false" />
<option name="SHOW_LOGCAT_AUTOMATICALLY" value="false" />
<option name="SKIP_NOOP_APK_INSTALLATIONS" value="true" />
<option name="FORCE_STOP_RUNNING_APP" value="true" />
<option name="TARGET_SELECTION_MODE" value="SHOW_DIALOG" />
<option name="USE_LAST_SELECTED_DEVICE" value="false" />
<option name="PREFERRED_AVD" value="" />
<option name="DEBUGGER_TYPE" value="Java" />
<Java />
<Profilers>
<option name="ENABLE_ADVANCED_PROFILING" value="false" />
<option name="SUPPORT_LIB_ENABLED" value="true" />
<option name="INSTRUMENTATION_ENABLED" value="true" />
</Profilers>
<option name="DEEP_LINK" value="" />
<option name="ACTIVITY_CLASS" value="" />
<method />
</configuration>
</component>

View File

@@ -1,3 +0,0 @@
<component name="DependencyValidationManager">
<scope name="masterpassword" pattern="com.lyndir.masterpassword..*" />
</component>

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.8'
tasks.withType( JavaCompile ) { tasks.withType( JavaCompile ) {
options.encoding = 'UTF-8' options.encoding = 'UTF-8'

View File

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

View File

@@ -4,13 +4,14 @@ class Mpw < Formula
url "https://masterpassword.app/mpw-2.6-cli-5-0-g344771db.tar.gz" url "https://masterpassword.app/mpw-2.6-cli-5-0-g344771db.tar.gz"
version "2.6-cli-5" version "2.6-cli-5"
sha256 "954c07b1713ecc2b30a07bead9c11e6204dd774ca67b5bdf7d2d6ad1c4eec170" sha256 "954c07b1713ecc2b30a07bead9c11e6204dd774ca67b5bdf7d2d6ad1c4eec170"
head "https://github.com/Lyndir/MasterPassword.git" revision 1
head "https://gitlab.com/MasterPassword/MasterPassword.git"
bottle do bottle do
cellar :any cellar :any
sha256 "ae8b265936797778a7cde788377eed89d9eacd267755a0b1186790057a10ff3b" => :high_sierra sha256 "46677cf8649983d5b77103d2ca56d9ad3697808ecc406f626a3462a089f932da" => :high_sierra
sha256 "b8a106c3c84ff939e928613d4a6ccf7b5234e40ebae1edf15e3cac52d8c2e5ea" => :sierra sha256 "19bf22915b3c534ad3ee6f1dfc20f142d53ae6c0c88757ae2632b7b1daa6667f" => :sierra
sha256 "9b58425b028a2598932474e1d0c17c13aad57e0a53ae7308c1b38404da8f3331" => :el_capitan sha256 "7090c3d31289d2ac5529bd0a6bae2632a36ba7fcd4bb7974248bb36a15f67c7e" => :el_capitan
end end
option "without-json-c", "Disable JSON configuration support" option "without-json-c", "Disable JSON configuration support"

View File

@@ -1,29 +1,27 @@
rootProject.name = 'masterpassword' rootProject.name = 'masterpassword'
def local = new Properties(); def local = new Properties()
try { def localFile = file( 'local.properties' )
local.load(file('local.properties').newDataInputStream()) localFile.exists() && local.load( localFile.newDataInputStream() )
} catch (FileNotFoundException ignored) {
}
include 'masterpassword-core' include 'masterpassword-core'
project(':masterpassword-core').projectDir = new File( '../platform-independent/c/core' ) project( ':masterpassword-core' ).projectDir = new File( '../platform-independent/c/core' )
include 'masterpassword-algorithm' include 'masterpassword-algorithm'
project(':masterpassword-algorithm').projectDir = new File( '../platform-independent/java/algorithm' ) project( ':masterpassword-algorithm' ).projectDir = new File( '../platform-independent/java/algorithm' )
include 'masterpassword-model' include 'masterpassword-model'
project(':masterpassword-model').projectDir = new File( '../platform-independent/java/model' ) project( ':masterpassword-model' ).projectDir = new File( '../platform-independent/java/model' )
include 'masterpassword-tests' include 'masterpassword-tests'
project(':masterpassword-tests').projectDir = new File( '../platform-independent/java/tests' ) project( ':masterpassword-tests' ).projectDir = new File( '../platform-independent/java/tests' )
include 'masterpassword-gui' include 'masterpassword-gui'
project(':masterpassword-gui').projectDir = new File( '../platform-independent/java/gui' ) project( ':masterpassword-gui' ).projectDir = new File( '../platform-independent/java/gui' )
if (local.containsKey('sdk.dir')) { if (local.containsKey( 'sdk.dir' ) && file( local.getProperty( 'sdk.dir' ) ).exists()) {
include 'masterpassword-android' include 'masterpassword-android'
project(':masterpassword-android').projectDir = new File( '../platform-android' ) project( ':masterpassword-android' ).projectDir = new File( '../platform-android' )
} else { } else {
logger.warn( "Skipping masterpassword-android since sdk.dir is not defined in local.properties." ) logger.warn( "Skipping masterpassword-android since sdk.dir is not defined in local.properties." )
} }

View File

@@ -2,7 +2,7 @@
# #
# Your build script should simply source this script, optionally override any build hooks and then invoke `build`. # Your build script should simply source this script, optionally override any build hooks and then invoke `build`.
# The build product should be available under `build-<platform>~/out`, under the library path. # The build product should be available under `build-<platform>~/out`, under the library path.
# #
# Hook lifecycle: # Hook lifecycle:
# - build # - build
# - initialize # - initialize
@@ -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,10 +58,16 @@ _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 `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() {
needs automake autoconf if [[ $platform = windows ]]; then
needs cmd
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'. See https://visualstudio.microsoft.com/downloads/?q=build+tools"; return 1; }
else
needs libtool:libtoolize,glibtoolize automake autoconf make
fi
} }
# clean <prefix> <platform> # clean <prefix> <platform>
@@ -63,9 +77,15 @@ _initialize_needs() {
# By default, this will wipe the prefix, run `make distclean` and `git clean -fdx`. # By default, this will wipe the prefix, run `make distclean` and `git clean -fdx`.
clean() { _clean "$@"; } clean() { _clean "$@"; }
_clean() { _clean() {
if [[ $platform = windows ]]; then
printf '"%%VSINSTALLDIR%%\Common7\Tools\VsMSBuildCmd.bat" && msbuild /t:Clean' > .clean.bat
cmd //c .clean.bat
rm -f .clean.bat
elif [[ -e Makefile ]] && make -s distclean; then :
elif [[ -e .git ]] && git clean -fdx; then :
fi
rm -rf "$prefix" rm -rf "$prefix"
[[ ! -e Makefile ]] || make -s distclean
[[ ! -e .git ]] || git clean -fdx
} }
# prepare <prefix> <platform> [ <arch> ... ] # prepare <prefix> <platform> [ <arch> ... ]
@@ -91,7 +111,7 @@ _prepare_clean() {
local prefix=$1 platform=$2; shift 2 local prefix=$1 platform=$2; shift 2
rm -rf "$prefix" rm -rf "$prefix"
install -d "$prefix" install -d "$prefix/out"
} }
# prepare_config <prefix> <platform> [ <arch> ... ] # prepare_config <prefix> <platform> [ <arch> ... ]
@@ -103,7 +123,14 @@ prepare_config() { _prepare_config "$@"; }
_prepare_config() { _prepare_config() {
local prefix=$1 platform=$2; shift 2 local prefix=$1 platform=$2; shift 2
[[ -e configure ]] || autoreconf --verbose --install --symlink 2> >(sed 's/^\([^:]*\):[0-9]\{1,\}: /\1: /') [[ -e "$prefix/out/.prepared" ]] && return
if [[ $platform = windows ]]; then :
else
autoreconf --verbose --install --force 2> >(sed 's/^\([^:]*\):[0-9]\{1,\}: /\1: /')
fi
touch "$prefix/out/.prepared"
} }
# target <prefix> <platform> <arch> # target <prefix> <platform> <arch>
@@ -127,19 +154,27 @@ target_prepare() { _target_prepare "$@"; }
_target_prepare() { _target_prepare() {
local prefix=$1 platform=$2 arch=$3; shift 3 local prefix=$1 platform=$2 arch=$3; shift 3
[[ ! -e Makefile ]] || make -s clean if [[ $platform = windows ]]; then :
else
[[ ! -e Makefile ]] || make -s clean
fi
} }
# target_configure <prefix> <platform> <arch> [ <args> ... ] # target_configure <prefix> <platform> <arch> [ <args> ... ]
# #
# Configure the library for building the target. # Configure the library for building the target.
# #
# By default, this will run `./configure --host=<host> --prefix=<prefix>/<arch> --disable-shared <args>`. # By default, this will run `./configure --host=<host> --prefix=<prefix>/<arch> <args>`.
# By default, some platform-specific arguments will be passed in as well as
# --enable-pic --disable-pie to ensure the resulting library can be linked again.
target_configure() { _target_configure "$@"; } target_configure() { _target_configure "$@"; }
_target_configure() { _target_configure() {
local prefix=$1 platform=$2 arch=$3; shift 3 local prefix=$1 platform=$2 arch=$3; shift 3
case "$platform" in case "$platform" in
'windows')
return
;;
'android') 'android')
host=( "$SDKROOT"/*-android* ) host=${host##*/} host=( "$SDKROOT"/*-android* ) host=${host##*/}
@@ -153,7 +188,7 @@ _target_configure() {
;; ;;
esac esac
./configure ${host:+--host="$host"} --prefix="$prefix/$arch" "$@" ./configure ${host:+--host="$host"} --enable-pic --disable-pie --prefix="$prefix/$arch" "$@"
} }
# target_build <prefix> <platform> <arch> # target_build <prefix> <platform> <arch>
@@ -164,10 +199,16 @@ _target_configure() {
target_build() { _target_build "$@"; } target_build() { _target_build "$@"; }
_target_build() { _target_build() {
local prefix=$1 platform=$2 arch=$3; shift 3 local prefix=$1 platform=$2 arch=$3; shift 3
#make -j3 check
cores=$(getconf NPROCESSORS_ONLN 2>/dev/null || getconf _NPROCESSORS_ONLN 2>/dev/null ||:) if [[ $platform = windows ]]; then
make -j"${cores:-3}" install # 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=Release;Platform=%s;OutDir=%s' "$arch" "$(cygpath -w "${prefix##$PWD/}/$arch/")" > .build.bat
cmd //c .build.bat
rm -f .build.bat
else
local cores=$(getconf NPROCESSORS_ONLN 2>/dev/null || getconf _NPROCESSORS_ONLN 2>/dev/null ||:)
make -j"${cores:-3}" install
fi
} }
# finalize <prefix> <platform> [ <arch> ... ] # finalize <prefix> <platform> [ <arch> ... ]
@@ -188,21 +229,34 @@ _finalize() {
finalize_merge() { _finalize_merge "$@"; } finalize_merge() { _finalize_merge "$@"; }
_finalize_merge() { _finalize_merge() {
local prefix=$1 platform=$2; shift 2 local prefix=$1 platform=$2; shift 2
local archs=( "$@" )
mv -f -- "$prefix/$1/include" "$prefix/out/" [[ -e "$prefix/$archs/include" ]] && mv -f -- "$prefix/$archs/include" "$prefix/out/"
mkdir -p "$prefix/out/lib" install -d "$prefix/out/lib"
case "$platform" in case "$platform" in
'linux')
for arch in "${archs[@]}"; do
install -d "$prefix/out/lib/$arch"
install -p "$prefix/$arch/lib/"*.a "$prefix/out/lib/$arch/"
done
;;
'windows')
for arch in "${archs[@]}"; do
install -d "$prefix/out/lib/$arch"
install -p "$prefix/$arch/"*.lib "$prefix/out/lib/$arch/"
done
;;
'macos'|'ios') 'macos'|'ios')
for lib in "$prefix/$1/lib/"*; do for lib in "$prefix/$archs/lib/"*; do
if lipo -info "$lib" >/dev/null 2>&1; then if lipo -info "$lib" >/dev/null 2>&1; then
local lib=("${lib##*/}") libs=("${@/#/$prefix/}") libs=("${libs[@]/%//lib/$lib}") local lib=("${lib##*/}") libs=("${archs[@]/#/$prefix/}") libs=("${libs[@]/%//lib/$lib}")
lipo -create "${libs[@]}" -output "$prefix/out/lib/$lib" lipo -create "${libs[@]}" -output "$prefix/out/lib/$lib"
fi fi
done done
;; ;;
'android') 'android')
for arch; do for arch in "${archs[@]}"; do
local abi=$arch local abi=$arch
case "$arch" in case "$arch" in
'arm') abi='armeabi-v7a' ;; 'arm') abi='armeabi-v7a' ;;
@@ -224,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() {
[[ ! -e Makefile ]] || make -s clean if [[ $platform = windows ]]; then :
else
[[ ! -e Makefile ]] || make -s clean
fi
} }
# build <name> [<platform>] # build <name> [<platform>]
@@ -247,6 +304,8 @@ _build() {
'macos') archs=( 'x86_64' ) ;; 'macos') archs=( 'x86_64' ) ;;
'ios') archs=( 'i386' 'x86_64' 'armv7' 'armv7s' 'arm64' ) ;; 'ios') archs=( 'i386' 'x86_64' 'armv7' 'armv7s' 'arm64' ) ;;
'android') archs=( 'arm' 'arm64' 'x86' 'x86_64' ) ;; 'android') archs=( 'arm' 'arm64' 'x86' 'x86_64' ) ;;
'windows') archs=( 'Win32' 'x64' ) ;;
*) archs=( 'i386' 'x86_64' ) ;;
esac esac
fi fi
@@ -270,6 +329,8 @@ _build() {
# Set up a base environment for the platform. # Set up a base environment for the platform.
case "$platform" in case "$platform" in
'windows')
;;
'macos') 'macos')
SDKROOT="$(xcrun --show-sdk-path --sdk macosx)" SDKROOT="$(xcrun --show-sdk-path --sdk macosx)"
export PATH="$(xcrun --show-sdk-platform-path --sdk macosx)/usr/bin:$PATH" export PATH="$(xcrun --show-sdk-platform-path --sdk macosx)/usr/bin:$PATH"
@@ -319,4 +380,3 @@ _build() {
finalize "$prefix" "$platform" "${archs[@]}" finalize "$prefix" "$platform" "${archs[@]}"
} }

8
lib/bin/build_libjson-c-linux Executable file
View File

@@ -0,0 +1,8 @@
#!/usr/bin/env bash
source "${BASH_SOURCE%/*}/build_lib"
autoreconf() {
command autoreconf -Iautoconf-archive/m4 "$@"
}
build libjson-c linux

View File

@@ -0,0 +1,8 @@
#!/usr/bin/env bash
source "${BASH_SOURCE%/*}/build_lib"
autoreconf() {
command autoreconf -Iautoconf-archive/m4 "$@"
}
build libjson-c windows

4
lib/bin/build_libsodium-linux Executable file
View File

@@ -0,0 +1,4 @@
#!/usr/bin/env bash
source "${BASH_SOURCE%/*}/build_lib"
build libsodium linux

View File

@@ -0,0 +1,13 @@
#!/usr/bin/env bash
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

View File

@@ -13,12 +13,12 @@ add_library( mpw SHARED
add_library( sodium SHARED IMPORTED ) add_library( sodium SHARED IMPORTED )
set_target_properties( sodium PROPERTIES IMPORTED_LOCATION "${PROJECT_SOURCE_DIR}/../lib/libsodium/build-android~/out/lib/${ANDROID_ABI}/libsodium.so" ) set_target_properties( sodium PROPERTIES IMPORTED_LOCATION "${PROJECT_SOURCE_DIR}/../lib/libsodium/build-android~/out/lib/${ANDROID_ABI}/libsodium.so" )
target_include_directories( mpw PRIVATE "${PROJECT_SOURCE_DIR}/../lib/libsodium/build-android~/out" ) target_include_directories( mpw PRIVATE "${PROJECT_SOURCE_DIR}/../lib/libsodium/build-android~/out/include" )
target_compile_definitions( mpw PRIVATE -DMPW_SODIUM=1 ) target_compile_definitions( mpw PRIVATE -DMPW_SODIUM=1 )
target_link_libraries( mpw PRIVATE sodium ) target_link_libraries( mpw PRIVATE sodium )
add_library( json-c SHARED IMPORTED ) add_library( json-c SHARED IMPORTED )
set_target_properties( json-c PROPERTIES IMPORTED_LOCATION "${PROJECT_SOURCE_DIR}/../lib/libjson-c/build-android~/out/lib/${ANDROID_ABI}/libjson-c.so" ) set_target_properties( json-c PROPERTIES IMPORTED_LOCATION "${PROJECT_SOURCE_DIR}/../lib/libjson-c/build-android~/out/lib/${ANDROID_ABI}/libjson-c.so" )
target_include_directories( mpw PRIVATE "${PROJECT_SOURCE_DIR}/../lib/libjson-c/build-android~/out" ) target_include_directories( mpw PRIVATE "${PROJECT_SOURCE_DIR}/../lib/libjson-c/build-android~/out/include" )
target_compile_definitions( mpw PRIVATE -DMPW_JSON=1 ) target_compile_definitions( mpw PRIVATE -DMPW_JSON=1 )
target_link_libraries( mpw PRIVATE json-c ) target_link_libraries( mpw PRIVATE json-c )

View File

@@ -22,8 +22,8 @@ android {
} }
sourceSets { sourceSets {
main { main {
jniLibs.srcDirs "$projectDir/../lib/libsodium/build-android~/out/lib", jniLibs.srcDirs "$rootDir/../lib/libsodium/build-android~/out/lib",
"$projectDir/../lib/libjson-c/build-android~/out/lib" "$rootDir/../lib/libjson-c/build-android~/out/lib"
} }
} }
@@ -55,11 +55,23 @@ dependencies {
} }
preBuild { preBuild {
dependsOn task( type: Exec, 'buildLibSodium', { dependsOn task( type: Exec, 'build_libsodium-android', {
commandLine "$projectDir/../lib/bin/build_libsodium-android" commandLine 'bash', "$rootDir/../lib/bin/build_libsodium-android"
environment 'ANDROID_NDK_HOME', android.ndkDirectory
} ) } )
dependsOn task( type: Exec, 'buildLibJson-c', { dependsOn task( type: Exec, 'build_libjson-c-android', {
commandLine "$projectDir/../lib/bin/build_libjson-c-android" commandLine 'bash', "$rootDir/../lib/bin/build_libjson-c-android"
environment 'ANDROID_NDK_HOME', android.ndkDirectory
} ) } )
} }
clean {
dependsOn task( type: Exec, 'clean_libsodium-android', {
commandLine 'bash', "$rootDir/../lib/bin/build_libsodium-android", 'clean'
environment 'ANDROID_NDK_HOME', android.ndkDirectory
} )
dependsOn task( type: Exec, 'clean_libjson-c-android', {
commandLine 'bash', "$rootDir/../lib/bin/build_libjson-c-android", 'clean'
environment 'ANDROID_NDK_HOME', android.ndkDirectory
} )
}

View File

@@ -62,7 +62,7 @@
DA1554ED20B3928E00EA92C5 /* mpw-util.h */, DA1554ED20B3928E00EA92C5 /* mpw-util.h */,
); );
name = core; name = core;
path = ../platform-independent/c/core/src; path = "../platform-independent/c/core/src";
sourceTree = "<group>"; sourceTree = "<group>";
}; };
/* End PBXGroup section */ /* End PBXGroup section */
@@ -129,8 +129,9 @@
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
buildSettings = { buildSettings = {
HEADER_SEARCH_PATHS = ( HEADER_SEARCH_PATHS = (
"/Library/Java/JavaVirtualMachines/jdk1.8.0_161.jdk/Contents/Home/include/**", "$(inherited)",
../lib/libsodium/src/libsodium/include, "\"$(PROJECT_DIR)/../lib/libsodium/build-ios~/out/include\"",
"\"$(PROJECT_DIR)/../lib/libjson-c/build-ios~/out/include\"",
); );
JAVA_HOME = /Library/Java/JavaVirtualMachines/jdk1.8.0_161.jdk/Contents/Home; JAVA_HOME = /Library/Java/JavaVirtualMachines/jdk1.8.0_161.jdk/Contents/Home;
OTHER_CFLAGS = ( OTHER_CFLAGS = (
@@ -144,8 +145,9 @@
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
buildSettings = { buildSettings = {
HEADER_SEARCH_PATHS = ( HEADER_SEARCH_PATHS = (
"/Library/Java/JavaVirtualMachines/jdk1.8.0_161.jdk/Contents/Home/include/**", "$(inherited)",
../lib/libsodium/src/libsodium/include, "\"$(PROJECT_DIR)/../lib/libsodium/build-ios~/out/include\"",
"\"$(PROJECT_DIR)/../lib/libjson-c/build-ios~/out/include\"",
); );
JAVA_HOME = /Library/Java/JavaVirtualMachines/jdk1.8.0_161.jdk/Contents/Home; JAVA_HOME = /Library/Java/JavaVirtualMachines/jdk1.8.0_161.jdk/Contents/Home;
OTHER_CFLAGS = ( OTHER_CFLAGS = (

View File

@@ -1783,7 +1783,7 @@
93D39CF7DB942C69D1C5D6BE /* mpw-util.h */, 93D39CF7DB942C69D1C5D6BE /* mpw-util.h */,
); );
name = core; name = core;
path = ../platform-independent/c/core/src; path = "../platform-independent/c/core/src";
sourceTree = "<group>"; sourceTree = "<group>";
}; };
DA0CC4F41EAB99BA009A8ED9 /* Resources */ = { DA0CC4F41EAB99BA009A8ED9 /* Resources */ = {
@@ -4309,12 +4309,6 @@
GCC_WARN_UNUSED_LABEL = YES; GCC_WARN_UNUSED_LABEL = YES;
GCC_WARN_UNUSED_VALUE = YES; GCC_WARN_UNUSED_VALUE = YES;
GCC_WARN_UNUSED_VARIABLE = YES; GCC_WARN_UNUSED_VARIABLE = YES;
HEADER_SEARCH_PATHS = (
"\"$(BUILD_ROOT)/../IntermediateBuildFilesPath/UninstalledProducts/$(PLATFORM_NAME)/include\"",
"\"$(PROJECT_DIR)/External/libsodium/libsodium-ios/include\"",
"\"$(PROJECT_DIR)/External/libjson-c/libjson-c-ios/include\"",
"$(inherited)",
);
IPHONEOS_DEPLOYMENT_TARGET = 8.0; IPHONEOS_DEPLOYMENT_TARGET = 8.0;
MTL_ENABLE_DEBUG_INFO = NO; MTL_ENABLE_DEBUG_INFO = NO;
OTHER_LDFLAGS = "-ObjC"; OTHER_LDFLAGS = "-ObjC";
@@ -4348,13 +4342,18 @@
GCC_C_LANGUAGE_STANDARD = c11; GCC_C_LANGUAGE_STANDARD = c11;
GCC_PREFIX_HEADER = "Source/MasterPassword-Prefix.pch"; GCC_PREFIX_HEADER = "Source/MasterPassword-Prefix.pch";
GCC_WARN_ABOUT_DEPRECATED_FUNCTIONS = NO; GCC_WARN_ABOUT_DEPRECATED_FUNCTIONS = NO;
HEADER_SEARCH_PATHS = (
"$(inherited)",
"\"$(PROJECT_DIR)/../lib/libsodium/build-ios~/out/include\"",
"\"$(PROJECT_DIR)/../lib/libjson-c/build-ios~/out/include\"",
);
INFOPLIST_FILE = "Source/iOS/MasterPassword-Info.plist"; INFOPLIST_FILE = "Source/iOS/MasterPassword-Info.plist";
IPHONEOS_DEPLOYMENT_TARGET = 9.0; IPHONEOS_DEPLOYMENT_TARGET = 9.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
LIBRARY_SEARCH_PATHS = ( LIBRARY_SEARCH_PATHS = (
"$(inherited)", "$(inherited)",
"$(PROJECT_DIR)/../lib/libsodium/build-ios~/out/lib", "\"$(PROJECT_DIR)/../lib/libsodium/build-ios~/out/lib\"",
"$(PROJECT_DIR)/../lib/libjson-c/build-ios~/out/lib", "\"$(PROJECT_DIR)/../lib/libjson-c/build-ios~/out/lib\"",
); );
OTHER_CFLAGS = ( OTHER_CFLAGS = (
"-DMPW_SODIUM=1", "-DMPW_SODIUM=1",
@@ -4524,12 +4523,6 @@
GCC_WARN_UNUSED_LABEL = YES; GCC_WARN_UNUSED_LABEL = YES;
GCC_WARN_UNUSED_VALUE = YES; GCC_WARN_UNUSED_VALUE = YES;
GCC_WARN_UNUSED_VARIABLE = YES; GCC_WARN_UNUSED_VARIABLE = YES;
HEADER_SEARCH_PATHS = (
"\"$(BUILD_ROOT)/../IntermediateBuildFilesPath/UninstalledProducts/$(PLATFORM_NAME)/include\"",
"\"$(PROJECT_DIR)/External/libsodium/libsodium-ios/include\"",
"\"$(PROJECT_DIR)/External/libjson-c/libjson-c-ios/include\"",
"$(inherited)",
);
IPHONEOS_DEPLOYMENT_TARGET = 8.0; IPHONEOS_DEPLOYMENT_TARGET = 8.0;
MTL_ENABLE_DEBUG_INFO = YES; MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES; ONLY_ACTIVE_ARCH = YES;
@@ -4635,12 +4628,6 @@
GCC_WARN_UNUSED_LABEL = YES; GCC_WARN_UNUSED_LABEL = YES;
GCC_WARN_UNUSED_VALUE = YES; GCC_WARN_UNUSED_VALUE = YES;
GCC_WARN_UNUSED_VARIABLE = YES; GCC_WARN_UNUSED_VARIABLE = YES;
HEADER_SEARCH_PATHS = (
"\"$(BUILD_ROOT)/../IntermediateBuildFilesPath/UninstalledProducts/$(PLATFORM_NAME)/include\"",
"\"$(PROJECT_DIR)/External/libsodium/libsodium-ios/include\"",
"\"$(PROJECT_DIR)/External/libjson-c/libjson-c-ios/include\"",
"$(inherited)",
);
IPHONEOS_DEPLOYMENT_TARGET = 8.0; IPHONEOS_DEPLOYMENT_TARGET = 8.0;
MTL_ENABLE_DEBUG_INFO = NO; MTL_ENABLE_DEBUG_INFO = NO;
OTHER_LDFLAGS = "-ObjC"; OTHER_LDFLAGS = "-ObjC";
@@ -4673,13 +4660,18 @@
GCC_C_LANGUAGE_STANDARD = c11; GCC_C_LANGUAGE_STANDARD = c11;
GCC_PREFIX_HEADER = "Source/MasterPassword-Prefix.pch"; GCC_PREFIX_HEADER = "Source/MasterPassword-Prefix.pch";
GCC_WARN_ABOUT_DEPRECATED_FUNCTIONS = NO; GCC_WARN_ABOUT_DEPRECATED_FUNCTIONS = NO;
HEADER_SEARCH_PATHS = (
"$(inherited)",
"\"$(PROJECT_DIR)/../lib/libsodium/build-ios~/out/include\"",
"\"$(PROJECT_DIR)/../lib/libjson-c/build-ios~/out/include\"",
);
INFOPLIST_FILE = "Source/iOS/MasterPassword-Info.plist"; INFOPLIST_FILE = "Source/iOS/MasterPassword-Info.plist";
IPHONEOS_DEPLOYMENT_TARGET = 9.0; IPHONEOS_DEPLOYMENT_TARGET = 9.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
LIBRARY_SEARCH_PATHS = ( LIBRARY_SEARCH_PATHS = (
"$(inherited)", "$(inherited)",
"$(PROJECT_DIR)/../lib/libsodium/build-ios~/out/lib", "\"$(PROJECT_DIR)/../lib/libsodium/build-ios~/out/lib\"",
"$(PROJECT_DIR)/../lib/libjson-c/build-ios~/out/lib", "\"$(PROJECT_DIR)/../lib/libjson-c/build-ios~/out/lib\"",
); );
OTHER_CFLAGS = ( OTHER_CFLAGS = (
"-DMPW_SODIUM=1", "-DMPW_SODIUM=1",
@@ -4712,13 +4704,18 @@
GCC_C_LANGUAGE_STANDARD = c11; GCC_C_LANGUAGE_STANDARD = c11;
GCC_PREFIX_HEADER = "Source/MasterPassword-Prefix.pch"; GCC_PREFIX_HEADER = "Source/MasterPassword-Prefix.pch";
GCC_WARN_ABOUT_DEPRECATED_FUNCTIONS = NO; GCC_WARN_ABOUT_DEPRECATED_FUNCTIONS = NO;
HEADER_SEARCH_PATHS = (
"$(inherited)",
"\"$(PROJECT_DIR)/../lib/libsodium/build-ios~/out/include\"",
"\"$(PROJECT_DIR)/../lib/libjson-c/build-ios~/out/include\"",
);
INFOPLIST_FILE = "Source/iOS/MasterPassword-Info.plist"; INFOPLIST_FILE = "Source/iOS/MasterPassword-Info.plist";
IPHONEOS_DEPLOYMENT_TARGET = 9.0; IPHONEOS_DEPLOYMENT_TARGET = 9.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
LIBRARY_SEARCH_PATHS = ( LIBRARY_SEARCH_PATHS = (
"$(inherited)", "$(inherited)",
"$(PROJECT_DIR)/../lib/libsodium/build-ios~/out/lib", "\"$(PROJECT_DIR)/../lib/libsodium/build-ios~/out/lib\"",
"$(PROJECT_DIR)/../lib/libjson-c/build-ios~/out/lib", "\"$(PROJECT_DIR)/../lib/libjson-c/build-ios~/out/lib\"",
); );
OTHER_CFLAGS = ( OTHER_CFLAGS = (
"-DMPW_SODIUM=1", "-DMPW_SODIUM=1",

View File

@@ -1224,7 +1224,7 @@
DA1C7ABC1F1A8F6E009A3551 /* mpw-tests.c */, DA1C7ABC1F1A8F6E009A3551 /* mpw-tests.c */,
); );
name = cli; name = cli;
path = "../../platform-independent/cli-c/cli"; path = ../../cli/src;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
DA2508F819513C1400AC23F1 /* Other Frameworks */ = { DA2508F819513C1400AC23F1 /* Other Frameworks */ = {
@@ -1820,7 +1820,7 @@
DA6773C61A4746AF004F356A /* mpw-util.h */, DA6773C61A4746AF004F356A /* mpw-util.h */,
); );
name = core; name = core;
path = ../platform-independent/c/core/src; path = "../platform-independent/c/core/src";
sourceTree = "<group>"; sourceTree = "<group>";
}; };
DA89D4E51A51E53100AC64D7 /* Pearl-Cocoa */ = { DA89D4E51A51E53100AC64D7 /* Pearl-Cocoa */ = {
@@ -2992,15 +2992,8 @@
GCC_WARN_UNUSED_LABEL = YES; GCC_WARN_UNUSED_LABEL = YES;
GCC_WARN_UNUSED_VALUE = YES; GCC_WARN_UNUSED_VALUE = YES;
GCC_WARN_UNUSED_VARIABLE = YES; GCC_WARN_UNUSED_VARIABLE = YES;
HEADER_SEARCH_PATHS = (
"\"$(BUILD_ROOT)/../IntermediateBuildFilesPath/UninstalledProducts/$(PLATFORM_NAME)/include\"",
"\"$(PROJECT_DIR)/External/libsodium/libsodium-osx/include\"",
"\"$(PROJECT_DIR)/External/libjson-c/libjson-c-osx/include\"",
"$(inherited)",
);
LD_DYLIB_INSTALL_NAME = "@rpath/$(EXECUTABLE_PATH)"; LD_DYLIB_INSTALL_NAME = "@rpath/$(EXECUTABLE_PATH)";
LD_RUNPATH_SEARCH_PATHS = "@loader_path/../Frameworks"; LD_RUNPATH_SEARCH_PATHS = "@loader_path/../Frameworks";
LIBRARY_SEARCH_PATHS = "$(inherit)";
MACOSX_DEPLOYMENT_TARGET = 10.11; MACOSX_DEPLOYMENT_TARGET = 10.11;
MTL_ENABLE_DEBUG_INFO = NO; MTL_ENABLE_DEBUG_INFO = NO;
OTHER_LDFLAGS = "-ObjC"; OTHER_LDFLAGS = "-ObjC";
@@ -3028,12 +3021,17 @@
); );
GCC_PREFIX_HEADER = "Source/MasterPassword-Prefix.pch"; GCC_PREFIX_HEADER = "Source/MasterPassword-Prefix.pch";
GCC_WARN_ABOUT_DEPRECATED_FUNCTIONS = NO; GCC_WARN_ABOUT_DEPRECATED_FUNCTIONS = NO;
HEADER_SEARCH_PATHS = (
"$(inherited)",
"\"$(PROJECT_DIR)/../lib/libsodium/build-macos~/out/include\"",
"\"$(PROJECT_DIR)/../lib/libjson-c/build-macos~/out/include\"",
);
INFOPLIST_FILE = "Source/Mac/MasterPassword-Info.plist"; INFOPLIST_FILE = "Source/Mac/MasterPassword-Info.plist";
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks";
LIBRARY_SEARCH_PATHS = ( LIBRARY_SEARCH_PATHS = (
"$(inherited)", "$(inherited)",
"$(PROJECT_DIR)/../lib/libsodium/build-macos~/out/lib", "\"$(PROJECT_DIR)/../lib/libsodium/build-macos~/out/lib\"",
"$(PROJECT_DIR)/../lib/libjson-c/build-macos~/out/lib", "\"$(PROJECT_DIR)/../lib/libjson-c/build-macos~/out/lib\"",
); );
OTHER_CFLAGS = ( OTHER_CFLAGS = (
"-DMPW_SODIUM=1", "-DMPW_SODIUM=1",
@@ -3084,13 +3082,15 @@
buildSettings = { buildSettings = {
CLANG_WARN_DOCUMENTATION_COMMENTS = NO; CLANG_WARN_DOCUMENTATION_COMMENTS = NO;
HEADER_SEARCH_PATHS = ( HEADER_SEARCH_PATHS = (
"\"$(BUILD_ROOT)/../IntermediateBuildFilesPath/UninstalledProducts/$(PLATFORM_NAME)/include\"",
/usr/include/libxml2,
"$(inherited)", "$(inherited)",
"\"$(PROJECT_DIR)/../lib/libsodium/build-macos~/out/include\"",
"\"$(PROJECT_DIR)/../lib/libjson-c/build-macos~/out/include\"",
/usr/include/libxml2,
); );
LIBRARY_SEARCH_PATHS = ( LIBRARY_SEARCH_PATHS = (
"$(inherited)", "$(inherited)",
"$(PROJECT_DIR)/../lib/libsodium/build-macos~/out/lib", "\"$(PROJECT_DIR)/../lib/libsodium/build-macos~/out/lib\"",
"\"$(PROJECT_DIR)/../lib/libjson-c/build-macos~/out/lib\"",
); );
OTHER_CFLAGS = ( OTHER_CFLAGS = (
"-DMPW_SODIUM=1", "-DMPW_SODIUM=1",
@@ -3103,10 +3103,15 @@
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
buildSettings = { buildSettings = {
CLANG_WARN_DOCUMENTATION_COMMENTS = NO; CLANG_WARN_DOCUMENTATION_COMMENTS = NO;
HEADER_SEARCH_PATHS = (
"$(inherited)",
"\"$(PROJECT_DIR)/../lib/libsodium/build-macos~/out/include\"",
"\"$(PROJECT_DIR)/../lib/libjson-c/build-macos~/out/include\"",
);
LIBRARY_SEARCH_PATHS = ( LIBRARY_SEARCH_PATHS = (
"$(inherited)", "$(inherited)",
"$(PROJECT_DIR)/../lib/libsodium/build-macos~/out/lib", "\"$(PROJECT_DIR)/../lib/libsodium/build-macos~/out/lib\"",
"$(PROJECT_DIR)/../lib/libjson-c/build-macos~/out/lib", "\"$(PROJECT_DIR)/../lib/libjson-c/build-macos~/out/lib\"",
); );
OTHER_CFLAGS = ( OTHER_CFLAGS = (
"-DMPW_SODIUM=1", "-DMPW_SODIUM=1",
@@ -3122,10 +3127,15 @@
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
buildSettings = { buildSettings = {
CLANG_WARN_DOCUMENTATION_COMMENTS = NO; CLANG_WARN_DOCUMENTATION_COMMENTS = NO;
HEADER_SEARCH_PATHS = (
"$(inherited)",
"\"$(PROJECT_DIR)/../lib/libsodium/build-macos~/out/include\"",
"\"$(PROJECT_DIR)/../lib/libjson-c/build-macos~/out/include\"",
);
LIBRARY_SEARCH_PATHS = ( LIBRARY_SEARCH_PATHS = (
"$(inherited)", "$(inherited)",
"$(PROJECT_DIR)/../lib/libsodium/build-macos~/out/lib", "\"$(PROJECT_DIR)/../lib/libsodium/build-macos~/out/lib\"",
"$(PROJECT_DIR)/../lib/libjson-c/build-macos~/out/lib", "\"$(PROJECT_DIR)/../lib/libjson-c/build-macos~/out/lib\"",
); );
OTHER_CFLAGS = ( OTHER_CFLAGS = (
"-DMPW_SODIUM=1", "-DMPW_SODIUM=1",
@@ -3141,10 +3151,15 @@
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
buildSettings = { buildSettings = {
CLANG_WARN_DOCUMENTATION_COMMENTS = NO; CLANG_WARN_DOCUMENTATION_COMMENTS = NO;
HEADER_SEARCH_PATHS = (
"$(inherited)",
"\"$(PROJECT_DIR)/../lib/libsodium/build-macos~/out/include\"",
"\"$(PROJECT_DIR)/../lib/libjson-c/build-macos~/out/include\"",
);
LIBRARY_SEARCH_PATHS = ( LIBRARY_SEARCH_PATHS = (
"$(inherited)", "$(inherited)",
"$(PROJECT_DIR)/../lib/libsodium/build-macos~/out/lib", "\"$(PROJECT_DIR)/../lib/libsodium/build-macos~/out/lib\"",
"$(PROJECT_DIR)/../lib/libjson-c/build-macos~/out/lib", "\"$(PROJECT_DIR)/../lib/libjson-c/build-macos~/out/lib\"",
); );
OTHER_CFLAGS = ( OTHER_CFLAGS = (
"-DMPW_SODIUM=1", "-DMPW_SODIUM=1",
@@ -3160,9 +3175,15 @@
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
buildSettings = { buildSettings = {
CLANG_WARN_DOCUMENTATION_COMMENTS = NO; CLANG_WARN_DOCUMENTATION_COMMENTS = NO;
HEADER_SEARCH_PATHS = (
"$(inherited)",
"\"$(PROJECT_DIR)/../lib/libsodium/build-macos~/out/include\"",
"\"$(PROJECT_DIR)/../lib/libjson-c/build-macos~/out/include\"",
);
LIBRARY_SEARCH_PATHS = ( LIBRARY_SEARCH_PATHS = (
"$(inherited)", "$(inherited)",
"$(PROJECT_DIR)/../lib/libsodium/build-macos~/out/lib", "\"$(PROJECT_DIR)/../lib/libsodium/build-macos~/out/lib\"",
"\"$(PROJECT_DIR)/../lib/libjson-c/build-macos~/out/lib\"",
); );
OTHER_CFLAGS = ( OTHER_CFLAGS = (
"-DMPW_SODIUM=1", "-DMPW_SODIUM=1",
@@ -3176,9 +3197,15 @@
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
buildSettings = { buildSettings = {
CLANG_WARN_DOCUMENTATION_COMMENTS = NO; CLANG_WARN_DOCUMENTATION_COMMENTS = NO;
HEADER_SEARCH_PATHS = (
"$(inherited)",
"\"$(PROJECT_DIR)/../lib/libsodium/build-macos~/out/include\"",
"\"$(PROJECT_DIR)/../lib/libjson-c/build-macos~/out/include\"",
);
LIBRARY_SEARCH_PATHS = ( LIBRARY_SEARCH_PATHS = (
"$(inherited)", "$(inherited)",
"$(PROJECT_DIR)/../lib/libsodium/build-macos~/out/lib", "\"$(PROJECT_DIR)/../lib/libsodium/build-macos~/out/lib\"",
"\"$(PROJECT_DIR)/../lib/libjson-c/build-macos~/out/lib\"",
); );
OTHER_CFLAGS = ( OTHER_CFLAGS = (
"-DMPW_SODIUM=1", "-DMPW_SODIUM=1",
@@ -3192,9 +3219,15 @@
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
buildSettings = { buildSettings = {
CLANG_WARN_DOCUMENTATION_COMMENTS = NO; CLANG_WARN_DOCUMENTATION_COMMENTS = NO;
HEADER_SEARCH_PATHS = (
"$(inherited)",
"\"$(PROJECT_DIR)/../lib/libsodium/build-macos~/out/include\"",
"\"$(PROJECT_DIR)/../lib/libjson-c/build-macos~/out/include\"",
);
LIBRARY_SEARCH_PATHS = ( LIBRARY_SEARCH_PATHS = (
"$(inherited)", "$(inherited)",
"$(PROJECT_DIR)/../lib/libsodium/build-macos~/out/lib", "\"$(PROJECT_DIR)/../lib/libsodium/build-macos~/out/lib\"",
"\"$(PROJECT_DIR)/../lib/libjson-c/build-macos~/out/lib\"",
); );
OTHER_CFLAGS = ( OTHER_CFLAGS = (
"-DMPW_SODIUM=1", "-DMPW_SODIUM=1",
@@ -3348,15 +3381,8 @@
GCC_WARN_UNUSED_LABEL = YES; GCC_WARN_UNUSED_LABEL = YES;
GCC_WARN_UNUSED_VALUE = YES; GCC_WARN_UNUSED_VALUE = YES;
GCC_WARN_UNUSED_VARIABLE = YES; GCC_WARN_UNUSED_VARIABLE = YES;
HEADER_SEARCH_PATHS = (
"\"$(BUILD_ROOT)/../IntermediateBuildFilesPath/UninstalledProducts/$(PLATFORM_NAME)/include\"",
"\"$(PROJECT_DIR)/External/libsodium/libsodium-osx/include\"",
"\"$(PROJECT_DIR)/External/libjson-c/libjson-c-osx/include\"",
"$(inherited)",
);
LD_DYLIB_INSTALL_NAME = "@rpath/$(EXECUTABLE_PATH)"; LD_DYLIB_INSTALL_NAME = "@rpath/$(EXECUTABLE_PATH)";
LD_RUNPATH_SEARCH_PATHS = "@loader_path/../Frameworks"; LD_RUNPATH_SEARCH_PATHS = "@loader_path/../Frameworks";
LIBRARY_SEARCH_PATHS = "$(inherit)";
MACOSX_DEPLOYMENT_TARGET = 10.11; MACOSX_DEPLOYMENT_TARGET = 10.11;
MTL_ENABLE_DEBUG_INFO = YES; MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES; ONLY_ACTIVE_ARCH = YES;
@@ -3455,15 +3481,8 @@
GCC_WARN_UNUSED_LABEL = YES; GCC_WARN_UNUSED_LABEL = YES;
GCC_WARN_UNUSED_VALUE = YES; GCC_WARN_UNUSED_VALUE = YES;
GCC_WARN_UNUSED_VARIABLE = YES; GCC_WARN_UNUSED_VARIABLE = YES;
HEADER_SEARCH_PATHS = (
"\"$(BUILD_ROOT)/../IntermediateBuildFilesPath/UninstalledProducts/$(PLATFORM_NAME)/include\"",
"\"$(PROJECT_DIR)/External/libsodium/libsodium-osx/include\"",
"\"$(PROJECT_DIR)/External/libjson-c/libjson-c-osx/include\"",
"$(inherited)",
);
LD_DYLIB_INSTALL_NAME = "@rpath/$(EXECUTABLE_PATH)"; LD_DYLIB_INSTALL_NAME = "@rpath/$(EXECUTABLE_PATH)";
LD_RUNPATH_SEARCH_PATHS = "@loader_path/../Frameworks"; LD_RUNPATH_SEARCH_PATHS = "@loader_path/../Frameworks";
LIBRARY_SEARCH_PATHS = "$(inherit)";
MACOSX_DEPLOYMENT_TARGET = 10.11; MACOSX_DEPLOYMENT_TARGET = 10.11;
MTL_ENABLE_DEBUG_INFO = NO; MTL_ENABLE_DEBUG_INFO = NO;
OTHER_LDFLAGS = "-ObjC"; OTHER_LDFLAGS = "-ObjC";
@@ -3491,12 +3510,17 @@
); );
GCC_PREFIX_HEADER = "Source/MasterPassword-Prefix.pch"; GCC_PREFIX_HEADER = "Source/MasterPassword-Prefix.pch";
GCC_WARN_ABOUT_DEPRECATED_FUNCTIONS = NO; GCC_WARN_ABOUT_DEPRECATED_FUNCTIONS = NO;
HEADER_SEARCH_PATHS = (
"$(inherited)",
"\"$(PROJECT_DIR)/../lib/libsodium/build-macos~/out/include\"",
"\"$(PROJECT_DIR)/../lib/libjson-c/build-macos~/out/include\"",
);
INFOPLIST_FILE = "Source/Mac/MasterPassword-Info.plist"; INFOPLIST_FILE = "Source/Mac/MasterPassword-Info.plist";
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks";
LIBRARY_SEARCH_PATHS = ( LIBRARY_SEARCH_PATHS = (
"$(inherited)", "$(inherited)",
"$(PROJECT_DIR)/../lib/libsodium/build-macos~/out/lib", "\"$(PROJECT_DIR)/../lib/libsodium/build-macos~/out/lib\"",
"$(PROJECT_DIR)/../lib/libjson-c/build-macos~/out/lib", "\"$(PROJECT_DIR)/../lib/libjson-c/build-macos~/out/lib\"",
); );
OTHER_CFLAGS = ( OTHER_CFLAGS = (
"-DMPW_SODIUM=1", "-DMPW_SODIUM=1",
@@ -3523,12 +3547,17 @@
); );
GCC_PREFIX_HEADER = "Source/MasterPassword-Prefix.pch"; GCC_PREFIX_HEADER = "Source/MasterPassword-Prefix.pch";
GCC_WARN_ABOUT_DEPRECATED_FUNCTIONS = NO; GCC_WARN_ABOUT_DEPRECATED_FUNCTIONS = NO;
HEADER_SEARCH_PATHS = (
"$(inherited)",
"\"$(PROJECT_DIR)/../lib/libsodium/build-macos~/out/include\"",
"\"$(PROJECT_DIR)/../lib/libjson-c/build-macos~/out/include\"",
);
INFOPLIST_FILE = "Source/Mac/MasterPassword-Info.plist"; INFOPLIST_FILE = "Source/Mac/MasterPassword-Info.plist";
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks";
LIBRARY_SEARCH_PATHS = ( LIBRARY_SEARCH_PATHS = (
"$(inherited)", "$(inherited)",
"$(PROJECT_DIR)/../lib/libsodium/build-macos~/out/lib", "\"$(PROJECT_DIR)/../lib/libsodium/build-macos~/out/lib\"",
"$(PROJECT_DIR)/../lib/libjson-c/build-macos~/out/lib", "\"$(PROJECT_DIR)/../lib/libjson-c/build-macos~/out/lib\"",
); );
OTHER_CFLAGS = ( OTHER_CFLAGS = (
"-DMPW_SODIUM=1", "-DMPW_SODIUM=1",
@@ -3545,13 +3574,15 @@
buildSettings = { buildSettings = {
CLANG_WARN_DOCUMENTATION_COMMENTS = NO; CLANG_WARN_DOCUMENTATION_COMMENTS = NO;
HEADER_SEARCH_PATHS = ( HEADER_SEARCH_PATHS = (
"\"$(BUILD_ROOT)/../IntermediateBuildFilesPath/UninstalledProducts/$(PLATFORM_NAME)/include\"",
/usr/include/libxml2,
"$(inherited)", "$(inherited)",
"\"$(PROJECT_DIR)/../lib/libsodium/build-macos~/out/include\"",
"\"$(PROJECT_DIR)/../lib/libjson-c/build-macos~/out/include\"",
/usr/include/libxml2,
); );
LIBRARY_SEARCH_PATHS = ( LIBRARY_SEARCH_PATHS = (
"$(inherited)", "$(inherited)",
"$(PROJECT_DIR)/../lib/libsodium/build-macos~/out/lib", "\"$(PROJECT_DIR)/../lib/libsodium/build-macos~/out/lib\"",
"\"$(PROJECT_DIR)/../lib/libjson-c/build-macos~/out/lib\"",
); );
OTHER_CFLAGS = ( OTHER_CFLAGS = (
"-DMPW_SODIUM=1", "-DMPW_SODIUM=1",
@@ -3565,13 +3596,15 @@
buildSettings = { buildSettings = {
CLANG_WARN_DOCUMENTATION_COMMENTS = NO; CLANG_WARN_DOCUMENTATION_COMMENTS = NO;
HEADER_SEARCH_PATHS = ( HEADER_SEARCH_PATHS = (
"\"$(BUILD_ROOT)/../IntermediateBuildFilesPath/UninstalledProducts/$(PLATFORM_NAME)/include\"",
/usr/include/libxml2,
"$(inherited)", "$(inherited)",
"\"$(PROJECT_DIR)/../lib/libsodium/build-macos~/out/include\"",
"\"$(PROJECT_DIR)/../lib/libjson-c/build-macos~/out/include\"",
/usr/include/libxml2,
); );
LIBRARY_SEARCH_PATHS = ( LIBRARY_SEARCH_PATHS = (
"$(inherited)", "$(inherited)",
"$(PROJECT_DIR)/../lib/libsodium/build-macos~/out/lib", "\"$(PROJECT_DIR)/../lib/libsodium/build-macos~/out/lib\"",
"\"$(PROJECT_DIR)/../lib/libjson-c/build-macos~/out/lib\"",
); );
OTHER_CFLAGS = ( OTHER_CFLAGS = (
"-DMPW_SODIUM=1", "-DMPW_SODIUM=1",

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

@@ -219,7 +219,7 @@
- (IBAction)sourceButton:(id)sender { - (IBAction)sourceButton:(id)sender {
[[self dismissPopup].navigationController performSegueWithIdentifier:@"web" sender: [[self dismissPopup].navigationController performSegueWithIdentifier:@"web" sender:
[NSURL URLWithString:@"https://github.com/Lyndir/MasterPassword/"]]; [NSURL URLWithString:@"https://gitlab.com/MasterPassword/MasterPassword/"]];
} }
- (IBAction)thanksButton:(id)sender { - (IBAction)thanksButton:(id)sender {

View File

@@ -9,71 +9,108 @@ plugins {
description = 'Master Password Algorithm Implementation' description = 'Master Password Algorithm Implementation'
artifacts { artifacts {
'default' task( type: Zip, "archive" ) { 'default' task( type: Zip, 'archive' ) {
// TODO: exclude lib files that are produced by the build.
from 'lib'
components.withType( ComponentWithRuntimeFile ) { components.withType( ComponentWithRuntimeFile ) {
if (isOptimized()) { if (optimized)
from getRuntimeFile() from runtimeFile, {
into standardOperatingSystem( linkTask.get().targetPlatform.get() ) + '/' + into standardOperatingSystem( targetPlatform ) + '/' + standardArchitecture( targetPlatform )
standardArchitecture( linkTask.get().targetPlatform.get() ) }
}
} }
} }
} }
library { library {
baseName.set( "mpw" ) baseName.set( 'mpw' )
linkage.set( [Linkage.STATIC, Linkage.SHARED] ) linkage.set( [Linkage.SHARED] )
// Reconfigure the toolchain from C++ to C. // Reconfigure the toolchain from C++ to C.
toolChains { toolChains {
withType( GccCompatibleToolChain ) { withType( VisualCpp ) {
eachPlatform { eachPlatform {
cppCompiler.withArguments { addAll( ["-x", "c", "-std=c11", "-Werror", "-DMPW_SODIUM=1"] ) } cppCompiler.withArguments { addAll( ['/TC', '/MT', '/Ox', '/DMPW_SODIUM=1', '/DSODIUM_STATIC', '/DSODIUM_EXPORT='] ) }
}
}
withType( GccCompatibleToolChain ) {
eachPlatform {
cppCompiler.withArguments { addAll( ['-x', 'c', '-O3', '-Werror', '-DMPW_SODIUM=1'] ) }
} }
} }
}
components.withType( CppComponent ) {
cppSource.from fileTree( dir: "src", include: "**/*.c" )
} }
// Cross-compile for these host platforms. // Cross-compile for these host platforms.
// TODO: Cross-compiling, blocked by: https://github.com/gradle/gradle-native/issues/169 - CppLibraryPlugin.java:163 // TODO: Cross-compiling, blocked by: https://github.com/gradle/gradle-native/issues/169 - CppLibraryPlugin.java:163
operatingSystems.set( [objects.named( OperatingSystemFamily, OperatingSystemFamily.WINDOWS ), operatingSystems.set( [objects.named( OperatingSystemFamily, OperatingSystemFamily.LINUX ),
objects.named( OperatingSystemFamily, OperatingSystemFamily.LINUX ), objects.named( OperatingSystemFamily, OperatingSystemFamily.MAC_OS ),
objects.named( OperatingSystemFamily, OperatingSystemFamily.MAC_OS )] ) objects.named( OperatingSystemFamily, OperatingSystemFamily.WINDOWS )] )
binaries.configureEach { components.withType( CppComponent ) {
// Resolve a standard name for the platform. cppSource.from fileTree( 'src' )
def platform = standardOperatingSystem( targetPlatform )
project.dependencies { privateHeaders {
// Depend on JDK for JNI support. // JDK for JNI support.
add( includePathConfiguration.name, from files( new File( Jvm.current().javaHome, 'include' ) ) { first().eachDir { from it } }
files( new File( Jvm.current().javaHome, "include" ) ) { first().eachDir { from it } } ) }
// Depend on libsodium from `lib`; run `lib/bin/build_libsodium-${platform}` first. binaries.whenElementFinalized {
add( includePathConfiguration.name, project.dependencies {
files( "../../../lib/libsodium/build-${platform}~/out/include" ) ) def system = standardOperatingSystem( targetPlatform )
add( linkLibraries.name,
fileTree( "../../../lib/libsodium/build-${platform}~/out/lib" ) ) // libsodium
archive.dependsOn project.tasks.maybeCreate( "build_libsodium-${system}", Exec ).configure {
commandLine 'bash', "$rootDir/../lib/bin/build_libsodium-${system}"
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 ).configure {
commandLine 'bash', "$rootDir/../lib/bin/build_libsodium-${system}", 'clean'
}
// libjson-c
/*archive.dependsOn project.tasks.maybeCreate( "build_libjson-c-${system}", Exec ).configure {
commandLine 'bash', "$rootDir/../lib/bin/build_libjson-c-${system}"
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 ).configure {
commandLine 'bash', "$rootDir/../lib/bin/build_libjson-c-${system}", 'clean'
}*/
}
} }
} }
} }
static String standardOperatingSystem(NativePlatform platform) { static String standardOperatingSystem(NativePlatform platform) {
OperatingSystem os = platform.getOperatingSystem() OperatingSystem os = platform.getOperatingSystem()
if (os.isWindows()) { if (os.isWindows())
return OperatingSystemFamily.WINDOWS return OperatingSystemFamily.WINDOWS
} else if (os.isLinux()) {
return OperatingSystemFamily.LINUX
} else if (os.isMacOsX()) {
return OperatingSystemFamily.MAC_OS
}
return os.name.toLowerCase() else if (os.isMacOsX())
return OperatingSystemFamily.MAC_OS
else if (os.isLinux())
return OperatingSystemFamily.LINUX
// Other systems will need to use a Linux compatibility layer.
return OperatingSystemFamily.LINUX
} }
static String standardArchitecture(NativePlatform platform) { static String standardArchitecture(NativePlatform platform) {
Architecture arch = platform.getArchitecture() Architecture arch = platform.getArchitecture()
return arch.name.toLowerCase().replaceAll( "-", "_" ) if (arch.isArm())
return 'arm'
else if (arch.name.toLowerCase( Locale.ROOT ).startsWith( 'arm' ))
return 'arm64'
else if (arch.isAmd64())
return 'x86_64'
else if (arch.isI386())
return 'x86'
// Other systems will need to be compatible with the x86 architecture.
return 'x86'
} }

Binary file not shown.

Binary file not shown.

Binary file not shown.

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,17 +35,21 @@ 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 parsing timezone into tm_gmtoff // 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,
&tm.tm_hour, &tm.tm_min, &tm.tm_sec ) == 6) { &tm.tm_hour, &tm.tm_min, &tm.tm_sec ) == 6) {
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
return mktime( &tm );
// mktime interprets tm as being local, we need to offset back to UTC (timegm/tm_gmtoff are non-standard).
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

@@ -206,7 +206,7 @@ static bool mpw_marshal_write_flat(
if (strftime( dateString, sizeof( dateString ), "%FT%TZ", gmtime( &site->lastUsed ) )) if (strftime( dateString, sizeof( dateString ), "%FT%TZ", gmtime( &site->lastUsed ) ))
mpw_string_pushf( out, "%s %8ld %lu:%lu:%lu %25s\t%25s\t%s\n", mpw_string_pushf( out, "%s %8ld %lu:%lu:%lu %25s\t%25s\t%s\n",
dateString, (long)site->uses, (long)site->type, (long)site->algorithm, (long)site->counter, dateString, (long)site->uses, (long)site->type, (long)site->algorithm, (long)site->counter,
loginContent?: "", site->name, content?: "" ); loginContent? loginContent: "", site->name, content? content: "" );
mpw_free_strings( &content, &loginContent, NULL ); mpw_free_strings( &content, &loginContent, NULL );
} }
mpw_free( &masterKey, MPMasterKeySize ); mpw_free( &masterKey, MPMasterKeySize );
@@ -304,39 +304,43 @@ static bool mpw_marshal_write_json(
if (strftime( dateString, sizeof( dateString ), "%FT%TZ", gmtime( &site->lastUsed ) )) if (strftime( dateString, sizeof( dateString ), "%FT%TZ", gmtime( &site->lastUsed ) ))
json_object_object_add( json_site, "last_used", json_object_new_string( dateString ) ); json_object_object_add( json_site, "last_used", json_object_new_string( dateString ) );
json_object *json_site_questions = json_object_new_object(); if (site->questions_count) {
json_object_object_add( json_site, "questions", json_site_questions ); json_object *json_site_questions = json_object_new_object();
for (size_t q = 0; q < site->questions_count; ++q) { json_object_object_add( json_site, "questions", json_site_questions );
MPMarshalledQuestion *question = &site->questions[q]; for (size_t q = 0; q < site->questions_count; ++q) {
if (!question->keyword) MPMarshalledQuestion *question = &site->questions[q];
continue; if (!question->keyword)
continue;
json_object *json_site_question = json_object_new_object(); json_object *json_site_question = json_object_new_object();
json_object_object_add( json_site_questions, question->keyword, json_site_question ); json_object_object_add( json_site_questions, question->keyword, json_site_question );
json_object_object_add( json_site_question, "type", json_object_new_int( (int32_t)question->type ) ); json_object_object_add( json_site_question, "type", json_object_new_int( (int32_t)question->type ) );
if (!user->redacted) { if (!user->redacted) {
// Clear Text // Clear Text
const char *answerContent = mpw_siteResult( masterKey, site->name, MPCounterValueInitial, const char *answerContent = mpw_siteResult( masterKey, site->name, MPCounterValueInitial,
MPKeyPurposeRecovery, question->keyword, question->type, question->content, site->algorithm ); MPKeyPurposeRecovery, question->keyword, question->type, question->content, site->algorithm );
json_object_object_add( json_site_question, "answer", json_object_new_string( answerContent ) ); json_object_object_add( json_site_question, "answer", json_object_new_string( answerContent ) );
} }
else { else {
// Redacted // Redacted
if (site->type & MPSiteFeatureExportContent && question->content && strlen( question->content )) if (site->type & MPSiteFeatureExportContent && question->content && strlen( question->content ))
json_object_object_add( json_site_question, "answer", json_object_new_string( question->content ) ); json_object_object_add( json_site_question, "answer", json_object_new_string( question->content ) );
}
} }
} }
json_object *json_site_mpw = json_object_new_object(); json_object *json_site_mpw = json_object_new_object();
json_object_object_add( json_site, "_ext_mpw", json_site_mpw );
if (site->url) if (site->url)
json_object_object_add( json_site_mpw, "url", json_object_new_string( site->url ) ); json_object_object_add( json_site_mpw, "url", json_object_new_string( site->url ) );
if (json_object_object_length( json_site_mpw ))
json_object_object_add( json_site, "_ext_mpw", json_site_mpw );
mpw_free_strings( &content, &loginContent, NULL ); mpw_free_strings( &content, &loginContent, NULL );
} }
mpw_string_pushf( out, "%s\n", json_object_to_json_string_ext( json_file, JSON_C_TO_STRING_PRETTY | JSON_C_TO_STRING_SPACED ) ); mpw_string_pushf( out, "%s\n", json_object_to_json_string_ext( json_file,
JSON_C_TO_STRING_PRETTY | JSON_C_TO_STRING_SPACED | JSON_C_TO_STRING_NOSLASHESCAPE ) );
mpw_free( &masterKey, MPMasterKeySize ); mpw_free( &masterKey, MPMasterKeySize );
json_object_put( json_file ); json_object_put( json_file );
@@ -403,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;
@@ -576,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;
@@ -646,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 );
@@ -703,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;
@@ -756,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;
@@ -797,23 +801,25 @@ static MPMarshalledUser *mpw_marshal_read_json(
site->loginContent = mpw_strdup( siteLoginName ); site->loginContent = mpw_strdup( siteLoginName );
} }
json_object_iter json_site_question;
json_object *json_site_questions = mpw_get_json_section( json_site.val, "questions" ); json_object *json_site_questions = mpw_get_json_section( json_site.val, "questions" );
json_object_object_foreachC( json_site_questions, json_site_question ) { if (json_site_questions) {
MPMarshalledQuestion *question = mpw_marshal_question( site, json_site_question.key ); json_object_iter json_site_question;
const char *answerContent = mpw_get_json_string( json_site_question.val, "answer", NULL ); json_object_object_foreachC( json_site_questions, json_site_question ) {
question->type = (MPResultType)mpw_get_json_int( json_site_question.val, "type", MPResultTypeTemplatePhrase ); MPMarshalledQuestion *question = mpw_marshal_question( site, json_site_question.key );
const char *answerContent = mpw_get_json_string( json_site_question.val, "answer", NULL );
question->type = (MPResultType)mpw_get_json_int( json_site_question.val, "type", MPResultTypeTemplatePhrase );
if (!user->redacted) { if (!user->redacted) {
// Clear Text // Clear Text
if (answerContent && strlen( answerContent )) if (answerContent && strlen( answerContent ))
question->content = mpw_siteState( masterKey, site->name, MPCounterValueInitial, question->content = mpw_siteState( masterKey, site->name, MPCounterValueInitial,
MPKeyPurposeRecovery, question->keyword, question->type, answerContent, site->algorithm ); MPKeyPurposeRecovery, question->keyword, question->type, answerContent, site->algorithm );
} }
else { else {
// Redacted // Redacted
if (answerContent && strlen( answerContent )) if (answerContent && strlen( answerContent ))
question->content = mpw_strdup( answerContent ); question->content = mpw_strdup( answerContent );
}
} }
} }
} }
@@ -871,21 +877,14 @@ const MPMarshalFormat mpw_formatWithName(
if (!formatName || !strlen( formatName )) if (!formatName || !strlen( formatName ))
return MPMarshalFormatNone; return MPMarshalFormatNone;
// Lower-case to standardize it. if (mpw_strncasecmp( mpw_nameForFormat( MPMarshalFormatNone ), formatName, strlen( formatName ) ) == 0)
size_t stdFormatNameSize = strlen( formatName );
char stdFormatName[stdFormatNameSize + 1];
for (size_t c = 0; c < stdFormatNameSize; ++c)
stdFormatName[c] = (char)tolower( formatName[c] );
stdFormatName[stdFormatNameSize] = '\0';
if (strncmp( mpw_nameForFormat( MPMarshalFormatNone ), stdFormatName, strlen( stdFormatName ) ) == 0)
return MPMarshalFormatNone; return MPMarshalFormatNone;
if (strncmp( mpw_nameForFormat( MPMarshalFormatFlat ), stdFormatName, strlen( stdFormatName ) ) == 0) if (mpw_strncasecmp( mpw_nameForFormat( MPMarshalFormatFlat ), formatName, strlen( formatName ) ) == 0)
return MPMarshalFormatFlat; return MPMarshalFormatFlat;
if (strncmp( mpw_nameForFormat( MPMarshalFormatJSON ), stdFormatName, strlen( stdFormatName ) ) == 0) if (mpw_strncasecmp( mpw_nameForFormat( MPMarshalFormatJSON ), formatName, strlen( formatName ) ) == 0)
return MPMarshalFormatJSON; return MPMarshalFormatJSON;
dbg( "Not a format name: %s", stdFormatName ); dbg( "Not a format name: %s", formatName );
return (MPMarshalFormat)ERR; return (MPMarshalFormat)ERR;
} }

View File

@@ -53,38 +53,31 @@ const MPResultType mpw_typeWithName(const char *typeName) {
return MPResultTypeDeriveKey; return MPResultTypeDeriveKey;
} }
// Lower-case typeName to standardize it.
size_t stdTypeNameSize = strlen( typeName );
char stdTypeName[stdTypeNameSize + 1];
for (size_t c = 0; c < stdTypeNameSize; ++c)
stdTypeName[c] = (char)tolower( typeName[c] );
stdTypeName[stdTypeNameSize] = '\0';
// Find what password type is represented by the type name. // Find what password type is represented by the type name.
if (strncmp( mpw_nameForType( MPResultTypeTemplateMaximum ), stdTypeName, strlen( stdTypeName ) ) == 0) if (mpw_strncasecmp( mpw_nameForType( MPResultTypeTemplateMaximum ), typeName, strlen( typeName ) ) == 0)
return MPResultTypeTemplateMaximum; return MPResultTypeTemplateMaximum;
if (strncmp( mpw_nameForType( MPResultTypeTemplateLong ), stdTypeName, strlen( stdTypeName ) ) == 0) if (mpw_strncasecmp( mpw_nameForType( MPResultTypeTemplateLong ), typeName, strlen( typeName ) ) == 0)
return MPResultTypeTemplateLong; return MPResultTypeTemplateLong;
if (strncmp( mpw_nameForType( MPResultTypeTemplateMedium ), stdTypeName, strlen( stdTypeName ) ) == 0) if (mpw_strncasecmp( mpw_nameForType( MPResultTypeTemplateMedium ), typeName, strlen( typeName ) ) == 0)
return MPResultTypeTemplateMedium; return MPResultTypeTemplateMedium;
if (strncmp( mpw_nameForType( MPResultTypeTemplateBasic ), stdTypeName, strlen( stdTypeName ) ) == 0) if (mpw_strncasecmp( mpw_nameForType( MPResultTypeTemplateBasic ), typeName, strlen( typeName ) ) == 0)
return MPResultTypeTemplateBasic; return MPResultTypeTemplateBasic;
if (strncmp( mpw_nameForType( MPResultTypeTemplateShort ), stdTypeName, strlen( stdTypeName ) ) == 0) if (mpw_strncasecmp( mpw_nameForType( MPResultTypeTemplateShort ), typeName, strlen( typeName ) ) == 0)
return MPResultTypeTemplateShort; return MPResultTypeTemplateShort;
if (strncmp( mpw_nameForType( MPResultTypeTemplatePIN ), stdTypeName, strlen( stdTypeName ) ) == 0) if (mpw_strncasecmp( mpw_nameForType( MPResultTypeTemplatePIN ), typeName, strlen( typeName ) ) == 0)
return MPResultTypeTemplatePIN; return MPResultTypeTemplatePIN;
if (strncmp( mpw_nameForType( MPResultTypeTemplateName ), stdTypeName, strlen( stdTypeName ) ) == 0) if (mpw_strncasecmp( mpw_nameForType( MPResultTypeTemplateName ), typeName, strlen( typeName ) ) == 0)
return MPResultTypeTemplateName; return MPResultTypeTemplateName;
if (strncmp( mpw_nameForType( MPResultTypeTemplatePhrase ), stdTypeName, strlen( stdTypeName ) ) == 0) if (mpw_strncasecmp( mpw_nameForType( MPResultTypeTemplatePhrase ), typeName, strlen( typeName ) ) == 0)
return MPResultTypeTemplatePhrase; return MPResultTypeTemplatePhrase;
if (strncmp( mpw_nameForType( MPResultTypeStatefulPersonal ), stdTypeName, strlen( stdTypeName ) ) == 0) if (mpw_strncasecmp( mpw_nameForType( MPResultTypeStatefulPersonal ), typeName, strlen( typeName ) ) == 0)
return MPResultTypeStatefulPersonal; return MPResultTypeStatefulPersonal;
if (strncmp( mpw_nameForType( MPResultTypeStatefulDevice ), stdTypeName, strlen( stdTypeName ) ) == 0) if (mpw_strncasecmp( mpw_nameForType( MPResultTypeStatefulDevice ), typeName, strlen( typeName ) ) == 0)
return MPResultTypeStatefulDevice; return MPResultTypeStatefulDevice;
if (strncmp( mpw_nameForType( MPResultTypeDeriveKey ), stdTypeName, strlen( stdTypeName ) ) == 0) if (mpw_strncasecmp( mpw_nameForType( MPResultTypeDeriveKey ), typeName, strlen( typeName ) ) == 0)
return MPResultTypeDeriveKey; return MPResultTypeDeriveKey;
dbg( "Not a generated type name: %s", stdTypeName ); dbg( "Not a generated type name: %s", typeName );
return (MPResultType)ERR; return (MPResultType)ERR;
} }
@@ -129,35 +122,35 @@ const char **mpw_templatesForType(MPResultType type, size_t *count) {
switch (type) { switch (type) {
case MPResultTypeTemplateMaximum: case MPResultTypeTemplateMaximum:
return mpw_alloc_array( count, const char *, return mpw_strings( count,
"anoxxxxxxxxxxxxxxxxx", "axxxxxxxxxxxxxxxxxno" ); "anoxxxxxxxxxxxxxxxxx", "axxxxxxxxxxxxxxxxxno", NULL );
case MPResultTypeTemplateLong: case MPResultTypeTemplateLong:
return mpw_alloc_array( count, const char *, return mpw_strings( count,
"CvcvnoCvcvCvcv", "CvcvCvcvnoCvcv", "CvcvCvcvCvcvno", "CvcvnoCvcvCvcv", "CvcvCvcvnoCvcv", "CvcvCvcvCvcvno",
"CvccnoCvcvCvcv", "CvccCvcvnoCvcv", "CvccCvcvCvcvno", "CvccnoCvcvCvcv", "CvccCvcvnoCvcv", "CvccCvcvCvcvno",
"CvcvnoCvccCvcv", "CvcvCvccnoCvcv", "CvcvCvccCvcvno", "CvcvnoCvccCvcv", "CvcvCvccnoCvcv", "CvcvCvccCvcvno",
"CvcvnoCvcvCvcc", "CvcvCvcvnoCvcc", "CvcvCvcvCvccno", "CvcvnoCvcvCvcc", "CvcvCvcvnoCvcc", "CvcvCvcvCvccno",
"CvccnoCvccCvcv", "CvccCvccnoCvcv", "CvccCvccCvcvno", "CvccnoCvccCvcv", "CvccCvccnoCvcv", "CvccCvccCvcvno",
"CvcvnoCvccCvcc", "CvcvCvccnoCvcc", "CvcvCvccCvccno", "CvcvnoCvccCvcc", "CvcvCvccnoCvcc", "CvcvCvccCvccno",
"CvccnoCvcvCvcc", "CvccCvcvnoCvcc", "CvccCvcvCvccno" ); "CvccnoCvcvCvcc", "CvccCvcvnoCvcc", "CvccCvcvCvccno", NULL );
case MPResultTypeTemplateMedium: case MPResultTypeTemplateMedium:
return mpw_alloc_array( count, const char *, return mpw_strings( count,
"CvcnoCvc", "CvcCvcno" ); "CvcnoCvc", "CvcCvcno", NULL );
case MPResultTypeTemplateShort: case MPResultTypeTemplateShort:
return mpw_alloc_array( count, const char *, return mpw_strings( count,
"Cvcn" ); "Cvcn", NULL );
case MPResultTypeTemplateBasic: case MPResultTypeTemplateBasic:
return mpw_alloc_array( count, const char *, return mpw_strings( count,
"aaanaaan", "aannaaan", "aaannaaa" ); "aaanaaan", "aannaaan", "aaannaaa", NULL );
case MPResultTypeTemplatePIN: case MPResultTypeTemplatePIN:
return mpw_alloc_array( count, const char *, return mpw_strings( count,
"nnnn" ); "nnnn", NULL );
case MPResultTypeTemplateName: case MPResultTypeTemplateName:
return mpw_alloc_array( count, const char *, return mpw_strings( count,
"cvccvcvcv" ); "cvccvcvcv", NULL );
case MPResultTypeTemplatePhrase: case MPResultTypeTemplatePhrase:
return mpw_alloc_array( count, const char *, return mpw_strings( count,
"cvcc cvc cvccvcv cvc", "cvc cvccvcvcv cvcv", "cv cvccv cvc cvcvccv" ); "cvcc cvc cvccvcv cvc", "cvc cvccvcvcv cvcv", "cv cvccv cvc cvcvccv", NULL );
default: { default: {
dbg( "Unknown generated type: %d", type ); dbg( "Unknown generated type: %d", type );
return NULL; return NULL;
@@ -177,21 +170,14 @@ const char *mpw_templateForType(MPResultType type, uint8_t templateIndex) {
const MPKeyPurpose mpw_purposeWithName(const char *purposeName) { const MPKeyPurpose mpw_purposeWithName(const char *purposeName) {
// Lower-case and trim optionally leading "generated" string from typeName to standardize it. if (mpw_strncasecmp( mpw_nameForPurpose( MPKeyPurposeAuthentication ), purposeName, strlen( purposeName ) ) == 0)
size_t stdPurposeNameSize = strlen( purposeName );
char stdPurposeName[stdPurposeNameSize + 1];
for (size_t c = 0; c < stdPurposeNameSize; ++c)
stdPurposeName[c] = (char)tolower( purposeName[c] );
stdPurposeName[stdPurposeNameSize] = '\0';
if (strncmp( mpw_nameForPurpose( MPKeyPurposeAuthentication ), stdPurposeName, strlen( stdPurposeName ) ) == 0)
return MPKeyPurposeAuthentication; return MPKeyPurposeAuthentication;
if (strncmp( mpw_nameForPurpose( MPKeyPurposeIdentification ), stdPurposeName, strlen( stdPurposeName ) ) == 0) if (mpw_strncasecmp( mpw_nameForPurpose( MPKeyPurposeIdentification ), purposeName, strlen( purposeName ) ) == 0)
return MPKeyPurposeIdentification; return MPKeyPurposeIdentification;
if (strncmp( mpw_nameForPurpose( MPKeyPurposeRecovery ), stdPurposeName, strlen( stdPurposeName ) ) == 0) if (mpw_strncasecmp( mpw_nameForPurpose( MPKeyPurposeRecovery ), purposeName, strlen( purposeName ) ) == 0)
return MPKeyPurposeRecovery; return MPKeyPurposeRecovery;
dbg( "Not a purpose name: %s", stdPurposeName ); dbg( "Not a purpose name: %s", purposeName );
return (MPKeyPurpose)ERR; return (MPKeyPurpose)ERR;
} }

View File

@@ -62,6 +62,27 @@ void mpw_uint64(const uint64_t number, uint8_t buf[8]) {
buf[7] = (uint8_t)((number >> 0L) & UINT8_MAX); buf[7] = (uint8_t)((number >> 0L) & UINT8_MAX);
} }
const char **mpw_strings(size_t *count, const char *strings, ...) {
va_list args;
va_start( args, strings );
const char **array = NULL;
size_t size = 0;
for (const char *string = strings; string; (string = va_arg( args, const char * ))) {
size_t cursor = size / sizeof( *array );
if (!mpw_realloc( &array, &size, sizeof( string ) )) {
mpw_free( &array, size );
*count = 0;
return NULL;
}
array[cursor] = string;
}
va_end( args );
*count = size / sizeof( *array );
return array;
}
bool mpw_push_buf(uint8_t **buffer, size_t *bufferSize, const void *pushBuffer, const size_t pushSize) { bool mpw_push_buf(uint8_t **buffer, size_t *bufferSize, const void *pushBuffer, const size_t pushSize) {
if (!buffer || !bufferSize || !pushBuffer || !pushSize) if (!buffer || !bufferSize || !pushBuffer || !pushSize)
@@ -275,14 +296,15 @@ static uint8_t const *mpw_aes(bool encrypt, const uint8_t *key, const size_t key
return NULL; return NULL;
// IV = zero // IV = zero
uint8_t iv[AES_BLOCKLEN]; static uint8_t *iv = NULL;
mpw_zero( iv, sizeof iv ); if (!iv)
iv = calloc( AES_BLOCKLEN, sizeof( uint8_t ) );
// Add PKCS#7 padding // Add PKCS#7 padding
uint32_t aesSize = ((uint32_t)*bufSize + AES_BLOCKLEN - 1) & -AES_BLOCKLEN; // round up to block size. uint32_t aesSize = ((uint32_t)*bufSize + AES_BLOCKLEN - 1) & -AES_BLOCKLEN; // round up to block size.
if (encrypt && !(*bufSize % AES_BLOCKLEN)) // add pad block if plain text fits block size. if (encrypt && !(*bufSize % AES_BLOCKLEN)) // add pad block if plain text fits block size.
encrypt += AES_BLOCKLEN; encrypt += AES_BLOCKLEN;
uint8_t aesBuf[aesSize]; uint8_t *aesBuf = malloc( aesSize );
memcpy( aesBuf, buf, *bufSize ); memcpy( aesBuf, buf, *bufSize );
memset( aesBuf + *bufSize, aesSize - *bufSize, aesSize - *bufSize ); memset( aesBuf + *bufSize, aesSize - *bufSize, aesSize - *bufSize );
uint8_t *resultBuf = malloc( aesSize ); uint8_t *resultBuf = malloc( aesSize );
@@ -291,8 +313,7 @@ static uint8_t const *mpw_aes(bool encrypt, const uint8_t *key, const size_t key
AES_CBC_encrypt_buffer( resultBuf, aesBuf, aesSize, key, iv ); AES_CBC_encrypt_buffer( resultBuf, aesBuf, aesSize, key, iv );
else else
AES_CBC_decrypt_buffer( resultBuf, aesBuf, aesSize, key, iv ); AES_CBC_decrypt_buffer( resultBuf, aesBuf, aesSize, key, iv );
mpw_zero( aesBuf, aesSize ); mpw_free( &aesBuf, aesSize );
mpw_zero( iv, AES_BLOCKLEN );
// Truncate PKCS#7 padding // Truncate PKCS#7 padding
if (encrypt) if (encrypt)
@@ -496,3 +517,12 @@ char *mpw_strndup(const char *src, size_t max) {
return dst; return dst;
} }
int mpw_strncasecmp(const char *s1, const char *s2, size_t max) {
int cmp = 0;
for (; !cmp && max-- > 0 && s1 && s2; ++s1, ++s2)
cmp = tolower( *(unsigned char *)s1 ) - tolower( *(unsigned char *)s2 );
return cmp;
}

View File

@@ -33,36 +33,36 @@ extern int mpw_verbosity;
#endif #endif
#ifndef mpw_log #ifndef mpw_log
#define mpw_log(level, ...) ({ \ #define mpw_log(level, format, ...) do { \
if (mpw_verbosity >= level) { \ if (mpw_verbosity >= level) { \
mpw_log_do( level, ##__VA_ARGS__ ); \ mpw_log_do( level, format, ##__VA_ARGS__ ); \
}; }) }; } while (0)
#endif #endif
#ifndef trc #ifndef trc
/** Logging internal state. */ /** Logging internal state. */
#define trc_level 3 #define trc_level 3
#define trc(...) mpw_log( trc_level, ##__VA_ARGS__ ) #define trc(format, ...) mpw_log( trc_level, format, ##__VA_ARGS__ )
/** Logging state and events interesting when investigating issues. */ /** Logging state and events interesting when investigating issues. */
#define dbg_level 2 #define dbg_level 2
#define dbg(...) mpw_log( dbg_level, ##__VA_ARGS__ ) #define dbg(format, ...) mpw_log( dbg_level, format, ##__VA_ARGS__ )
/** User messages. */ /** User messages. */
#define inf_level 1 #define inf_level 1
#define inf(...) mpw_log( inf_level, ##__VA_ARGS__ ) #define inf(format, ...) mpw_log( inf_level, format, ##__VA_ARGS__ )
/** Recoverable issues and user suggestions. */ /** Recoverable issues and user suggestions. */
#define wrn_level 0 #define wrn_level 0
#define wrn(...) mpw_log( wrn_level, ##__VA_ARGS__ ) #define wrn(format, ...) mpw_log( wrn_level, format, ##__VA_ARGS__ )
/** Unrecoverable issues. */ /** Unrecoverable issues. */
#define err_level -1 #define err_level -1
#define err(...) mpw_log( err_level, ##__VA_ARGS__ ) #define err(format, ...) mpw_log( err_level, format, ##__VA_ARGS__ )
/** Issues that lead to abortion. */ /** Issues that lead to abortion. */
#define ftl_level -2 #define ftl_level -2
#define ftl(...) mpw_log( ftl_level, ##__VA_ARGS__ ) #define ftl(format, ...) mpw_log( ftl_level, format, ##__VA_ARGS__ )
#endif #endif
#ifndef min #ifndef min
@@ -98,14 +98,8 @@ void mpw_uint32(const uint32_t number, uint8_t buf[4]);
void mpw_uint64(const uint64_t number, uint8_t buf[8]); void mpw_uint64(const uint64_t number, uint8_t buf[8]);
/** Allocate a new array of _type, assign its element count to _count if not NULL and populate it with the varargs. */ /** Allocate a new array of _type, assign its element count to _count if not NULL and populate it with the varargs. */
#define mpw_alloc_array(_count, _type, ...) ({ \ const char **mpw_strings(
_type stackElements[] = { __VA_ARGS__ }; \ size_t *count, const char *strings, ...);
if (_count) \
*_count = sizeof( stackElements ) / sizeof( _type ); \
_type *allocElements = malloc( sizeof( stackElements ) ); \
memcpy( allocElements, stackElements, sizeof( stackElements ) ); \
allocElements; \
})
/** Push a buffer onto a buffer. reallocs the given buffer and appends the given buffer. */ /** Push a buffer onto a buffer. reallocs the given buffer and appends the given buffer. */
bool mpw_push_buf( bool mpw_push_buf(
@@ -124,9 +118,9 @@ bool mpw_push_int(
/** 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.
* On failure, the buffer and pointers will remain unaffected. * On failure, the pointers will remain unaffected.
* @param buffer A pointer to the buffer to reallocate. * @param buffer A pointer to the buffer to reallocate.
* @param bufferSize A pointer to the buffer's actual size. * @param bufferSize A pointer to the buffer's current size.
* @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.
*/ */
@@ -150,6 +144,20 @@ bool __mpw_free_string(
({ __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( bool __mpw_free_strings(
char **strings, ...); char **strings, ...);
#ifdef _MSC_VER
#undef mpw_realloc
#define mpw_realloc(buffer, bufferSize, deltaSize) \
__mpw_realloc( (const void **)buffer, bufferSize, deltaSize )
#undef mpw_free
#define mpw_free(buffer, bufferSize) \
__mpw_free( (void **)buffer, bufferSize )
#undef mpw_free_string
#define mpw_free_string(string) \
__mpw_free_string( (char **)string )
#undef mpw_free_strings
#define mpw_free_strings(strings, ...) \
__mpw_free_strings( (char **)strings, __VA_ARGS__ )
#endif
//// Cryptographic functions. //// Cryptographic functions.
@@ -207,5 +215,7 @@ const size_t mpw_utf8_strlen(const char *utf8String);
char *mpw_strdup(const char *src); char *mpw_strdup(const char *src);
/** Drop-in for POSIX strndup(3). */ /** Drop-in for POSIX strndup(3). */
char *mpw_strndup(const char *src, size_t max); char *mpw_strndup(const char *src, size_t max);
/** Drop-in for POSIX strncasecmp(3). */
int mpw_strncasecmp(const char *s1, const char *s2, size_t max);
#endif // _MPW_UTIL_H #endif // _MPW_UTIL_H

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
} }
@@ -20,20 +25,11 @@ dependencies {
} }
processResources { processResources {
dependsOn task( type: Sync, 'copyResources', { dependsOn task( type: Sync, 'processResources-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 )
into new File( processResources.outputs.files.singleFile, "lib" )
}
} }
} ) } )
} }
compileJava {
doLast {
ant.javah( class: 'com.lyndir.masterpassword.impl.MPAlgorithmV0',
outputFile: '../../c/core/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,19 @@ 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.
*/ */
@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 );
@@ -171,6 +185,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 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;
@@ -45,8 +46,6 @@ public class MPAlgorithmV0 extends MPAlgorithm {
Native.load( MPAlgorithmV0.class, "mpw" ); Native.load( MPAlgorithmV0.class, "mpw" );
} }
public final Version version = MPAlgorithm.Version.V0;
protected final Logger logger = Logger.get( getClass() ); protected final Logger logger = Logger.get( getClass() );
@Nullable @Nullable
@@ -124,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;
@@ -213,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 ) );
@@ -234,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

@@ -19,11 +19,15 @@
package com.lyndir.masterpassword.impl; package com.lyndir.masterpassword.impl;
import com.google.common.base.Joiner; import com.google.common.base.Joiner;
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;
/** /**
@@ -39,54 +43,161 @@ 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 );
InputStream libraryStream = context.getResourceAsStream( libraryResource );
if (libraryStream == null)
throw new IllegalStateException(
"Library: " + name + " (" + libraryResource + "), not found in class loader for: " + context );
// Write the library resource to a temporary file. @Nullable
File libraryFile = File.createTempFile( libraryName, libraryExtension ); File libraryFile = null;
FileOutputStream libraryFileStream = new FileOutputStream( libraryFile ); Set<String> libraryResources = getLibraryResources( library );
for (final String libraryResource : libraryResources) {
try { try {
libraryFile.deleteOnExit(); InputStream libraryStream = context.getResourceAsStream( libraryResource );
ByteStreams.copy( libraryStream, libraryFileStream ); if (libraryStream == null) {
} logger.dbg( "No resource for library: %s", libraryResource );
finally { continue;
libraryFileStream.close(); }
libraryStream.close();
}
// Load the library from the temporary file. // Write the library resource to a temporary file.
System.load( libraryFile.getAbsolutePath() ); libraryFile = File.createTempFile( libraryName, libraryExtension );
} FileOutputStream libraryFileStream = new FileOutputStream( libraryFile );
catch (final IOException e) { try {
throw new IllegalStateException( "Couldn't extract library: " + name, e ); libraryFile.deleteOnExit();
ByteStreams.copy( libraryStream, libraryFileStream );
}
finally {
libraryFileStream.close();
libraryStream.close();
}
// Load the library from the temporary file.
System.load( libraryFile.getAbsolutePath() );
return true;
}
catch (@SuppressWarnings("ErrorNotRethrown") final IOException | UnsatisfiedLinkError 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 = System.getProperty( "os.name" ).toLowerCase( Locale.ROOT ); // Standardize system naming in accordance with masterpassword-core.
String architecture = System.getProperty( "os.arch" ).toLowerCase( Locale.ROOT ); Sys system = Sys.findCurrent();
if ("Mac OS X".equalsIgnoreCase( system ))
system = "macos";
return Joiner.on( RESOURCE_SEPARATOR ).join( "", NATIVES_PATH, system, architecture, library ); // Standardize architecture naming in accordance with masterpassword-core.
Collection<Arch> architectures = new LinkedHashSet<>();
architectures.add( Arch.findCurrent() );
architectures.addAll( Arrays.asList( Arch.values() ) );
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 {
if (System.getenv( 'KEY_PW_DESKTOP' ) != null) manifest {
ant.signjar( attributes 'Implementation-Title': description
jar: archivePath, attributes 'Implementation-Version': version
alias: 'masterpassword-desktop', }
keystore: 'masterpassword.keystore', doLast {
storepass: System.getenv( 'STORE_PW' ), if (System.getenv( 'KEY_PW_DESKTOP' ) != null)
keypass: System.getenv( 'KEY_PW_DESKTOP' ), ant.signjar( jar: archivePath,
preservelastmodified: 'true', alias: 'masterpassword-desktop',
destdir: '.' keystore: 'masterpassword.keystore',
) storepass: System.getenv( 'STORE_PW' ),
keypass: System.getenv( 'KEY_PW_DESKTOP' ),
preservelastmodified: 'true',
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,128 +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.CharSource;
import com.google.common.io.Resources;
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.URI;
import java.net.URL;
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) {
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) {
}
try {
// AppleGUI adds support for macOS features.
Optional<Class<GUI>> appleGUI = TypeUtils.loadClass( "com.lyndir.masterpassword.gui.platform.mac.AppleGUI" );
if (appleGUI.isPresent())
appleGUI.get().getConstructor().newInstance().open();
else // No special platform handling.
new GUI().open();
}
catch (final IllegalAccessException | InstantiationException | NoSuchMethodException | InvocationTargetException e) {
throw logger.bug( e );
}
}
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 upstreamRevisionURL = "https://masterpassword.app/masterpassword-gui.jar.rev";
CharSource upstream = Resources.asCharSource( URI.create( upstreamRevisionURL ).toURL(), Charsets.UTF_8 );
String upstreamRevision = upstream.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,359 +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.base.Throwables;
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> executorByWindow = new WeakHashMap<>();
private static final Logger logger = Logger.get( Res.class );
private static final Colors colors = new Colors();
public static Future<?> execute(final Window host, final Runnable job) {
return schedule( host, job, 0, TimeUnit.MILLISECONDS );
}
public static Future<?> schedule(final Window host, final Runnable job, final long delay, final TimeUnit timeUnit) {
return getExecutor( host ).schedule( () -> {
try {
job.run();
}
catch (final Throwable t) {
logger.err( t, "Unexpected: %s", t.getLocalizedMessage() );
}
}, delay, timeUnit );
}
public static <V> ListenableFuture<V> execute(final Window host, final Callable<V> job) {
return schedule( host, job, 0, TimeUnit.MILLISECONDS );
}
public static <V> ListenableFuture<V> schedule(final Window host, final Callable<V> job, final long delay, final TimeUnit timeUnit) {
ScheduledExecutorService executor = getExecutor( host );
return JdkFutureAdapters.listenInPoolThread( executor.schedule( () -> {
try {
return job.call();
}
catch (final Throwable t) {
logger.err( t, "Unexpected: %s", t.getLocalizedMessage() );
throw Throwables.propagate( t );
}
}, delay, timeUnit ), executor );
}
private static ScheduledExecutorService getExecutor(final Window host) {
ScheduledExecutorService executor = executorByWindow.get( host );
if (executor == null) {
executorByWindow.put( host, executor = Executors.newSingleThreadScheduledExecutor() );
host.addWindowListener( new WindowAdapter() {
@Override
public void windowClosed(final WindowEvent e) {
ExecutorService executor = executorByWindow.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 Throwables.propagate( 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

@@ -22,30 +22,28 @@ import com.google.common.primitives.UnsignedInteger;
import com.lyndir.masterpassword.MPAlgorithm; import com.lyndir.masterpassword.MPAlgorithm;
import com.lyndir.masterpassword.MPResultType; import com.lyndir.masterpassword.MPResultType;
import com.lyndir.masterpassword.model.impl.MPBasicSite; import com.lyndir.masterpassword.model.impl.MPBasicSite;
import javax.annotation.Nonnull;
import javax.annotation.Nullable; 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
@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,22 @@
package com.lyndir.masterpassword.gui.model;
import com.lyndir.masterpassword.model.*;
import com.lyndir.masterpassword.model.impl.*;
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." );
}
}

View File

@@ -1,51 +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.lyndir.masterpassword.gui.GUI;
/**
* @author lhunath, 2014-06-10
*/
public class AppleGUI extends GUI {
public AppleGUI() {
Application application = Application.getApplication();
application.addAppEventListener( new AppForegroundListener() {
@Override
public void appMovedToBackground(final AppEvent.AppForegroundEvent arg0) {
}
@Override
public void appRaisedToForeground(final AppEvent.AppForegroundEvent arg0) {
open();
}
} );
application.addAppEventListener( new AppReOpenedListener() {
@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 float TEXT_SIZE_HEADING = 19f;
GradientPanel container = gradientPanel( null, null ); public static final float TEXT_SIZE_CONTROL = 13f;
// 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 ); public static GradientPanel panel(@Nullable final LayoutManager layout, @Nullable final Color color) {
setAlignmentX( LEFT_ALIGNMENT ); return new GradientPanel( layout, color );
setAlignmentY( BOTTOM_ALIGNMENT ); }
}
}; 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
@@ -177,30 +428,52 @@ public abstract class Components {
public static JCheckBox checkBox(final String label) { public static JCheckBox checkBox(final String label) {
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 );
} }
}; };
} }
@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 +483,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 +509,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 +538,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) { @Override
paint = new GradientPaint( new Point( 0, 0 ), gradientColor, new Point( getWidth(), getHeight() ), gradientColor.darker() ); public void setBounds(final int x, final int y, final int width, final int height) {
repaint(); 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,23 @@
package com.lyndir.masterpassword.gui.util;
import com.google.common.util.concurrent.FutureCallback;
import com.lyndir.lhunath.opal.system.logging.Logger;
/**
* @author lhunath, 2018-07-08
*/
public abstract class FailableCallback<T> implements FutureCallback<T> {
private final Logger logger;
protected FailableCallback(final Logger logger) {
this.logger = logger;
}
@Override
public void onFailure(final Throwable t) {
logger.err( t, "Future failed." );
onSuccess( null );
}
}

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,373 @@
//==============================================================================
// 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.collect.Maps;
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.lang.ref.SoftReference;
import java.util.Map;
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 float size) {
return emoticonsRegular().deriveFont( size );
}
public Font controlFont(final float size) {
return exoRegular().deriveFont( size );
}
public Font valueFont(final float size) {
return sourceSansProRegular().deriveFont( size );
}
public Font bigValueFont(final float size) {
return sourceSansProBlack().deriveFont( size );
}
public Font emoticonsRegular() {
return font( "fonts/Emoticons-Regular.otf" );
}
public Font sourceCodeProRegular() {
return font( "fonts/SourceCodePro-Regular.otf" );
}
public Font sourceCodeProBlack() {
return font( "fonts/SourceCodePro-Bold.otf" );
}
public Font sourceSansProRegular() {
return font( "fonts/SourceSansPro-Regular.otf" );
}
public Font sourceSansProBlack() {
return font( "fonts/SourceSansPro-Bold.otf" );
}
public Font exoBold() {
return font( "fonts/Exo2.0-Bold.otf" );
}
public Font exoExtraBold() {
return font( "fonts/Exo2.0-ExtraBold.otf" );
}
public Font exoRegular() {
return font( "fonts/Exo2.0-Regular.otf" );
}
public Font exoThin() {
return font( "fonts/Exo2.0-Thin.otf" );
}
public Font arimoBold() {
return font( "fonts/Arimo-Bold.ttf" );
}
public Font arimoBoldItalic() {
return font( "fonts/Arimo-BoldItalic.ttf" );
}
public Font arimoItalic() {
return font( "fonts/Arimo-Italic.ttf" );
}
public 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 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

@@ -0,0 +1,93 @@
package com.lyndir.masterpassword.gui.util;
import static com.lyndir.lhunath.opal.system.util.ObjectUtils.*;
import com.google.common.collect.*;
import java.util.List;
import java.util.concurrent.*;
import javax.swing.*;
import org.jetbrains.annotations.NotNull;
/**
* @author lhunath, 2018-07-08
*/
public class SwingExecutorService extends AbstractExecutorService {
private final List<Runnable> pendingCommands = Lists.newLinkedList();
private final BlockingQueue<Boolean> terminated = Queues.newLinkedBlockingDeque( 1 );
private final boolean immediate;
private boolean shutdown;
/**
* @param immediate Allow immediate execution of the job in {@link #execute(Runnable)} if already on the right thread.
* If {@code false}, jobs are always posted for later execution on the event thread.
*/
public SwingExecutorService(final boolean immediate) {
this.immediate = immediate;
}
@Override
public void shutdown() {
synchronized (pendingCommands) {
shutdown = true;
if (pendingCommands.isEmpty())
terminated.add( true );
}
}
@NotNull
@Override
public List<Runnable> shutdownNow() {
shutdown();
synchronized (pendingCommands) {
return ImmutableList.copyOf( pendingCommands );
}
}
@Override
public boolean isShutdown() {
synchronized (pendingCommands) {
return shutdown;
}
}
@Override
public boolean isTerminated() {
return ifNotNullElse( terminated.peek(), false );
}
@Override
public boolean awaitTermination(final long timeout, @NotNull final TimeUnit unit)
throws InterruptedException {
return ifNotNullElse( terminated.poll( timeout, unit ), false );
}
@Override
public void execute(@NotNull final Runnable command) {
synchronized (pendingCommands) {
if (shutdown)
throw new RejectedExecutionException( "Executor is shut down." );
pendingCommands.add( command );
}
if (immediate && SwingUtilities.isEventDispatchThread())
run( command );
else
SwingUtilities.invokeLater( () -> run( command ) );
}
private void run(final Runnable command) {
command.run();
synchronized (pendingCommands) {
pendingCommands.remove( command );
if (shutdown && pendingCommands.isEmpty())
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,281 +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.*;
import com.lyndir.masterpassword.*;
import com.lyndir.masterpassword.gui.Res;
import com.lyndir.masterpassword.gui.util.Components;
import com.lyndir.masterpassword.gui.util.UnsignedIntegerModel;
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.*;
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 {
@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( new ActionListener() {
@Override
public void actionPerformed(final ActionEvent e) {
Futures.addCallback( updatePassword( true ), new FutureCallback<String>() {
@Override
public void onSuccess(@Nullable final String sitePassword) {
Transferable clipboardContents = new StringSelection( sitePassword );
Toolkit.getDefaultToolkit().getSystemClipboard().setContents( clipboardContents, null );
SwingUtilities.invokeLater( () -> {
passwordField.setText( null );
siteNameField.setText( null );
dispatchEvent( new WindowEvent( PasswordFrame.this, WindowEvent.WINDOW_CLOSING ) );
} );
}
@Override
public void onFailure(@Nonnull final Throwable t) {
}
} );
}
} );
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.execute( this, () -> site.getResult( MPKeyPurpose.Authentication, null, null ) );
Futures.addCallback( passwordFuture, new FutureCallback<String>() {
@Override
public void onSuccess(@Nullable final String sitePassword) {
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() );
siteCounterField.setValue( currentSite.getCounter() );
siteNameField.setText( currentSite.getName() );
if (siteNameField.getText().startsWith( siteNameQuery ))
siteNameField.select( siteNameQuery.length(), siteNameField.getText().length() );
passwordField.setText( sitePassword );
tipLabel.setText( "Press [Enter] to copy the password. Then paste it into the password field." );
updatingUI = false;
} );
}
@Override
public void onFailure(@Nonnull final Throwable t) {
}
} );
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.schedule( 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.execute( 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

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