├── MinaOTP-MAC
├── MinaOtp.icns
├── Assets.xcassets
│ ├── Contents.json
│ ├── open.imageset
│ │ ├── open@1x.png
│ │ ├── open@2x.png
│ │ └── Contents.json
│ ├── close.imageset
│ │ ├── close@1x.png
│ │ ├── close@2x.png
│ │ └── Contents.json
│ ├── AppIcon.appiconset
│ │ ├── icon_16x16.png
│ │ ├── icon_32x32.png
│ │ ├── icon_128x128.png
│ │ ├── icon_16x16@2x.png
│ │ ├── icon_256x256.png
│ │ ├── icon_32x32@2x.png
│ │ ├── icon_512x512.png
│ │ ├── icon_128x128@2x.png
│ │ ├── icon_256x256@2x.png
│ │ ├── icon_512x512@2x.png
│ │ └── Contents.json
│ ├── statusIcon.imageset
│ │ ├── iconBlack@1x.png
│ │ └── iconBlack@2x.png
│ ├── image_normal.imageset
│ │ ├── image_normal@1x.png
│ │ ├── image_normal@2x.png
│ │ └── Contents.json
│ ├── image_selected.imageset
│ │ ├── image_selected@1x.png
│ │ ├── image_selected@2x.png
│ │ └── Contents.json
│ └── StatusIcon.imageset
│ │ └── Contents.json
├── en.lproj
│ ├── InfoPlist.strings
│ ├── Localizable.strings
│ └── MainMenu.strings
├── zh-Hans.lproj
│ ├── InfoPlist.strings
│ ├── Localizable.strings
│ └── MainMenu.strings
├── MinaOTP_MAC.entitlements
├── Utils.h
├── Magnet
│ ├── BoolExtensions.swift
│ ├── Magnet.h
│ ├── Info.plist
│ ├── HotKey.swift
│ ├── KeyCombo.swift
│ ├── KeyCodeTransformer.swift
│ ├── KeyTransformer.swift
│ └── HotKeyCenter.swift
├── OTPGenerator
│ ├── GeneratorTotp.h
│ ├── Base32Addition.h
│ ├── GeneratorTotp.m
│ └── Base32Addition.m
├── MinaOTP-MAC-Bridging-Header.h
├── CSTableRowView.swift
├── MinaOTP-MAC.entitlements
├── Info.plist
├── ShowTips.swift
├── StatusItemView.swift
├── ProgressView.swift
├── Utils.m
├── CellView.swift
├── DataManager.swift
├── ScanWindow.swift
├── Tools.swift
├── EditPopoverViewController.swift
├── AppDelegate.swift
├── AddWindow.swift
├── FlatButton.swift
└── PopoverViewController.swift
├── MinaOTP-MAC.xcodeproj
├── project.xcworkspace
│ ├── contents.xcworkspacedata
│ ├── xcuserdata
│ │ ├── Four.xcuserdatad
│ │ │ └── UserInterfaceState.xcuserstate
│ │ ├── wubin.xcuserdatad
│ │ │ └── UserInterfaceState.xcuserstate
│ │ ├── rawlings.xcuserdatad
│ │ │ └── UserInterfaceState.xcuserstate
│ │ └── wujianming.xcuserdatad
│ │ │ └── UserInterfaceState.xcuserstate
│ └── xcshareddata
│ │ └── IDEWorkspaceChecks.plist
├── xcuserdata
│ ├── wubin.xcuserdatad
│ │ └── xcschemes
│ │ │ └── xcschememanagement.plist
│ ├── Four.xcuserdatad
│ │ └── xcschemes
│ │ │ └── xcschememanagement.plist
│ ├── wujianming.xcuserdatad
│ │ └── xcschemes
│ │ │ ├── xcschememanagement.plist
│ │ │ └── MinaOTP-MAC.xcscheme
│ └── rawlings.xcuserdatad
│ │ └── xcschemes
│ │ └── xcschememanagement.plist
└── xcshareddata
│ └── xcschemes
│ └── MinaOTP-MAC.xcscheme
├── .github
└── workflows
│ └── swift.yml
├── MinaOTP-MACTests
├── Info.plist
└── MinaOTP_MACTests.swift
├── MinaOTP-MACUITests
├── Info.plist
└── MinaOTP_MACUITests.swift
├── README_zh.md
└── README.md
/MinaOTP-MAC/MinaOtp.icns:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MinaOTP/MinaOTP-MAC/HEAD/MinaOTP-MAC/MinaOtp.icns
--------------------------------------------------------------------------------
/MinaOTP-MAC/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "version" : 1,
4 | "author" : "xcode"
5 | }
6 | }
--------------------------------------------------------------------------------
/MinaOTP-MAC/Assets.xcassets/open.imageset/open@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MinaOTP/MinaOTP-MAC/HEAD/MinaOTP-MAC/Assets.xcassets/open.imageset/open@1x.png
--------------------------------------------------------------------------------
/MinaOTP-MAC/Assets.xcassets/open.imageset/open@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MinaOTP/MinaOTP-MAC/HEAD/MinaOTP-MAC/Assets.xcassets/open.imageset/open@2x.png
--------------------------------------------------------------------------------
/MinaOTP-MAC/Assets.xcassets/close.imageset/close@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MinaOTP/MinaOTP-MAC/HEAD/MinaOTP-MAC/Assets.xcassets/close.imageset/close@1x.png
--------------------------------------------------------------------------------
/MinaOTP-MAC/Assets.xcassets/close.imageset/close@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MinaOTP/MinaOTP-MAC/HEAD/MinaOTP-MAC/Assets.xcassets/close.imageset/close@2x.png
--------------------------------------------------------------------------------
/MinaOTP-MAC/Assets.xcassets/AppIcon.appiconset/icon_16x16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MinaOTP/MinaOTP-MAC/HEAD/MinaOTP-MAC/Assets.xcassets/AppIcon.appiconset/icon_16x16.png
--------------------------------------------------------------------------------
/MinaOTP-MAC/Assets.xcassets/AppIcon.appiconset/icon_32x32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MinaOTP/MinaOTP-MAC/HEAD/MinaOTP-MAC/Assets.xcassets/AppIcon.appiconset/icon_32x32.png
--------------------------------------------------------------------------------
/MinaOTP-MAC/Assets.xcassets/AppIcon.appiconset/icon_128x128.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MinaOTP/MinaOTP-MAC/HEAD/MinaOTP-MAC/Assets.xcassets/AppIcon.appiconset/icon_128x128.png
--------------------------------------------------------------------------------
/MinaOTP-MAC/Assets.xcassets/AppIcon.appiconset/icon_16x16@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MinaOTP/MinaOTP-MAC/HEAD/MinaOTP-MAC/Assets.xcassets/AppIcon.appiconset/icon_16x16@2x.png
--------------------------------------------------------------------------------
/MinaOTP-MAC/Assets.xcassets/AppIcon.appiconset/icon_256x256.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MinaOTP/MinaOTP-MAC/HEAD/MinaOTP-MAC/Assets.xcassets/AppIcon.appiconset/icon_256x256.png
--------------------------------------------------------------------------------
/MinaOTP-MAC/Assets.xcassets/AppIcon.appiconset/icon_32x32@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MinaOTP/MinaOTP-MAC/HEAD/MinaOTP-MAC/Assets.xcassets/AppIcon.appiconset/icon_32x32@2x.png
--------------------------------------------------------------------------------
/MinaOTP-MAC/Assets.xcassets/AppIcon.appiconset/icon_512x512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MinaOTP/MinaOTP-MAC/HEAD/MinaOTP-MAC/Assets.xcassets/AppIcon.appiconset/icon_512x512.png
--------------------------------------------------------------------------------
/MinaOTP-MAC/Assets.xcassets/statusIcon.imageset/iconBlack@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MinaOTP/MinaOTP-MAC/HEAD/MinaOTP-MAC/Assets.xcassets/statusIcon.imageset/iconBlack@1x.png
--------------------------------------------------------------------------------
/MinaOTP-MAC/Assets.xcassets/statusIcon.imageset/iconBlack@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MinaOTP/MinaOTP-MAC/HEAD/MinaOTP-MAC/Assets.xcassets/statusIcon.imageset/iconBlack@2x.png
--------------------------------------------------------------------------------
/MinaOTP-MAC/Assets.xcassets/AppIcon.appiconset/icon_128x128@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MinaOTP/MinaOTP-MAC/HEAD/MinaOTP-MAC/Assets.xcassets/AppIcon.appiconset/icon_128x128@2x.png
--------------------------------------------------------------------------------
/MinaOTP-MAC/Assets.xcassets/AppIcon.appiconset/icon_256x256@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MinaOTP/MinaOTP-MAC/HEAD/MinaOTP-MAC/Assets.xcassets/AppIcon.appiconset/icon_256x256@2x.png
--------------------------------------------------------------------------------
/MinaOTP-MAC/Assets.xcassets/AppIcon.appiconset/icon_512x512@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MinaOTP/MinaOTP-MAC/HEAD/MinaOTP-MAC/Assets.xcassets/AppIcon.appiconset/icon_512x512@2x.png
--------------------------------------------------------------------------------
/MinaOTP-MAC/Assets.xcassets/image_normal.imageset/image_normal@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MinaOTP/MinaOTP-MAC/HEAD/MinaOTP-MAC/Assets.xcassets/image_normal.imageset/image_normal@1x.png
--------------------------------------------------------------------------------
/MinaOTP-MAC/Assets.xcassets/image_normal.imageset/image_normal@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MinaOTP/MinaOTP-MAC/HEAD/MinaOTP-MAC/Assets.xcassets/image_normal.imageset/image_normal@2x.png
--------------------------------------------------------------------------------
/MinaOTP-MAC/Assets.xcassets/image_selected.imageset/image_selected@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MinaOTP/MinaOTP-MAC/HEAD/MinaOTP-MAC/Assets.xcassets/image_selected.imageset/image_selected@1x.png
--------------------------------------------------------------------------------
/MinaOTP-MAC/Assets.xcassets/image_selected.imageset/image_selected@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MinaOTP/MinaOTP-MAC/HEAD/MinaOTP-MAC/Assets.xcassets/image_selected.imageset/image_selected@2x.png
--------------------------------------------------------------------------------
/MinaOTP-MAC/en.lproj/InfoPlist.strings:
--------------------------------------------------------------------------------
1 | /*
2 | InfoPlist.strings
3 | MinaOTP-MAC
4 |
5 | Created by 武建明 on 2018/8/13.
6 | Copyright © 2018年 Four_w. All rights reserved.
7 | */
8 | CFBundleDisplayName = "MinaOTP";
9 |
--------------------------------------------------------------------------------
/MinaOTP-MAC/zh-Hans.lproj/InfoPlist.strings:
--------------------------------------------------------------------------------
1 | /*
2 | InfoPlist.strings
3 | MinaOTP-MAC
4 |
5 | Created by 武建明 on 2018/8/13.
6 | Copyright © 2018年 Four_w. All rights reserved.
7 | */
8 | CFBundleDisplayName = "MinaOTP";
9 |
--------------------------------------------------------------------------------
/MinaOTP-MAC/MinaOTP_MAC.entitlements:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/MinaOTP-MAC.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/MinaOTP-MAC.xcodeproj/project.xcworkspace/xcuserdata/Four.xcuserdatad/UserInterfaceState.xcuserstate:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MinaOTP/MinaOTP-MAC/HEAD/MinaOTP-MAC.xcodeproj/project.xcworkspace/xcuserdata/Four.xcuserdatad/UserInterfaceState.xcuserstate
--------------------------------------------------------------------------------
/MinaOTP-MAC.xcodeproj/project.xcworkspace/xcuserdata/wubin.xcuserdatad/UserInterfaceState.xcuserstate:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MinaOTP/MinaOTP-MAC/HEAD/MinaOTP-MAC.xcodeproj/project.xcworkspace/xcuserdata/wubin.xcuserdatad/UserInterfaceState.xcuserstate
--------------------------------------------------------------------------------
/MinaOTP-MAC.xcodeproj/project.xcworkspace/xcuserdata/rawlings.xcuserdatad/UserInterfaceState.xcuserstate:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MinaOTP/MinaOTP-MAC/HEAD/MinaOTP-MAC.xcodeproj/project.xcworkspace/xcuserdata/rawlings.xcuserdatad/UserInterfaceState.xcuserstate
--------------------------------------------------------------------------------
/MinaOTP-MAC.xcodeproj/project.xcworkspace/xcuserdata/wujianming.xcuserdatad/UserInterfaceState.xcuserstate:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MinaOTP/MinaOTP-MAC/HEAD/MinaOTP-MAC.xcodeproj/project.xcworkspace/xcuserdata/wujianming.xcuserdatad/UserInterfaceState.xcuserstate
--------------------------------------------------------------------------------
/MinaOTP-MAC/Utils.h:
--------------------------------------------------------------------------------
1 | //
2 | // QRCodeUtils.h
3 | // ShadowsocksX-NG
4 | //
5 | // Created by 邱宇舟 on 16/6/8.
6 | // Copyright © 2016年 qiuyuzhou. All rights reserved.
7 | //
8 |
9 | #ifndef QRCodeUtils_h
10 | #define QRCodeUtils_h
11 |
12 | void ScanQRCodeOnScreen();
13 |
14 | #endif /* QRCodeUtils_h */
15 |
--------------------------------------------------------------------------------
/MinaOTP-MAC.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/MinaOTP-MAC/Magnet/BoolExtensions.swift:
--------------------------------------------------------------------------------
1 | //
2 | // BoolExtensions.swift
3 | // Magnet
4 | //
5 | // Created by 古林俊佑 on 2018/09/22.
6 | // Copyright © 2018年 Shunsuke Furubayashi. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | extension Bool {
12 | var intValue: Int {
13 | return NSNumber(value: self).intValue
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/MinaOTP-MAC/OTPGenerator/GeneratorTotp.h:
--------------------------------------------------------------------------------
1 | //
2 | // GeneratorTotp.h
3 | // Mina_OTP_OC
4 | //
5 | // Created by 武建明 on 2018/2/23.
6 | // Copyright © 2018年 Four_w. All rights reserved.
7 | //
8 |
9 | #import
10 |
11 | @interface GeneratorTotp : NSObject
12 |
13 | + (NSString *)generateOTPForSecret:(NSString *)secret;
14 |
15 | @end
16 |
--------------------------------------------------------------------------------
/.github/workflows/swift.yml:
--------------------------------------------------------------------------------
1 | name: MinaOTP-MAC
2 |
3 | on:
4 | push:
5 | branches: [ master ]
6 | pull_request:
7 | branches: [ master ]
8 |
9 | jobs:
10 | build:
11 |
12 | runs-on: macos-latest
13 |
14 | steps:
15 | - uses: actions/checkout@v2
16 | - name: Build
17 | run: swift build -v
18 | - name: Run tests
19 | run: swift test -v
20 |
--------------------------------------------------------------------------------
/MinaOTP-MAC/MinaOTP-MAC-Bridging-Header.h:
--------------------------------------------------------------------------------
1 | //
2 | // MinaOTP-MAC-Bridging-Header.h
3 | // MinaOTP-MAC
4 | //
5 | // Created by 武建明 on 2018/8/3.
6 | // Copyright © 2018年 Four_w. All rights reserved.
7 | //
8 |
9 | #ifndef MinaOTP_MAC_Bridging_Header_h
10 | #define MinaOTP_MAC_Bridging_Header_h
11 |
12 | #import "GeneratorTotp.h"
13 | #import "Utils.h"
14 |
15 | #endif /* MinaOTP_MAC_Bridging_Header_h */
16 |
--------------------------------------------------------------------------------
/MinaOTP-MAC.xcodeproj/xcuserdata/wubin.xcuserdatad/xcschemes/xcschememanagement.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | SchemeUserState
6 |
7 | MinaOTP-MAC.xcscheme
8 |
9 | orderHint
10 | 0
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/MinaOTP-MAC.xcodeproj/xcuserdata/Four.xcuserdatad/xcschemes/xcschememanagement.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | SchemeUserState
6 |
7 | MinaOTP-MAC.xcscheme_^#shared#^_
8 |
9 | orderHint
10 | 0
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/MinaOTP-MAC/Assets.xcassets/open.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "open@1x.png",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "filename" : "open@2x.png",
11 | "scale" : "2x"
12 | },
13 | {
14 | "idiom" : "universal",
15 | "scale" : "3x"
16 | }
17 | ],
18 | "info" : {
19 | "version" : 1,
20 | "author" : "xcode"
21 | }
22 | }
--------------------------------------------------------------------------------
/MinaOTP-MAC/Assets.xcassets/image_normal.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "image_normal@1x.png",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "filename" : "image_normal@2x.png",
11 | "scale" : "2x"
12 | },
13 | {
14 | "idiom" : "universal",
15 | "scale" : "3x"
16 | }
17 | ],
18 | "info" : {
19 | "version" : 1,
20 | "author" : "xcode"
21 | }
22 | }
--------------------------------------------------------------------------------
/MinaOTP-MAC/Assets.xcassets/image_selected.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "image_selected@1x.png",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "filename" : "image_selected@2x.png",
11 | "scale" : "2x"
12 | },
13 | {
14 | "idiom" : "universal",
15 | "scale" : "3x"
16 | }
17 | ],
18 | "info" : {
19 | "version" : 1,
20 | "author" : "xcode"
21 | }
22 | }
--------------------------------------------------------------------------------
/MinaOTP-MAC/Assets.xcassets/close.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "close@1x.png",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "filename" : "close@2x.png",
10 | "idiom" : "universal",
11 | "scale" : "2x"
12 | },
13 | {
14 | "idiom" : "universal",
15 | "scale" : "3x"
16 | }
17 | ],
18 | "info" : {
19 | "author" : "xcode",
20 | "version" : 1
21 | },
22 | "properties" : {
23 | "template-rendering-intent" : "template"
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/MinaOTP-MAC/Magnet/Magnet.h:
--------------------------------------------------------------------------------
1 | //
2 | // Magnet.h
3 | // Magnet
4 | //
5 | // Created by 古林俊佑 on 2016/03/09.
6 | // Copyright © 2016年 Shunsuke Furubayashi. All rights reserved.
7 | //
8 |
9 | #import
10 |
11 | //! Project version number for Magnet.
12 | FOUNDATION_EXPORT double MagnetVersionNumber;
13 |
14 | //! Project version string for Magnet.
15 | FOUNDATION_EXPORT const unsigned char MagnetVersionString[];
16 |
17 | // In this header, you should import all the public headers of your framework using statements like #import
18 |
19 |
20 |
--------------------------------------------------------------------------------
/MinaOTP-MAC/Assets.xcassets/StatusIcon.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "iconBlack@1x.png",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "filename" : "iconBlack@2x.png",
10 | "idiom" : "universal",
11 | "scale" : "2x"
12 | },
13 | {
14 | "idiom" : "universal",
15 | "scale" : "3x"
16 | }
17 | ],
18 | "info" : {
19 | "author" : "xcode",
20 | "version" : 1
21 | },
22 | "properties" : {
23 | "template-rendering-intent" : "template"
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/MinaOTP-MAC/OTPGenerator/Base32Addition.h:
--------------------------------------------------------------------------------
1 | //
2 | // Base32Addition.h
3 | // Mina_OTP_OC
4 | //
5 | // Created by 武建明 on 2018/4/11.
6 | // Copyright © 2018年 Four_w. All rights reserved.
7 | //
8 |
9 | #import
10 |
11 | #define NSBase32StringEncoding 0x4D467E32
12 |
13 | @interface NSString (Base32Addition)
14 | +(NSString *)stringFromBase32String:(NSString *)base32String;
15 | -(NSString *)base32String;
16 | @end
17 |
18 | @interface NSData (Base32Addition)
19 | +(NSData *)dataWithBase32String:(NSString *)base32String;
20 | -(NSString *)base32String;
21 | @end
22 |
23 | @interface Base32Addition : NSObject
24 |
25 | +(NSData *)dataFromBase32String:(NSString *)base32String;
26 | +(NSString *)base32StringFromData:(NSData *)data;
27 |
28 | @end
29 |
--------------------------------------------------------------------------------
/MinaOTP-MACTests/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | BNDL
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleVersion
20 | 1
21 |
22 |
23 |
--------------------------------------------------------------------------------
/MinaOTP-MACUITests/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | BNDL
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleVersion
20 | 1
21 |
22 |
23 |
--------------------------------------------------------------------------------
/MinaOTP-MAC/CSTableRowView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CSTableRowView.swift
3 | // MinaOTP-MAC
4 | //
5 | // Created by 武建明 on 2018/8/6.
6 | // Copyright © 2018年 Four_w. All rights reserved.
7 | //
8 |
9 | import Cocoa
10 | class CSTableRowView: NSTableRowView {
11 |
12 | override func drawSelection(in dirtyRect: NSRect) {
13 |
14 | }
15 | override func drawSeparator(in dirtyRect: NSRect) {
16 | var sepRect = self.bounds
17 | sepRect.origin.y = sepRect.maxY-1
18 | sepRect.size.height = 1;
19 | sepRect = NSIntersectionRect(sepRect, dirtyRect);
20 | if (!NSIsEmptyRect(sepRect)) {
21 | NSColor.init(red: 0.92, green: 0.92, blue: 0.92, alpha: 1).set()
22 | }else{
23 | NSColor.clear.set()
24 | }
25 | __NSRectFill(sepRect);
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/MinaOTP-MAC/MinaOTP-MAC.entitlements:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | com.apple.developer.aps-environment
6 | development
7 | com.apple.developer.icloud-container-identifiers
8 |
9 | iCloud.com.jiangren.minaotp
10 |
11 | com.apple.developer.icloud-services
12 |
13 | CloudKit
14 |
15 | com.apple.security.app-sandbox
16 |
17 | com.apple.security.files.user-selected.read-write
18 |
19 | com.apple.security.network.client
20 |
21 | com.apple.security.network.server
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/MinaOTP-MAC.xcodeproj/xcuserdata/wujianming.xcuserdatad/xcschemes/xcschememanagement.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | SchemeUserState
6 |
7 | MinaOTP-MAC.xcscheme
8 |
9 | orderHint
10 | 0
11 |
12 |
13 | SuppressBuildableAutocreation
14 |
15 | 20FBA95221106EB10017BFC3
16 |
17 | primary
18 |
19 |
20 | 20FBA96221106EB20017BFC3
21 |
22 | primary
23 |
24 |
25 | 20FBA96D21106EB20017BFC3
26 |
27 | primary
28 |
29 |
30 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/MinaOTP-MAC.xcodeproj/xcuserdata/rawlings.xcuserdatad/xcschemes/xcschememanagement.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | SchemeUserState
6 |
7 | MinaOTP-MAC.xcscheme_^#shared#^_
8 |
9 | orderHint
10 | 0
11 |
12 |
13 | SuppressBuildableAutocreation
14 |
15 | 20FBA95221106EB10017BFC3
16 |
17 | primary
18 |
19 |
20 | 20FBA96221106EB20017BFC3
21 |
22 | primary
23 |
24 |
25 | 20FBA96D21106EB20017BFC3
26 |
27 | primary
28 |
29 |
30 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/MinaOTP-MAC/Magnet/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | FMWK
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | $(CURRENT_PROJECT_VERSION)
23 | NSHumanReadableCopyright
24 | Copyright © 2016年 Shunsuke Furubayashi. All rights reserved.
25 | NSPrincipalClass
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/MinaOTP-MACTests/MinaOTP_MACTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // MinaOTP_MACTests.swift
3 | // MinaOTP-MACTests
4 | //
5 | // Created by 武建明 on 2018/7/31.
6 | // Copyright © 2018年 Four_w. All rights reserved.
7 | //
8 |
9 | import XCTest
10 | //@testable import MinaOTP_MAC
11 |
12 | class MinaOTP_MACTests: XCTestCase {
13 |
14 | override func setUp() {
15 | super.setUp()
16 | // Put setup code here. This method is called before the invocation of each test method in the class.
17 | }
18 |
19 | override func tearDown() {
20 | // Put teardown code here. This method is called after the invocation of each test method in the class.
21 | super.tearDown()
22 | }
23 |
24 | func testExample() {
25 | // This is an example of a functional test case.
26 | // Use XCTAssert and related functions to verify your tests produce the correct results.
27 | }
28 |
29 | func testPerformanceExample() {
30 | // This is an example of a performance test case.
31 | self.measure {
32 | // Put the code you want to measure the time of here.
33 | }
34 | }
35 |
36 | }
37 |
--------------------------------------------------------------------------------
/MinaOTP-MAC/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIconFile
10 |
11 | CFBundleIdentifier
12 | $(PRODUCT_BUNDLE_IDENTIFIER)
13 | CFBundleInfoDictionaryVersion
14 | 6.0
15 | CFBundleName
16 | $(PRODUCT_NAME)
17 | CFBundlePackageType
18 | APPL
19 | CFBundleShortVersionString
20 | $(MARKETING_VERSION)
21 | CFBundleVersion
22 | 1
23 | LSMinimumSystemVersion
24 | $(MACOSX_DEPLOYMENT_TARGET)
25 | LSUIElement
26 |
27 | NSHumanReadableCopyright
28 | Copyright © 2018年 Four_w. All rights reserved.
29 | NSMainNibFile
30 | MainMenu
31 | NSPrincipalClass
32 | NSApplication
33 |
34 |
35 |
--------------------------------------------------------------------------------
/MinaOTP-MAC/zh-Hans.lproj/Localizable.strings:
--------------------------------------------------------------------------------
1 | /*
2 | Localizable.strings
3 | MinaOTP-MAC
4 |
5 | Created by 武建明 on 2018/8/13.
6 | Copyright © 2018年 Four_w. All rights reserved.
7 | */
8 | "add" = "添加";
9 | "export" = "导出";
10 | "import" = "导入";
11 | "add_title" = "添加二步验证";
12 | "scan_title" = "扫描二维码图片";
13 | "choose_qr_image_btn_title" = "选取二维码图片";
14 | "scan_qr_image_btn_title" = "扫描二维码图片";
15 | "scan_qr_success" = "扫描成功, 1秒后将自动关闭";
16 | "save" = "保存";
17 | "cancel" = "取消";
18 | "remark_placeholder" = "请输入remark:";
19 | "issuer_placeholder" = "请输入issuer:";
20 | "secret_placeholder" = "请输入secret:";
21 | "add_success_tip" = "添加成功,请返回程序查看";
22 | "image_error_tip" = "非二维码图片";
23 | "image_content_error_tip" = "图片内容格式不正确";
24 | "tool_tip" = "祝:每天开心,代码无BUG";
25 | "export_help" = "下载导入文件格式";
26 | "help" = "帮助";
27 | "exit" = "退出";
28 | "export_title" = "选择文件导出位置";
29 | "delete" = "删除";
30 | "edit" = "编辑";
31 | "export_sucess" = "导出成功";
32 | "export_failure" = "导出失败,请重试";
33 | "parse_failed" = "导入失败,请提供正确的Json格式";
34 | "choose_title" = "选择";
35 | "copy_success" = "复制成功";
36 | "check_releases" = "检测更新";
37 | "confirm" = "确定";
38 | "has_new_version" = "呀哈哈! 你发现了新版本更新。\n请前往[https://github.com/MinaOTP/MinaOTP-MAC/releases]进行更新吧!";
39 | "no_new_version" = "呀哈哈! 你的版本是最新的哦!";
40 |
41 |
--------------------------------------------------------------------------------
/MinaOTP-MAC/ShowTips.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ShowTips.swift
3 | // MinaOTP-MAC
4 | //
5 | // Created by 武建明 on 2018/8/6.
6 | // Copyright © 2018年 Four_w. All rights reserved.
7 | //
8 |
9 | import Cocoa
10 |
11 | class ShowTips: NSObject {
12 |
13 | public func showTip(message: String, view: NSView) {
14 | self.tipTextField.stringValue = " \(message) "
15 | tipTextField.sizeToFit()
16 | tipTextField.frame = NSRect(x: (view.bounds.size.width-tipTextField.bounds.size.width)/2, y: (view.bounds.size.height-tipTextField.bounds.size.height)/2, width: tipTextField.bounds.size.width, height: tipTextField.bounds.size.height+5)
17 | view.addSubview(tipTextField)
18 | DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
19 | self.tipTextField.removeFromSuperview()
20 | }
21 | }
22 | lazy var tipTextField:NSTextField! = {
23 | let tip = NSTextField.init(frame: NSRect(x: 100, y: 100, width: 100, height: 30))
24 | tip.wantsLayer = true
25 | tip.textColor = NSColor.white
26 | tip.isBordered = false
27 | tip.backgroundColor = NSColor.init(red: 0, green: 0, blue: 0, alpha: 0.5)
28 | tip.font = NSFont.systemFont(ofSize: 18)
29 | tip.layer?.cornerRadius = 2
30 | return tip
31 | }()
32 | }
33 |
--------------------------------------------------------------------------------
/MinaOTP-MACUITests/MinaOTP_MACUITests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // MinaOTP_MACUITests.swift
3 | // MinaOTP-MACUITests
4 | //
5 | // Created by 武建明 on 2018/7/31.
6 | // Copyright © 2018年 Four_w. All rights reserved.
7 | //
8 |
9 | import XCTest
10 |
11 | class MinaOTP_MACUITests: XCTestCase {
12 |
13 | override func setUp() {
14 | super.setUp()
15 |
16 | // Put setup code here. This method is called before the invocation of each test method in the class.
17 |
18 | // In UI tests it is usually best to stop immediately when a failure occurs.
19 | continueAfterFailure = false
20 | // UI tests must launch the application that they test. Doing this in setup will make sure it happens for each test method.
21 | XCUIApplication().launch()
22 |
23 | // In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this.
24 | }
25 |
26 | override func tearDown() {
27 | // Put teardown code here. This method is called after the invocation of each test method in the class.
28 | super.tearDown()
29 | }
30 |
31 | func testExample() {
32 | // Use recording to get started writing UI tests.
33 | // Use XCTAssert and related functions to verify your tests produce the correct results.
34 | }
35 |
36 | }
37 |
--------------------------------------------------------------------------------
/MinaOTP-MAC/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "size" : "16x16",
5 | "idiom" : "mac",
6 | "filename" : "icon_16x16.png",
7 | "scale" : "1x"
8 | },
9 | {
10 | "size" : "16x16",
11 | "idiom" : "mac",
12 | "filename" : "icon_16x16@2x.png",
13 | "scale" : "2x"
14 | },
15 | {
16 | "size" : "32x32",
17 | "idiom" : "mac",
18 | "filename" : "icon_32x32.png",
19 | "scale" : "1x"
20 | },
21 | {
22 | "size" : "32x32",
23 | "idiom" : "mac",
24 | "filename" : "icon_32x32@2x.png",
25 | "scale" : "2x"
26 | },
27 | {
28 | "size" : "128x128",
29 | "idiom" : "mac",
30 | "filename" : "icon_128x128.png",
31 | "scale" : "1x"
32 | },
33 | {
34 | "size" : "128x128",
35 | "idiom" : "mac",
36 | "filename" : "icon_128x128@2x.png",
37 | "scale" : "2x"
38 | },
39 | {
40 | "size" : "256x256",
41 | "idiom" : "mac",
42 | "filename" : "icon_256x256.png",
43 | "scale" : "1x"
44 | },
45 | {
46 | "size" : "256x256",
47 | "idiom" : "mac",
48 | "filename" : "icon_256x256@2x.png",
49 | "scale" : "2x"
50 | },
51 | {
52 | "size" : "512x512",
53 | "idiom" : "mac",
54 | "filename" : "icon_512x512.png",
55 | "scale" : "1x"
56 | },
57 | {
58 | "size" : "512x512",
59 | "idiom" : "mac",
60 | "filename" : "icon_512x512@2x.png",
61 | "scale" : "2x"
62 | }
63 | ],
64 | "info" : {
65 | "version" : 1,
66 | "author" : "xcode"
67 | }
68 | }
--------------------------------------------------------------------------------
/MinaOTP-MAC/en.lproj/Localizable.strings:
--------------------------------------------------------------------------------
1 | /*
2 | Localizable.strings
3 | MinaOTP-MAC
4 |
5 | Created by 武建明 on 2018/8/13.
6 | Copyright © 2018年 Four_w. All rights reserved.
7 | */
8 | "add" = "Add";
9 | "export" = "Export";
10 | "import" = "Import";
11 | "add_title" = "Add Two-factor Authentication";
12 | "scan_title" = "Scan Qr Code Image";
13 | "choose_qr_image_btn_title" = "Choose Qr Code Image";
14 | "scan_qr_image_btn_title" = "Scan Qr Code Image";
15 | "scan_qr_success" = "Scan is successful, will automatically shut down after 1 second";
16 | "save" = "Save";
17 | "cancel" = "Cancel";
18 | "remark_placeholder" = "Please enter the remark:";
19 | "issuer_placeholder" = "Please enter the issuer:";
20 | "secret_placeholder" = "Please enter the secret:";
21 | "add_success_tip" = "Added successfully, please return it to look at it";
22 | "image_error_tip" = "It's not qr code image";
23 | "image_content_error_tip" = "Qr code image content format is not correct";
24 | "tool_tip" = "Wish you have a nice day";
25 | "export_help" = "Download the format of import file";
26 | "help" = "Help";
27 | "exit" = "Exit";
28 | "export_title" = "Select the file export position";
29 | "delete" = "Delete";
30 | "edit" = "Edit";
31 | "export_sucess" = "Exported successfully";
32 | "export_failure" = "Export of failure, please try again";
33 | "parse_failed" = "Parse failed, please provide the correct json content";
34 | "choose_title" = "Choose";
35 | "copy_success" = "Copy Success";
36 | "check_releases" = "Check Update";
37 | "confirm" = "Confirm";
38 | "has_new_version" = "Ah Ha Ha! You have found the new version! \n Please go to [https://github.com/MinaOTP/MinaOTP-MAC/releases]! to update";
39 | "no_new_version" = "Ah Ha Ha! Your version is the latest!";
40 |
41 |
--------------------------------------------------------------------------------
/MinaOTP-MAC/StatusItemView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // StatusItemView.swift
3 | // MinaOTP-MAC
4 | //
5 | // Created by 武建明 on 2018/8/8.
6 | // Copyright © 2018年 Four_w. All rights reserved.
7 | //
8 |
9 | import Cocoa
10 | class StatusItemView: NSControl {
11 |
12 | var statusItem = NSStatusItem.init()
13 | let image = NSImage(named: "icon")
14 | weak var delegate : StatusItemViewDelegate?
15 |
16 | override init(frame frameRect: NSRect) {
17 | super.init(frame: NSZeroRect)
18 | self.wantsLayer = true
19 | self.layer?.backgroundColor = NSColor.clear.cgColor
20 | }
21 |
22 | required init?(coder decoder: NSCoder) {
23 | fatalError("init(coder:) has not been implemented")
24 | }
25 | override func draw(_ dirtyRect: NSRect) {
26 | super.draw(dirtyRect)
27 | statusItem.drawStatusBarBackground(in: self.bounds, withHighlight: false)
28 | let imageRect = NSInsetRect(NSRect(x: (self.bounds.size.width-18)/2, y: (self.bounds.size.height-18)/2, width: 18, height: 18), 0, 0)
29 | self.image?.draw(in: imageRect, from: NSZeroRect, operation: .color, fraction: 1)
30 | }
31 | override func moveDown(_ sender: Any?) {
32 | self.setNeedsDisplay()
33 | }
34 | override func rightMouseDown(with event: NSEvent) {
35 | // NSApp.sendAction(righeAction, to: self.target, from: self)
36 | self.setNeedsDisplay()
37 | self.delegate?.rightMouseDownAction()
38 | }
39 | override func mouseDown(with event: NSEvent) {
40 | self.setNeedsDisplay()
41 | self.delegate?.leftMouseDownAction()
42 | }
43 |
44 | }
45 | protocol StatusItemViewDelegate: class {
46 | func rightMouseDownAction()
47 | func leftMouseDownAction()
48 | }
49 |
50 |
51 |
--------------------------------------------------------------------------------
/MinaOTP-MAC/OTPGenerator/GeneratorTotp.m:
--------------------------------------------------------------------------------
1 | //
2 | // GeneratorTotp.m
3 | // Mina_OTP_OC
4 | //
5 | // Created by 武建明 on 2018/2/23.
6 | // Copyright © 2018年 Four_w. All rights reserved.
7 | //
8 |
9 | #import "GeneratorTotp.h"
10 | #include
11 | #include
12 | #import "Base32Addition.h"
13 |
14 | static NSUInteger kPinModTable[] = {
15 | 0,
16 | 10,
17 | 100,
18 | 1000,
19 | 10000,
20 | 100000,
21 | 1000000,
22 | 10000000,
23 | 100000000,
24 | };
25 |
26 | @implementation GeneratorTotp
27 |
28 | + (NSTimeInterval)defaultPeriod {
29 | return 30;
30 | }
31 | + (NSString *)generateOTPForSecret:(NSString *)secret{
32 |
33 | CCHmacAlgorithm alg = kCCHmacAlgSHA1;
34 | NSUInteger hashLength = CC_SHA1_DIGEST_LENGTH;
35 |
36 | NSData *secretData = [NSData dataWithBase32String:secret];
37 |
38 | NSDate *date = [NSDate date];
39 | NSLog(@"%@",date);
40 | NSTimeInterval seconds = [date timeIntervalSince1970];
41 |
42 | uint64_t counter = (uint64_t)(seconds / [self defaultPeriod]);
43 |
44 | counter = NSSwapHostLongLongToBig(counter);
45 | NSData *counterData = [NSData dataWithBytes:&counter
46 | length:sizeof(counter)];
47 |
48 | NSMutableData *hash = [NSMutableData dataWithLength:hashLength];
49 | CCHmacContext ctx;
50 | CCHmacInit(&ctx, alg, [secretData bytes], [secretData length]);
51 | CCHmacUpdate(&ctx, [counterData bytes], [counterData length]);
52 | CCHmacFinal(&ctx, [hash mutableBytes]);
53 | const char *ptr = [hash bytes];
54 | unsigned char offset = ptr[hashLength-1] & 0x0f;
55 |
56 | uint32_t truncatedHash =
57 | NSSwapBigIntToHost(*((uint32_t *)&ptr[offset])) & 0x7fffffff;
58 | uint32_t pinValue = truncatedHash % kPinModTable[6];
59 |
60 | NSString *str = [NSString stringWithFormat:@"%0*u", (int)6, pinValue];;
61 |
62 | while (str.length < 6) {
63 | str = [NSString stringWithFormat:@"0%@",str];
64 | }
65 | return str;
66 | }
67 | @end
68 |
--------------------------------------------------------------------------------
/README_zh.md:
--------------------------------------------------------------------------------
1 | # MinaOTP-MAC
2 |
3 | [](https://github.com/MinaOTP/MinaOTP-MAC) [](https://github.com/MinaOTP/MinaOTP-MAC/releases) [](https://github.com/MinaOTP/MinaOTP-MTP)
4 |
6 | MinaOTP-MAC 是一款运行在macOS上的App. 是基于[RFC6238](https://tools.ietf.org/html/rfc6238)算法, 算法实现语言是 `Objective-C`。App功能开发基于Swift
7 |
8 | 该App能够很方便的为你提供二步验证码。包括对Token的添加、编辑、删除、导入、导出等功能
9 |
10 | ## 依赖
11 |
12 | - macOS 10.10+
13 | - Xcode 9.4.1+
14 | - Swift 4.1
15 |
16 | ### 软件截图
17 |
18 | 首页
19 |
20 | 
21 |
22 | 鼠标右键点击弹出删除和编辑
23 |
24 | 
25 |
26 | 编辑
27 |
28 | 
29 |
30 | 鼠标右键点击状态栏Icon弹出帮助页面
31 |
32 | 
33 |
34 | 添加Token
35 |
36 | 
37 |
38 | 扫描二维码
39 |
40 | 
41 | 
42 |
43 |
44 | ### 功能
45 |
46 | * 生成 2FA token
47 | * 选择一个二维码图片,识别并添加
48 | * 扫描一个二维码图片,识别并添加
49 | * 手动添加
50 | * 编辑所有信息
51 | * 删除
52 | * 导出token信息,生成本地的Json文件
53 | * 从本地的Json文件,添加Token信息
54 | * 快捷键: ⌘⌘ (双击 ⌘ 键)
55 |
56 | ### 将来
57 | * [x] 同步到icloud
58 | * [ ] 添加快捷键用来复制token
59 |
60 | ### 感谢
61 | [FlatButton](https://github.com/OskarGroth/FlatButton)
62 | [Magnet](https://github.com/Clipy/Magnet)
63 | Thanks for their great work.
64 |
65 | ### [README](README.md)
--------------------------------------------------------------------------------
/MinaOTP-MAC/ProgressView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ProgressView.swift
3 | // MinaOTP-MAC
4 | //
5 | // Created by 武建明 on 2018/8/3.
6 | // Copyright © 2018年 Four_w. All rights reserved.
7 | //
8 |
9 | import Cocoa
10 |
11 | class ProgressView: NSView {
12 |
13 | override init(frame frameRect: NSRect) {
14 | super.init(frame: frameRect)
15 | print("已经创建")
16 | self.wantsLayer = true
17 | self.layer?.addSublayer(self.bottomShapeLayer)
18 | self.layer?.addSublayer(self.topShapeLayer)
19 | }
20 |
21 | required init?(coder decoder: NSCoder) {
22 | fatalError("init(coder:) has not been implemented")
23 | }
24 |
25 | lazy var bottomShapeLayer: CAShapeLayer = {
26 | let layer = CAShapeLayer.init()
27 | layer.frame = self.bounds
28 | layer.fillColor = NSColor.clear.cgColor
29 | layer.lineWidth = 2
30 | layer.lineJoin = CAShapeLayerLineJoin.miter
31 | layer.lineCap = CAShapeLayerLineCap.square
32 | layer.strokeColor = NSColor.init(red: 0.9, green: 0.9, blue: 0.9, alpha: 1).cgColor
33 | layer.path = self.bezierPath.cgPath
34 | layer.strokeEnd = 1
35 | return layer
36 | }()
37 | lazy var topShapeLayer: CAShapeLayer = {
38 | let layer = CAShapeLayer.init()
39 | layer.fillColor = NSColor.clear.cgColor
40 | layer.frame = self.bounds
41 | layer.lineWidth = 2
42 | layer.lineJoin = CAShapeLayerLineJoin.miter
43 | layer.lineCap = CAShapeLayerLineCap.square
44 | layer.strokeColor = NSColor.mainColor.cgColor
45 | layer.path = self.bezierPath.cgPath
46 | layer.strokeEnd = 0
47 | return layer
48 | }()
49 | lazy var bezierPath: NSBezierPath = {
50 | let path = NSBezierPath.init()
51 | path.move(to: CGPoint(x: 0, y: self.bounds.size.height/2))
52 | path.line(to: CGPoint(x: self.bounds.size.width, y: self.bounds.size.height/2))
53 | path.stroke()
54 | return path
55 | }()
56 | func setProgress(value: CGFloat) {
57 | // if value>0.85 {
58 | // self.topShapeLayer.strokeColor = NSColor.red.cgColor
59 | //
60 | // }else{
61 | // self.topShapeLayer.strokeColor = NSColor.green.cgColor
62 | // }
63 | self.topShapeLayer.strokeEnd = value
64 | self.topShapeLayer.strokeColor = NSColor.mainColor.cgColor
65 | // self.topShapeLayer.strokeColor = NSColor.init(red: value, green: (1.0-value)*3, blue: 0, alpha: 1).cgColor
66 | }
67 |
68 |
69 | }
70 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # MinaOTP-MAC
2 |
3 | [](https://github.com/MinaOTP/MinaOTP-MAC) [](https://github.com/MinaOTP/MinaOTP-MAC/releases) [](https://github.com/MinaOTP/MinaOTP-MTP)
4 |
6 | MinaOTP-MAC is a two-factor authentication tray app that runs at macOS. It's based on [RFC6238](https://tools.ietf.org/html/rfc6238), and the algorithm was implement by `Objective-C`
7 |
8 | The program will generate secure dynamic 2FA tokens for you, and the `add`, `edit`, `remove`,`import`, `export` are pretty convenient.
9 |
10 | ## Requirements
11 |
12 | - macOS 10.10+
13 | - Xcode 9.4.1+
14 | - Swift 4.1
15 |
16 | ### Software Screenshot
17 |
18 | Home
19 |
20 | 
21 |
22 | right click the item to delete or edit
23 |
24 | 
25 |
26 | Edit
27 |
28 | 
29 |
30 | right click the statusbar to get help
31 |
32 | 
33 |
34 | Add Token
35 |
36 | 
37 |
38 | Scan Qr Code
39 |
40 | 
41 | 
42 |
43 |
44 | ### Feature
45 |
46 | * Generate the 2FA token
47 | * Choose a qrcode_image to add a new token
48 | * Scan a qrcode_image to add a new token
49 | * Add a new token mannually
50 | * Edit the issuer and remark info
51 | * Remove a existed token
52 | * Backup datas to local json files
53 | * Import datas from local json files
54 | * Hotkey: ⌘⌘ (double click ⌘)
55 |
56 | ### Todo
57 | * [x] sync to icloud
58 | * [ ] add hotkey to copy token
59 |
60 | ### Acknowledgements
61 | [FlatButton](https://github.com/OskarGroth/FlatButton)
62 | [Magnet](https://github.com/Clipy/Magnet)
63 | Thanks for their great work.
64 |
65 | ### [中文文档](README_zh.md)
--------------------------------------------------------------------------------
/MinaOTP-MAC/Utils.m:
--------------------------------------------------------------------------------
1 | //
2 | // QRCodeUtils.m
3 | // ShadowsocksX-NG
4 | //
5 | // Created by 邱宇舟 on 16/6/8.
6 | // Copyright © 2016年 qiuyuzhou. All rights reserved.
7 | //
8 |
9 | #import
10 | #import
11 |
12 | void ScanQRCodeOnScreen() {
13 | /* displays[] Quartz display ID's */
14 | CGDirectDisplayID *displays = nil;
15 |
16 | CGError err = CGDisplayNoErr;
17 | CGDisplayCount dspCount = 0;
18 |
19 | /* How many active displays do we have? */
20 | err = CGGetActiveDisplayList(0, NULL, &dspCount);
21 |
22 | /* If we are getting an error here then their won't be much to display. */
23 | if(err != CGDisplayNoErr)
24 | {
25 | NSLog(@"Could not get active display count (%d)\n", err);
26 | return;
27 | }
28 |
29 | /* Allocate enough memory to hold all the display IDs we have. */
30 | displays = calloc((size_t)dspCount, sizeof(CGDirectDisplayID));
31 |
32 | // Get the list of active displays
33 | err = CGGetActiveDisplayList(dspCount,
34 | displays,
35 | &dspCount);
36 |
37 | /* More error-checking here. */
38 | if(err != CGDisplayNoErr)
39 | {
40 | NSLog(@"Could not get active display list (%d)\n", err);
41 | return;
42 | }
43 |
44 | // NSMutableArray* foundSSUrls = [NSMutableArray array];
45 |
46 | CIDetector *detector = [CIDetector detectorOfType:@"CIDetectorTypeQRCode"
47 | context:nil
48 | options:@{ CIDetectorAccuracy:CIDetectorAccuracyHigh }];
49 |
50 | for (unsigned int displaysIndex = 0; displaysIndex < dspCount; displaysIndex++){
51 | /* Make a snapshot image of the current display. */
52 | CGImageRef image = CGDisplayCreateImage(displays[displaysIndex]);
53 | NSArray *features = [detector featuresInImage:[CIImage imageWithCGImage:image]];
54 |
55 | NSLog(@"=====features:\n%@", features);
56 |
57 | for (CIQRCodeFeature *feature in features) {
58 | NSLog(@"%@", feature.messageString);
59 | // if ( [feature.messageString hasPrefix:@"ss://"] )
60 | // {
61 | // NSURL *url = [NSURL URLWithString:feature.messageString];
62 | // if (url) {
63 | // [foundSSUrls addObject:url];
64 | // }
65 | // }
66 | }
67 | CGImageRelease(image);
68 | }
69 |
70 | free(displays);
71 |
72 | // [[NSNotificationCenter defaultCenter]
73 | // postNotificationName:@"NOTIFY_FOUND_SS_URL"
74 | // object:nil
75 | // userInfo: @{ @"urls": foundSSUrls,
76 | // @"source": @"qrcode"
77 | // }
78 | // ];
79 | }
80 |
--------------------------------------------------------------------------------
/MinaOTP-MAC/CellView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CellView.swift
3 | // MinaOTP-MAC
4 | //
5 | // Created by 武建明 on 2018/8/3.
6 | // Copyright © 2018年 Four_w. All rights reserved.
7 | //
8 |
9 | import Cocoa
10 |
11 | class CellView: NSView {
12 | override init(frame frameRect: NSRect) {
13 | super.init(frame: frameRect)
14 | self.addSubview(self.codeTextField)
15 | self.addSubview(self.issuerTextField)
16 | self.addSubview(self.remarkTextField)
17 | // self.addSubview(self.hotKeyTextField)
18 | self.wantsLayer = true
19 | self.layer?.backgroundColor = NSColor.clear.cgColor
20 | self.layer?.addSublayer(self.lineLayer)
21 | print(self.bounds.size.height/2)
22 | }
23 |
24 | lazy var codeTextField: NSTextField = {
25 | let lab = NSTextField.init(frame: CGRect(x: 198, y: 30, width: 90, height: 20))
26 | lab.textColor = .labelColor //NSColor(red: 0, green: 0, blue: 0, alpha: 0.8)
27 | lab.stringValue = ""
28 | lab.isBordered = false
29 | lab.isEditable = false
30 | lab.font = NSFont.boldSystemFont(ofSize: 20)
31 | lab.alignment = NSTextAlignment.right
32 | lab.backgroundColor = NSColor.clear
33 | // lab.backgroundColor = NSColor.red
34 | return lab
35 | }()
36 | lazy var remarkTextField: NSTextField = {
37 | let lab = NSTextField.init(frame: CGRect(x: 12, y: 30, width: 160, height: 20))
38 | lab.textColor = .labelColor //NSColor(red: 0, green: 0, blue: 0, alpha: 0.8)
39 | lab.stringValue = ""
40 | lab.isBordered = false
41 | lab.isEditable = false
42 | lab.backgroundColor = NSColor.clear
43 | lab.alignment = NSTextAlignment.left
44 | lab.font = NSFont.systemFont(ofSize: 14)
45 | // lab.backgroundColor = NSColor.red
46 | return lab
47 | }()
48 | lazy var issuerTextField: NSTextField = {
49 | let lab = NSTextField.init(frame: CGRect(x: 14, y: 10, width: 100, height: 12))
50 | lab.textColor = .labelColor //NSColor(red: 0, green: 0, blue: 0, alpha: 0.8)
51 | lab.stringValue = ""
52 | lab.isBordered = false
53 | lab.isEditable = false
54 | lab.backgroundColor = NSColor.clear
55 | lab.alignment = NSTextAlignment.left
56 | lab.font = NSFont.systemFont(ofSize: 10)
57 | return lab
58 | }()
59 | lazy var hotKeyTextField: NSTextField = {
60 | let lab = NSTextField.init(frame: CGRect(x: 263, y: 10, width: 25, height: 12))
61 | lab.textColor = .labelColor // NSColor(red: 0, green: 0, blue: 0, alpha: 0.8)
62 | lab.stringValue = ""
63 | lab.isBordered = false
64 | lab.isEditable = false
65 | lab.backgroundColor = NSColor.clear
66 | lab.alignment = NSTextAlignment.right
67 | lab.font = NSFont.systemFont(ofSize: 10)
68 | return lab
69 | }()
70 |
71 | lazy var lineLayer: CALayer = {
72 | let line = CALayer()
73 | line.frame = CGRect(x: 0, y: 0, width: self.bounds.size.width, height: 1)
74 | line.backgroundColor = NSColor.init(red: 0.6, green: 0.6, blue: 0.6, alpha: 0.5).cgColor
75 | return line
76 | }()
77 | required init?(coder decoder: NSCoder) {
78 | fatalError("init(coder:) has not been implemented")
79 | }
80 |
81 | }
82 |
--------------------------------------------------------------------------------
/MinaOTP-MAC/Magnet/HotKey.swift:
--------------------------------------------------------------------------------
1 | //
2 | // HotKey.swift
3 | // Magnet
4 | //
5 | // Created by 古林俊佑 on 2016/03/09.
6 | // Copyright © 2016年 Shunsuke Furubayashi. All rights reserved.
7 | //
8 |
9 | import Cocoa
10 | import Carbon
11 |
12 | public final class HotKey: Equatable {
13 |
14 | // MARK: - Properties
15 | public let identifier: String
16 | public let keyCombo: KeyCombo
17 | public let callback: ((HotKey) -> Void)?
18 | public let target: AnyObject?
19 | public let action: Selector?
20 | public let actionQueue: ActionQueue
21 |
22 | var hotKeyId: UInt32?
23 | var hotKeyRef: EventHotKeyRef?
24 |
25 | // MARK: - Enum Value
26 | public enum ActionQueue {
27 | case main
28 | case session
29 |
30 | public func execute(closure: @escaping () -> Void) {
31 | switch self {
32 | case .main:
33 | DispatchQueue.main.async {
34 | closure()
35 | }
36 | case .session:
37 | closure()
38 | }
39 | }
40 | }
41 |
42 | // MARK: - Initialize
43 | public init(identifier: String, keyCombo: KeyCombo, target: AnyObject, action: Selector, actionQueue: ActionQueue = .main) {
44 | self.identifier = identifier
45 | self.keyCombo = keyCombo
46 | self.callback = nil
47 | self.target = target
48 | self.action = action
49 | self.actionQueue = actionQueue
50 | }
51 |
52 | public init(identifier: String, keyCombo: KeyCombo, actionQueue: ActionQueue = .main, handler: @escaping ((HotKey) -> Void)) {
53 | self.identifier = identifier
54 | self.keyCombo = keyCombo
55 | self.callback = handler
56 | self.target = nil
57 | self.action = nil
58 | self.actionQueue = actionQueue
59 | }
60 |
61 | }
62 |
63 | // MARK: - Invoke
64 | public extension HotKey {
65 | func invoke() {
66 | guard let callback = self.callback else {
67 | guard let target = self.target as? NSObject, let selector = self.action else { return }
68 | guard target.responds(to: selector) else { return }
69 | actionQueue.execute { [weak self] in
70 | guard let wSelf = self else { return }
71 | target.perform(selector, with: wSelf)
72 | }
73 | return
74 | }
75 | actionQueue.execute { [weak self] in
76 | guard let wSelf = self else { return }
77 | callback(wSelf)
78 | }
79 | }
80 | }
81 |
82 | // MARK: - Register & UnRegister
83 | public extension HotKey {
84 | @discardableResult
85 | func register() -> Bool {
86 | return HotKeyCenter.shared.register(with: self)
87 | }
88 |
89 | func unregister() {
90 | return HotKeyCenter.shared.unregister(with: self)
91 | }
92 | }
93 |
94 | // MARK: - Equatable
95 | public func == (lhs: HotKey, rhs: HotKey) -> Bool {
96 | return lhs.identifier == rhs.identifier &&
97 | lhs.keyCombo == rhs.keyCombo &&
98 | lhs.hotKeyId == rhs.hotKeyId &&
99 | lhs.hotKeyRef == rhs.hotKeyRef
100 | }
101 |
--------------------------------------------------------------------------------
/MinaOTP-MAC/Magnet/KeyCombo.swift:
--------------------------------------------------------------------------------
1 | //
2 | // KeyCombo.swift
3 | // Magnet
4 | //
5 | // Created by 古林俊佑 on 2016/03/09.
6 | // Copyright © 2016年 Shunsuke Furubayashi. All rights reserved.
7 | //
8 |
9 | import Cocoa
10 | import Carbon
11 |
12 | public final class KeyCombo: NSObject, NSCopying, NSCoding, Codable {
13 |
14 | // MARK: - Properties
15 | public let keyCode: Int
16 | public let modifiers: Int
17 | public let doubledModifiers: Bool
18 | public var characters: String {
19 | if doubledModifiers { return "" }
20 | return KeyCodeTransformer.shared.transformValue(keyCode, carbonModifiers: modifiers)
21 | }
22 |
23 | // MARK: - Initialize
24 | public init?(keyCode: Int, carbonModifiers: Int) {
25 | if keyCode < 0 || carbonModifiers < 0 { return nil }
26 |
27 | if KeyTransformer.containsFunctionKey(keyCode) {
28 | self.modifiers = Int(UInt(carbonModifiers) | NSEvent.ModifierFlags.function.rawValue)
29 | } else {
30 | self.modifiers = carbonModifiers
31 | }
32 | self.keyCode = keyCode
33 | self.doubledModifiers = false
34 | }
35 |
36 | public init?(keyCode: Int, cocoaModifiers: NSEvent.ModifierFlags) {
37 | if keyCode < 0 || !KeyTransformer.supportedCocoaFlags(cocoaModifiers) { return nil }
38 |
39 | if KeyTransformer.containsFunctionKey(keyCode) {
40 | self.modifiers = Int(UInt(KeyTransformer.carbonFlags(from: cocoaModifiers)) | NSEvent.ModifierFlags.function.rawValue)
41 | } else {
42 | self.modifiers = KeyTransformer.carbonFlags(from: cocoaModifiers)
43 | }
44 | self.keyCode = keyCode
45 | self.doubledModifiers = false
46 | }
47 |
48 | public init?(doubledCarbonModifiers modifiers: Int) {
49 | if !KeyTransformer.singleCarbonFlags(modifiers) { return nil }
50 |
51 | self.keyCode = 0
52 | self.modifiers = modifiers
53 | self.doubledModifiers = true
54 | }
55 |
56 | public init?(doubledCocoaModifiers modifiers: NSEvent.ModifierFlags) {
57 | if !KeyTransformer.singleCocoaFlags(modifiers) { return nil }
58 |
59 | self.keyCode = 0
60 | self.modifiers = KeyTransformer.carbonFlags(from: modifiers)
61 | self.doubledModifiers = true
62 | }
63 |
64 | public func copy(with zone: NSZone?) -> Any {
65 | if doubledModifiers {
66 | return KeyCombo(doubledCarbonModifiers: modifiers)!
67 | } else {
68 | return KeyCombo(keyCode: keyCode, carbonModifiers: modifiers)!
69 | }
70 | }
71 |
72 | public init?(coder aDecoder: NSCoder) {
73 | self.keyCode = aDecoder.decodeInteger(forKey: "keyCode")
74 | self.modifiers = aDecoder.decodeInteger(forKey: "modifiers")
75 | self.doubledModifiers = aDecoder.decodeBool(forKey: "doubledModifiers")
76 | }
77 |
78 | public func encode(with aCoder: NSCoder) {
79 | aCoder.encode(keyCode, forKey: "keyCode")
80 | aCoder.encode(modifiers, forKey: "modifiers")
81 | aCoder.encode(doubledModifiers, forKey: "doubledModifiers")
82 | }
83 |
84 | // MARK: - Equatable
85 | public override func isEqual(_ object: Any?) -> Bool {
86 | guard let keyCombo = object as? KeyCombo else { return false }
87 | return keyCode == keyCombo.keyCode &&
88 | modifiers == keyCombo.modifiers &&
89 | doubledModifiers == keyCombo.doubledModifiers
90 | }
91 | }
92 |
--------------------------------------------------------------------------------
/MinaOTP-MAC.xcodeproj/xcshareddata/xcschemes/MinaOTP-MAC.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
33 |
39 |
40 |
41 |
43 |
49 |
50 |
51 |
52 |
53 |
63 |
65 |
71 |
72 |
73 |
74 |
80 |
82 |
88 |
89 |
90 |
91 |
93 |
94 |
97 |
98 |
99 |
--------------------------------------------------------------------------------
/MinaOTP-MAC.xcodeproj/xcuserdata/wujianming.xcuserdatad/xcschemes/MinaOTP-MAC.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
33 |
39 |
40 |
41 |
43 |
49 |
50 |
51 |
52 |
53 |
59 |
60 |
61 |
62 |
63 |
64 |
74 |
76 |
82 |
83 |
84 |
85 |
86 |
87 |
93 |
95 |
101 |
102 |
103 |
104 |
106 |
107 |
110 |
111 |
112 |
--------------------------------------------------------------------------------
/MinaOTP-MAC/Magnet/KeyCodeTransformer.swift:
--------------------------------------------------------------------------------
1 | //
2 | // KeyCodeTransformer.swift
3 | // Magnet
4 | //
5 | // Created by 古林俊佑 on 2016/06/26.
6 | // Copyright © 2016年 Shunsuke Furubayashi. All rights reserved.
7 | //
8 |
9 | import AppKit
10 | import Foundation
11 | import Carbon
12 |
13 | open class KeyCodeTransformer {
14 | // MARK: - Properties
15 | public static let shared = KeyCodeTransformer()
16 | }
17 |
18 | // MARK: - Transform
19 | public extension KeyCodeTransformer {
20 | func transformValue(_ keyCode: Int, carbonModifiers: Int) -> String {
21 | return transformValue(keyCode, modifiers: carbonModifiers)
22 | }
23 |
24 | func transformValue(_ keyCode: Int, cocoaModifiers: NSEvent.ModifierFlags) -> String {
25 | return transformValue(keyCode, modifiers: KeyTransformer.carbonFlags(from: cocoaModifiers))
26 | }
27 |
28 | fileprivate func transformValue(_ keyCode: Int, modifiers: Int) -> String {
29 | // Return Special KeyCode
30 | if let unmappedString = transformSpecialKeyCode(keyCode) {
31 | return unmappedString
32 | }
33 |
34 | let source = TISCopyCurrentASCIICapableKeyboardLayoutInputSource().takeUnretainedValue()
35 | let layoutData = TISGetInputSourceProperty(source, kTISPropertyUnicodeKeyLayoutData)
36 | let dataRef = unsafeBitCast(layoutData, to: CFData.self)
37 |
38 | let keyLayout = unsafeBitCast(CFDataGetBytePtr(dataRef), to: UnsafePointer.self)
39 |
40 | let keyTranslateOptions = OptionBits(CoreServices.kUCKeyTranslateNoDeadKeysBit)
41 | var deadKeyState: UInt32 = 0
42 | let maxChars = 256
43 | var chars = [UniChar](repeating: 0, count: maxChars)
44 | var length = 0
45 |
46 | let error = CoreServices.UCKeyTranslate(keyLayout,
47 | UInt16(keyCode),
48 | UInt16(CoreServices.kUCKeyActionDisplay),
49 | UInt32(modifiers),
50 | UInt32(LMGetKbdType()),
51 | keyTranslateOptions,
52 | &deadKeyState,
53 | maxChars,
54 | &length,
55 | &chars)
56 |
57 | if error != noErr { return "" }
58 |
59 | return NSString(characters: &chars, length: length).uppercased
60 | }
61 |
62 | fileprivate func transformSpecialKeyCode(_ keyCode: Int) -> String? {
63 | return specialKeyCodeStrings[keyCode]
64 | }
65 | }
66 |
67 | // MARK: - Mapping
68 | private extension KeyCodeTransformer {
69 | var specialKeyCodeStrings: [Int: String] {
70 | return [
71 | kVK_F1: "F1",
72 | kVK_F2: "F2",
73 | kVK_F3: "F3",
74 | kVK_F4: "F4",
75 | kVK_F5: "F5",
76 | kVK_F6: "F6",
77 | kVK_F7: "F7",
78 | kVK_F8: "F8",
79 | kVK_F9: "F9",
80 | kVK_F10: "F10",
81 | kVK_F11: "F11",
82 | kVK_F12: "F12",
83 | kVK_F13: "F13",
84 | kVK_F14: "F14",
85 | kVK_F15: "F15",
86 | kVK_F16: "F16",
87 | kVK_F17: "F17",
88 | kVK_F18: "F18",
89 | kVK_F19: "F19",
90 | kVK_F20: "F20",
91 | kVK_Space: "Space",
92 | kVK_Delete: string(from: 0x232B), // ⌫
93 | kVK_ForwardDelete: string(from: 0x2326), // ⌦
94 | kVK_ANSI_Keypad0: string(from: 0x2327), // ⌧
95 | kVK_LeftArrow: string(from: 0x2190), // ←
96 | kVK_RightArrow: string(from: 0x2192), // →
97 | kVK_UpArrow: string(from: 0x2191), // ↑
98 | kVK_DownArrow: string(from: 0x2193), // ↓
99 | kVK_End: string(from: 0x2198), // ↘
100 | kVK_Home: string(from: 0x2196), // ↖
101 | kVK_Escape: string(from: 0x238B), // ⎋
102 | kVK_PageDown: string(from: 0x21DF), // ⇟
103 | kVK_PageUp: string(from: 0x21DE), // ⇞
104 | kVK_Return: string(from: 0x21A9), // ↩
105 | kVK_ANSI_KeypadEnter: string(from: 0x2305), // ⌅
106 | kVK_Tab: string(from: 0x21E5), // ⇥
107 | kVK_Help: "?⃝"
108 | ]
109 | }
110 | }
111 |
112 | // MARK: - Charactor
113 | private extension KeyCodeTransformer {
114 | func string(from char: unichar) -> String {
115 | return String(format: "%C", char)
116 | }
117 | }
118 |
--------------------------------------------------------------------------------
/MinaOTP-MAC/DataManager.swift:
--------------------------------------------------------------------------------
1 | import CloudKit
2 |
3 |
4 | private let ALERT_OFF = "ALERT_OFF"
5 | private let RECORD_KEY = "MinaOtp"
6 | private let recordID = CKRecord.ID(recordName: RECORD_KEY)
7 |
8 | final class DataManager {
9 |
10 | fileprivate init() {
11 | ///forbide to create instance of helper class
12 | }
13 |
14 | fileprivate static var cloudDatabase: CKDatabase {
15 | return CKContainer.default().privateCloudDatabase
16 | }
17 |
18 | static func initial(_ whenDataUpdate: (() -> Void)?) {
19 | checkLoginStatus { isLogged in
20 | print("iCloud isLogged: " + isLogged.description)
21 | if isLogged {
22 | fetchRemote { (totps, error) in
23 | print("***** remote fetched *****")
24 | print(totps)
25 | if let error = error {
26 | print(error)
27 | } else {
28 | UserDefaults.standard.set(totps, forKey: RECORD_KEY)
29 | if let whenDataUpdate = whenDataUpdate {
30 | DispatchQueue.main.async {
31 | whenDataUpdate()
32 | }
33 | }
34 | }
35 | }
36 | } else {
37 | let alertOff = UserDefaults.standard.value(forKey: ALERT_OFF) as? Bool ?? false
38 | if (!alertOff) {
39 | showAlert()
40 | }
41 | }
42 | }
43 | }
44 |
45 | static func get() -> [String] {
46 | return UserDefaults.standard.value(forKey: RECORD_KEY) as? [String] ?? []
47 | }
48 |
49 | static func save(_ totps: [String]) {
50 | UserDefaults.standard.set(totps, forKey: RECORD_KEY)
51 | updateRemote(totps) { (saved, error) in
52 | if let error = error {
53 | print(error)
54 | } else {
55 | print("***** remote saved *****")
56 | print(saved)
57 | }
58 | }
59 | }
60 | }
61 |
62 |
63 | fileprivate extension DataManager {
64 |
65 | static func createRecord(_ totps: [String], _ completion: @escaping ([String], NSError?) -> Void) {
66 | let record = CKRecord(recordType: RECORD_KEY, recordID: recordID)
67 | record.setValue(totps, forKey: RECORD_KEY)
68 | cloudDatabase.save(record) { (savedRecord, error) in
69 | DispatchQueue.main.async {
70 | completion(savedRecord?.object(forKey: RECORD_KEY) as? [String] ?? [], error as NSError?)
71 | }
72 | }
73 | }
74 |
75 | static func fetchRemote(_ completion: @escaping ([String], NSError?) -> Void) {
76 | cloudDatabase.fetch(withRecordID: recordID) { (record, error) in
77 | DispatchQueue.main.async {
78 | completion(record?.object(forKey: RECORD_KEY) as? [String] ?? [], error as NSError?)
79 | }
80 | }
81 | }
82 |
83 | static func updateRemote(_ totps: [String], _ completion: @escaping ([String], NSError?) -> Void) {
84 | cloudDatabase.fetch(withRecordID: recordID) { record, error in
85 | guard let record = record else {
86 | createRecord(totps, completion)
87 | return
88 | }
89 | record.setValue(totps, forKey: RECORD_KEY)
90 | self.cloudDatabase.save(record) { savedRecord, error in
91 | DispatchQueue.main.async {
92 | completion(savedRecord?.object(forKey: RECORD_KEY) as? [String] ?? [], error as NSError?)
93 | }
94 | }
95 | }
96 | }
97 |
98 | static func checkLoginStatus(_ handler: @escaping (_ islogged: Bool) -> Void) {
99 | CKContainer.default().accountStatus{ accountStatus, error in
100 | if let error = error {
101 | print(error.localizedDescription)
102 | }
103 | DispatchQueue.main.async {
104 | handler(accountStatus == .available)
105 | }
106 | }
107 | }
108 |
109 | static func showAlert() {
110 | // let alert = UIAlertController(title: "iCloud", message: "iCloud is unavailable, please login and try again if you need sync feature. or click [Cancel] for local usage", preferredStyle: .alert)
111 | // let cancel = UIAlertAction(title: "Cancel", style: .cancel) { _ in
112 | // UserDefaults.standard.set(true, forKey: ALERT_OFF)
113 | // }
114 | // let settings = UIAlertAction(title: "Settings", style: .default) { _ in
115 | // guard let url = URL(string:"App-Prefs:root=General") else { return }
116 | // UIApplication.shared.openURL(url)
117 | // }
118 | // alert.addAction(cancel)
119 | // alert.addAction(settings)
120 | // UIApplication.shared.keyWindow?.rootViewController?.present(alert, animated: true)
121 | }
122 | }
123 |
--------------------------------------------------------------------------------
/MinaOTP-MAC/ScanWindow.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ScanWindow.swift
3 | // MinaOTP-MAC
4 | //
5 | // Created by 武建明 on 2018/8/17.
6 | // Copyright © 2018年 Four_w. All rights reserved.
7 | //
8 |
9 | import Cocoa
10 | import CoreImage
11 | import Foundation
12 |
13 |
14 | class ScanWindow: NSWindow, NSWindowDelegate{
15 |
16 | let imgView = NSImageView.init()
17 | weak var scanDelegate : ScanWindowDelegate?
18 |
19 | override init(contentRect: NSRect, styleMask style: NSWindow.StyleMask, backing backingStoreType: NSWindow.BackingStoreType, defer flag: Bool) {
20 | super.init(contentRect: contentRect, styleMask: style, backing: backingStoreType, defer: flag)
21 | config()
22 | self.delegate = self
23 | }
24 |
25 | private func config() {
26 | self.center()
27 | self.toolbar?.isVisible = false
28 |
29 | self.styleMask = [.titled, .closable, .resizable]
30 | self.title = NSLocalizedString("scan_title", comment: "")
31 |
32 | self.standardWindowButton(.miniaturizeButton)?.isHidden = true
33 | self.standardWindowButton(.zoomButton)?.isHidden = true
34 |
35 | self.isOpaque = false
36 | self.backgroundColor = NSColor.clear
37 | self.titlebarAppearsTransparent = true
38 | self.backgroundColor = NSColor(calibratedRed: 0.0, green: 0.0, blue: 0.0, alpha: 0.1)
39 | self.isMovableByWindowBackground = true
40 |
41 | imgView.wantsLayer = true
42 | imgView.translatesAutoresizingMaskIntoConstraints = false
43 | imgView.layer?.backgroundColor = NSColor.clear.cgColor
44 | imgView.image = NSImage.init(named: "image_normal")
45 | imgView.imageAlignment = .alignCenter
46 | imgView.imageScaling = .scaleProportionallyUpOrDown
47 |
48 | self.contentView?.addSubview(imgView)
49 |
50 | let heigtConstraint = NSLayoutConstraint(item: imgView, attribute: NSLayoutConstraint.Attribute.height, relatedBy: NSLayoutConstraint.Relation.equal, toItem: self.contentView, attribute:NSLayoutConstraint.Attribute.height, multiplier: 1.0, constant: 0)
51 | let weightConstraint = NSLayoutConstraint(item: imgView, attribute: NSLayoutConstraint.Attribute.width, relatedBy: NSLayoutConstraint.Relation.equal, toItem: self.contentView, attribute:NSLayoutConstraint.Attribute.width, multiplier: 1.0, constant: 0)
52 |
53 | let leftConstraint = NSLayoutConstraint(item: imgView, attribute: NSLayoutConstraint.Attribute.left, relatedBy: NSLayoutConstraint.Relation.equal, toItem: self.contentView, attribute:NSLayoutConstraint.Attribute.left, multiplier: 1.0, constant: 0)
54 | let topConstraint = NSLayoutConstraint(item: imgView, attribute: NSLayoutConstraint.Attribute.top, relatedBy: NSLayoutConstraint.Relation.equal, toItem: self.contentView, attribute:NSLayoutConstraint.Attribute.top, multiplier: 1.0, constant: 0)
55 | self.contentView?.addConstraints([leftConstraint, topConstraint, heigtConstraint, weightConstraint])
56 |
57 | }
58 |
59 | func windowDidMove(_ notification: Notification) {
60 | print("windowDidMove")
61 |
62 | let screenRect = NSScreen.main?.visibleFrame
63 | let dif = (screenRect?.size.height)!-self.frame.origin.y-self.frame.size.height+23
64 |
65 | let cgImage = CGWindowListCreateImage(CGRect(x: self.frame.origin.x, y: dif, width: self.frame.size.width, height: self.frame.size.height), .optionOnScreenBelowWindow, CGWindowID(self.windowNumber), .bestResolution)
66 | let detector = CIDetector.init(ofType: CIDetectorTypeQRCode, context: nil, options: [CIDetectorAccuracy:CIDetectorAccuracyHigh])
67 | let ciImage = CIImage.init(cgImage: cgImage!)
68 |
69 | let feature = detector?.features(in: ciImage)
70 | if (feature?.count)! > 0 {
71 | let f = feature?.first as! CIQRCodeFeature
72 | if f.messageString?.contains("otpauth://totp/") == false || f.messageString?.contains("secret=") == false || f.messageString?.contains("issuer=") == false{
73 | imgView.image = NSImage.init(named: "image_normal")
74 | }else{
75 | imgView.image = NSImage.init(named: "image_selected")
76 | self.delegate = nil
77 | self.title = NSLocalizedString("scan_qr_success", comment: "")
78 | DispatchQueue.main.asyncAfter(deadline: .now() + 1.5) {
79 | self.scanDelegate?.scanSuccess(code: f.messageString!)
80 | self.close()
81 | }
82 | }
83 | }else{
84 | imgView.image = NSImage.init(named: "image_normal")
85 | }
86 | }
87 | func windowWillResize(_ sender: NSWindow, to frameSize: NSSize) -> NSSize {
88 | var toHeight = frameSize.height
89 | if toHeight > 700 {
90 | toHeight = 700
91 | }
92 | if toHeight < 300 {
93 | toHeight = 300
94 | }
95 | return NSSize.init(width: toHeight, height: toHeight)
96 | }
97 | }
98 |
99 | protocol ScanWindowDelegate: class {
100 | func scanSuccess(code:String)
101 | }
102 |
--------------------------------------------------------------------------------
/MinaOTP-MAC/Magnet/KeyTransformer.swift:
--------------------------------------------------------------------------------
1 | //
2 | // KeyTransformer.swift
3 | // Magnet
4 | //
5 | // Created by 古林俊佑 on 2016/06/18.
6 | // Copyright © 2016年 Shunsuke Furubayashi. All rights reserved.
7 | //
8 |
9 | import Cocoa
10 | import Carbon
11 |
12 | public final class KeyTransformer {}
13 |
14 | // MARK: - Cocoa & Carbon
15 | public extension KeyTransformer {
16 | static func cocoaFlags(from carbonFlags: Int) -> NSEvent.ModifierFlags {
17 | var cocoaFlags: NSEvent.ModifierFlags = NSEvent.ModifierFlags(rawValue: 0)
18 |
19 | if (carbonFlags & cmdKey) != 0 {
20 | cocoaFlags.insert(.command)
21 | }
22 | if (carbonFlags & optionKey) != 0 {
23 | cocoaFlags.insert(.option)
24 | }
25 | if (carbonFlags & controlKey) != 0 {
26 | cocoaFlags.insert(.control)
27 | }
28 | if (carbonFlags & shiftKey) != 0 {
29 | cocoaFlags.insert(.shift)
30 | }
31 |
32 | return cocoaFlags
33 | }
34 |
35 | static func carbonFlags(from cocoaFlags: NSEvent.ModifierFlags) -> Int {
36 | var carbonFlags: Int = 0
37 |
38 | if cocoaFlags.contains(.command) {
39 | carbonFlags |= cmdKey
40 | }
41 | if cocoaFlags.contains(.option) {
42 | carbonFlags |= optionKey
43 | }
44 | if cocoaFlags.contains(.control) {
45 | carbonFlags |= controlKey
46 | }
47 | if cocoaFlags.contains(.shift) {
48 | carbonFlags |= shiftKey
49 | }
50 |
51 | return carbonFlags
52 | }
53 |
54 | static func supportedCarbonFlags(_ carbonFlags: Int) -> Bool {
55 | return cocoaFlags(from: carbonFlags).rawValue != 0
56 | }
57 |
58 | static func supportedCocoaFlags(_ cocoaFlogs: NSEvent.ModifierFlags) -> Bool {
59 | return carbonFlags(from: cocoaFlogs) != 0
60 | }
61 |
62 | static func singleCarbonFlags(_ carbonFlags: Int) -> Bool {
63 | let commandSelected = (carbonFlags & cmdKey) != 0
64 | let optionSelected = (carbonFlags & optionKey) != 0
65 | let controlSelected = (carbonFlags & controlKey) != 0
66 | let shiftSelected = (carbonFlags & shiftKey) != 0
67 | let hash = commandSelected.intValue + optionSelected.intValue + controlSelected.intValue + shiftSelected.intValue
68 | return hash == 1
69 | }
70 |
71 | static func singleCocoaFlags(_ cocoaFlags: NSEvent.ModifierFlags) -> Bool {
72 | let commandSelected = cocoaFlags.contains(.command)
73 | let optionSelected = cocoaFlags.contains(.option)
74 | let controlSelected = cocoaFlags.contains(.control)
75 | let shiftSelected = cocoaFlags.contains(.shift)
76 | let hash = commandSelected.intValue + optionSelected.intValue + controlSelected.intValue + shiftSelected.intValue
77 | return hash == 1
78 | }
79 | }
80 |
81 | // MARK: - Function
82 | public extension KeyTransformer {
83 | static func containsFunctionKey(_ keyCode: Int) -> Bool {
84 | switch keyCode {
85 | case kVK_F1: fallthrough
86 | case kVK_F2: fallthrough
87 | case kVK_F3: fallthrough
88 | case kVK_F4: fallthrough
89 | case kVK_F5: fallthrough
90 | case kVK_F6: fallthrough
91 | case kVK_F7: fallthrough
92 | case kVK_F8: fallthrough
93 | case kVK_F9: fallthrough
94 | case kVK_F10: fallthrough
95 | case kVK_F11: fallthrough
96 | case kVK_F12: fallthrough
97 | case kVK_F13: fallthrough
98 | case kVK_F14: fallthrough
99 | case kVK_F15: fallthrough
100 | case kVK_F16: fallthrough
101 | case kVK_F17: fallthrough
102 | case kVK_F18: fallthrough
103 | case kVK_F19: fallthrough
104 | case kVK_F20:
105 | return true
106 | default:
107 | return false
108 | }
109 | }
110 | }
111 |
112 | // MARK: - Modifiers
113 | public extension KeyTransformer {
114 | static func modifiersToString(_ carbonModifiers: Int) -> [String] {
115 | var strings = [String]()
116 |
117 | if (carbonModifiers & cmdKey) != 0 {
118 | strings.append("⌘")
119 | }
120 | if (carbonModifiers & optionKey) != 0 {
121 | strings.append("⌥")
122 | }
123 | if (carbonModifiers & controlKey) != 0 {
124 | strings.append("⌃")
125 | }
126 | if (carbonModifiers & shiftKey) != 0 {
127 | strings.append("⇧")
128 | }
129 |
130 | return strings
131 | }
132 |
133 | static func modifiersToString(_ cocoaModifiers: NSEvent.ModifierFlags) -> [String] {
134 | var strings = [String]()
135 |
136 | if cocoaModifiers.contains(.command) {
137 | strings.append("⌘")
138 | }
139 | if cocoaModifiers.contains(.option) {
140 | strings.append("⌥")
141 | }
142 | if cocoaModifiers.contains(.control) {
143 | strings.append("⌃")
144 | }
145 | if cocoaModifiers.contains(.shift) {
146 | strings.append("⇧")
147 | }
148 |
149 | return strings
150 | }
151 | }
152 |
--------------------------------------------------------------------------------
/MinaOTP-MAC/Tools.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Tools.swift
3 | // MinaOTP-MAC
4 | //
5 | // Created by 武建明 on 2018/8/1.
6 | // Copyright © 2018年 Four_w. All rights reserved.
7 | //
8 |
9 | import Cocoa
10 | import Foundation
11 | import CoreImage
12 |
13 |
14 | class CustomFlatButton{
15 | func customFlatButton(frame: CGRect, title: String) -> FlatButton {
16 | let button = FlatButton.init(frame: frame)
17 | button.title = title
18 | button.font = NSFont.boldSystemFont(ofSize: 12)
19 | button.setButtonType(.momentaryChange)
20 | button.textColor = NSColor.white
21 | button.cornerRadius = 4
22 | button.borderColor = NSColor.mainColor
23 | button.borderWidth = 1
24 | button.activeBorderColor = NSColor.mainColor
25 | button.buttonColor = NSColor.mainColor
26 | button.focusRingType = .none
27 |
28 | return button
29 | }
30 | }
31 |
32 | extension NSColor {
33 |
34 | class var mainColor: NSColor {
35 | let color = NSColor.systemBlue
36 | // let color = NSColor.init(red: 0.00, green: 0.56, blue: 0.98, alpha: 1.00)
37 | return color
38 | }
39 |
40 | convenience init(hex: String) {
41 | let scanner = Scanner(string: hex)
42 | scanner.scanLocation = 0
43 |
44 | var rgbValue: UInt64 = 0
45 |
46 | scanner.scanHexInt64(&rgbValue)
47 |
48 | let r = (rgbValue & 0xff0000) >> 16
49 | let g = (rgbValue & 0xff00) >> 8
50 | let b = rgbValue & 0xff
51 |
52 | self.init(
53 | red: CGFloat(r) / 0xff,
54 | green: CGFloat(g) / 0xff,
55 | blue: CGFloat(b) / 0xff, alpha: 1
56 | )
57 | }
58 | }
59 |
60 | extension NSBezierPath {
61 |
62 | public var cgPath: CGPath {
63 | let path = CGMutablePath()
64 | var points = [CGPoint](repeating: .zero, count: 3)
65 |
66 | for i in 0 ..< self.elementCount {
67 | let type = self.element(at: i, associatedPoints: &points)
68 | switch type {
69 | case .moveTo:
70 | path.move(to: points[0])
71 | case .lineTo:
72 | path.addLine(to: points[0])
73 | case .curveTo:
74 | path.addCurve(to: points[2], control1: points[0], control2: points[1])
75 | case .closePath:
76 | path.closeSubpath()
77 | @unknown default:
78 | fatalError("Unknown path type.")
79 | }
80 | }
81 |
82 | return path
83 | }
84 | }
85 |
86 | class Tools: NSObject {
87 |
88 | public func showAlert(message: String) {
89 |
90 | let alert: NSAlert = NSAlert()
91 | alert.messageText = message
92 | alert.addButton(withTitle: NSLocalizedString("confirm", comment: ""))
93 | alert.alertStyle = NSAlert.Style.informational
94 | alert.runModal()
95 | }
96 |
97 | public func generateTextField(frame: NSRect, textColor: NSColor, text: String, font: CGFloat) -> NSTextField {
98 | let lab = NSTextField.init(frame: frame)
99 | lab.wantsLayer = true
100 | lab.textColor = textColor
101 | lab.stringValue = text
102 | lab.isBordered = false
103 | lab.backgroundColor = NSColor.clear
104 | lab.font = NSFont.systemFont(ofSize: font)
105 | // lab.layer?.borderWidth = 1
106 | // lab.layer?.borderColor = NSColor.orange.cgColor
107 | return lab
108 | }
109 |
110 | func totpDictionaryFormat(code: String) -> Dictionary {
111 |
112 | let url = code
113 | let params = NSMutableDictionary()
114 | if url.contains("otpauth://totp/") == false || url.contains("issuer") == false || url.contains("secret") == false{
115 | return params as! Dictionary
116 | }
117 | var index = url.firstIndex(of: "?")
118 | if index == nil {
119 | return params as! Dictionary
120 | }
121 | // 获取otpauth://totp/部分 并替换为remark
122 | let otpauth = url.prefix(upTo: index!)
123 | let otpauthIndx = otpauth.index(otpauth.startIndex, offsetBy: "otpauth://totp/".count)
124 | let remark = otpauth.suffix(from: Optional.init(otpauthIndx)!)
125 | params.setValue(remark, forKey: "remark")
126 | // 获取问号后面部分的字符串
127 | index = url.index(index!, offsetBy: 1)
128 | let parametersString = url.suffix(from: index!)
129 | // 解析参数
130 | let urlComponents = parametersString.components(separatedBy: "&")
131 | for keyValuePair in urlComponents {
132 | let pairComponents = keyValuePair.components(separatedBy: "=")
133 | let key = pairComponents.first
134 | let value = pairComponents.last
135 | if key == nil || value == nil {
136 | continue
137 | }
138 | params.setValue(value, forKey: key!)
139 | }
140 | return params as! Dictionary
141 | }
142 |
143 | func totpStringFormat(remark: String, issuer:String, secret:String) -> String {
144 | return "otpauth://totp/\(remark)?secret=\(secret)&issuer=\(issuer)"
145 | }
146 |
147 | }
148 |
149 |
150 |
151 |
152 |
--------------------------------------------------------------------------------
/MinaOTP-MAC/EditPopoverViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // EditPopoverViewController.swift
3 | // MinaOTP-MAC
4 | //
5 | // Created by 武建明 on 2018/8/6.
6 | // Copyright © 2018年 Four_w. All rights reserved.
7 | //
8 |
9 | import Cocoa
10 |
11 | class EditPopoverViewController: NSViewController, NSTextFieldDelegate {
12 |
13 | let cancelButton = CustomFlatButton().customFlatButton(frame: NSRect(x: 12, y: 12, width: 48, height: 24), title: NSLocalizedString("cancel", comment: ""))
14 | let saveButton = CustomFlatButton().customFlatButton(frame: NSRect(x: 140, y: 12, width: 48, height: 24), title: NSLocalizedString("save", comment: ""))
15 | var editRow:Int = -1
16 | let textColor: NSColor = .labelColor //NSColor(calibratedRed: 0, green: 0, blue: 0, alpha: 0.8)
17 |
18 | override func loadView() {
19 | self.view = NSView(frame: CGRect(x: 0, y: 0, width: 200, height: 250))
20 | }
21 |
22 | override func viewDidLoad() {
23 | super.viewDidLoad()
24 | self.config()
25 | let allItems = DataManager.get()
26 | let totpDic = Tools().totpDictionaryFormat(code: allItems[editRow])
27 | remarkTextField.stringValue = (totpDic["remark"] as? String)!
28 | issuerTextField.stringValue = (totpDic["issuer"] as? String)!
29 | secretTextFiled.stringValue = (totpDic["secret"] as? String)!
30 | }
31 | override func awakeFromNib() {
32 |
33 | }
34 | private func config() {
35 | self.view.addSubview(cancelButton)
36 | self.view.addSubview(saveButton)
37 | self.view.addSubview(self.remarkTitleTextField)
38 | self.view.addSubview(self.remarkTextField)
39 | self.view.addSubview(self.issuerTitleTextField)
40 | self.view.addSubview(self.issuerTextField)
41 | self.view.addSubview(self.secretTitleTextField)
42 | self.view.addSubview(self.secretTextFiled)
43 |
44 | cancelButton.target = self
45 | saveButton.target = self
46 | cancelButton.action = #selector(self.cancelButtonAction)
47 | saveButton.action = #selector(self.saveButtonAction(button:))
48 |
49 | saveButton.isEnabled = false
50 | }
51 | lazy var remarkTitleTextField: NSTextField = {
52 | let lab = Tools().generateTextField(frame: NSRect(x: 12, y: 220, width: 180, height: 18), textColor: textColor, text: NSLocalizedString("remark_placeholder", comment: ""), font: 10)
53 | lab.isEditable = false
54 | lab.delegate = self
55 | return lab
56 | }()
57 | lazy var remarkTextField: NSTextField = {
58 | let lab = Tools().generateTextField(frame: NSRect(x: 12, y: 185, width: 180, height: 35), textColor: textColor, text: "", font: 12)
59 | lab.delegate = self
60 | lab.isBordered = true
61 | lab.focusRingType = .none
62 | lab.isBezeled = true
63 | lab.bezelStyle = .squareBezel
64 | lab.layer?.borderWidth = 1
65 | lab.layer?.borderColor = NSColor.mainColor.cgColor
66 | return lab
67 | }()
68 | lazy var issuerTitleTextField: NSTextField = {
69 | let lab = Tools().generateTextField(frame: NSRect(x: 12, y: 160, width: 180, height: 18), textColor: textColor, text: NSLocalizedString("issuer_placeholder", comment: ""), font: 10)
70 | lab.isEditable = false
71 | return lab
72 | }()
73 | lazy var issuerTextField: NSTextField = {
74 | let lab = Tools().generateTextField(frame: NSRect(x: 12, y: 125, width: 180, height: 35), textColor: textColor, text: "", font: 12)
75 | lab.delegate = self
76 | lab.delegate = self
77 | lab.isBordered = true
78 | lab.focusRingType = .none
79 | lab.isBezeled = true
80 | lab.bezelStyle = .squareBezel
81 | lab.layer?.borderWidth = 1
82 | lab.layer?.borderColor = NSColor.mainColor.cgColor
83 | return lab
84 | }()
85 | lazy var secretTitleTextField: NSTextField = {
86 | let lab = Tools().generateTextField(frame: NSRect(x: 12, y: 100, width: 180, height: 18), textColor: textColor, text: NSLocalizedString("secret_placeholder", comment: ""), font: 10)
87 | lab.isEditable = false
88 | lab.delegate = self
89 | return lab
90 | }()
91 | lazy var secretTextFiled: NSTextField = {
92 | let lab = Tools().generateTextField(frame: NSRect(x: 12, y: 55, width: 180, height: 45), textColor: textColor, text: "", font: 12)
93 | lab.delegate = self
94 | lab.isBordered = true
95 | lab.focusRingType = .none
96 | lab.isBezeled = true
97 | lab.bezelStyle = .squareBezel
98 | lab.layer?.borderWidth = 1
99 | lab.layer?.borderColor = NSColor.mainColor.cgColor
100 | return lab
101 | }()
102 | func controlTextDidChange(_ obj: Notification) {
103 | if self.remarkTextField.stringValue.count == 0 || self.issuerTextField.stringValue.count == 0 || self.secretTextFiled.stringValue.count == 0{
104 | saveButton.isEnabled = false
105 | }else{
106 | print("可以输入了")
107 | saveButton.isEnabled = true
108 | }
109 |
110 | }
111 | @objc func cancelButtonAction() {
112 | print("cancelButtonAction")
113 | NotificationCenter.default.post(name: NSNotification.Name("reloadData"), object: self, userInfo: ["type":"edit_cancel"])
114 | }
115 |
116 | @objc func saveButtonAction(button: NSButton) {
117 | print("saveButtonAction")
118 | let otp = Tools().totpStringFormat(remark: self.remarkTextField.stringValue, issuer: self.issuerTextField.stringValue, secret: self.secretTextFiled.stringValue)
119 |
120 | // 将数据保存到UserDefaults
121 | var allItems = DataManager.get()
122 | allItems.remove(at: editRow)
123 | allItems.insert(otp, at: editRow)
124 | DataManager.save(allItems)
125 | NotificationCenter.default.post(name: NSNotification.Name("reloadData"), object: self, userInfo: ["type":"edit_save"])
126 | }
127 |
128 | override func viewWillAppear() {
129 | super.viewWillAppear()
130 | }
131 | }
132 |
--------------------------------------------------------------------------------
/MinaOTP-MAC/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.swift
3 | // MinaOTP-MAC
4 | //
5 | // Created by 武建明 on 2018/7/31.
6 | // Copyright © 2018年 Four_w. All rights reserved.
7 | //
8 |
9 | import Cocoa
10 | import Carbon
11 | import Foundation
12 |
13 | @NSApplicationMain
14 |
15 | class AppDelegate: NSObject, NSApplicationDelegate, NSPopoverDelegate, NSMenuDelegate{
16 |
17 | let statusItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.squareLength)
18 | let popover = NSPopover()
19 | let popoverVC = PopoverViewController()
20 |
21 | struct GitHubInfoModel:Codable {
22 | var tag_name:String
23 | var name:String
24 | var html_url:String
25 | }
26 |
27 | func applicationDidFinishLaunching(_ aNotification: Notification) {
28 |
29 | if let button = statusItem.button{
30 | button.image = NSImage(named: "close")
31 | button.image?.size = NSSize(width: 20, height: 20)
32 | button.action = #selector(AppDelegate.mouseDownAction)
33 | button.sendAction(on: [.leftMouseDown, .rightMouseDown])
34 | button.target = self
35 | button.toolTip = NSLocalizedString("tool_tip", comment: "")
36 | }
37 | statusItem.highlightMode = true
38 |
39 | popover.behavior = .transient
40 | //popover.appearance = NSAppearance.init(named: .vibrantLight)
41 | popover.contentViewController = popoverVC
42 | popover.delegate = self
43 | NSEvent.addGlobalMonitorForEvents(matching: [.leftMouseDown, .rightMouseDown]) { (event) in
44 | if event.type == .leftMouseDown || event.type == .rightMouseDown{
45 | self.statusItem.button?.state = NSControl.StateValue.off
46 | if self.popover.isShown {
47 | self.popover.close()
48 | }
49 | }
50 | }
51 | addHotKey()
52 | }
53 | @objc func mouseDownAction() {
54 | let event = NSApp.currentEvent
55 | if event?.type == .leftMouseDown{
56 | statusItem.button?.state = NSControl.StateValue.off
57 | leftMouseDownAction()
58 | }else if event?.type == .rightMouseDown{
59 | rightMouseDownAction()
60 | }else{
61 | print("谁点的")
62 | }
63 | }
64 | func leftMouseDownAction() {
65 | popover.show(relativeTo: (statusItem.button?.bounds)!, of: (statusItem.button)!, preferredEdge: NSRectEdge.maxY)
66 | }
67 | func rightMouseDownAction() {
68 | let menu = NSMenu.init()
69 | menu.delegate = self
70 | menu.addItem(withTitle: NSLocalizedString("export_help", comment: ""), action: #selector(exportDemoAction), keyEquivalent: "")
71 | menu.addItem(withTitle: NSLocalizedString("check_releases", comment: ""), action: #selector(checkReleases), keyEquivalent: "")
72 | menu.addItem(withTitle: NSLocalizedString("help", comment: ""), action: #selector(helpAction), keyEquivalent: "")
73 | menu.addItem(withTitle: NSLocalizedString("exit", comment: ""), action: #selector(exitAction), keyEquivalent: "")
74 | statusItem.popUpMenu(menu)
75 | }
76 | @objc func helpAction() {
77 | NSWorkspace.shared.open(NSURL.init(string: "https://github.com/MinaOTP/MinaOTP-MAC")! as URL)
78 | }
79 | @objc func exitAction() {
80 | NSApp.terminate(nil)
81 | }
82 | @objc func checkReleases() {
83 | let requestUrl = NSURL.init(string: "https://api.github.com/repos/MinaOTP/MinaOTP-MAC/releases/latest")
84 | let request = URLRequest.init(url: requestUrl! as URL)
85 | let task = URLSession.shared.dataTask(with: request) { (data, response, error) in
86 |
87 | DispatchQueue.main.async {
88 | self.dealRequestData(data: data!)
89 | }
90 | }
91 | task.resume()
92 | }
93 | func dealRequestData (data: Data){
94 | let decoder = JSONDecoder()
95 | do {
96 | let model = try decoder.decode(GitHubInfoModel.self, from: data as Data)
97 | print(model.tag_name)
98 | let version = Bundle.main.infoDictionary!["CFBundleShortVersionString"] as! String
99 | print(version)
100 |
101 | if version != model.tag_name {
102 | Tools().showAlert(message: NSLocalizedString("has_new_version", comment: ""))
103 | }else{
104 | Tools().showAlert(message: NSLocalizedString("no_new_version", comment: ""))
105 | }
106 |
107 | } catch {
108 | print("error")
109 | }
110 | }
111 | @objc func exportDemoAction() {
112 | let savePanel = NSSavePanel()
113 | savePanel.title = NSLocalizedString("export_title", comment: "")
114 | savePanel.nameFieldStringValue = "minaOTP_example.json"
115 | savePanel.canCreateDirectories = true
116 | savePanel.isExtensionHidden = false
117 | let i = savePanel.runModal()
118 | if i == NSApplication.ModalResponse.OK {
119 | let temArray = [["remark":"remark_value", "secret":"secret_value", "issuer":"issuer_value"], ["remark":"remark_value", "secret":"secret_value", "issuer":"issuer_value"]]
120 | let temJsonData = try! JSONSerialization.data(withJSONObject: temArray, options: .prettyPrinted)
121 | let temJsonStr = String.init(data: temJsonData, encoding: .utf8)
122 | do {
123 | try temJsonStr?.write(to: savePanel.url!, atomically: true, encoding: .utf8)
124 | Tools().showAlert(message: NSLocalizedString("export_sucess", comment: ""))
125 | } catch {
126 | Tools().showAlert(message: NSLocalizedString("export_failure", comment: ""))
127 | }
128 | }
129 |
130 | }
131 | func popoverWillShow(_ notification: Notification) {
132 | NotificationCenter.default.post(name: NSNotification.Name("reloadData"), object: self, userInfo: ["type":"reload"])
133 | statusItem.button?.image = NSImage.init(named: "open")
134 | statusItem.button?.image?.size = NSSize(width: 20, height: 20)
135 |
136 | }
137 | func popoverDidClose(_ notification: Notification) {
138 | NotificationCenter.default.post(name: NSNotification.Name("reloadData"), object: self, userInfo: ["type":"close"])
139 | statusItem.button?.image = NSImage.init(named: "close")
140 | statusItem.button?.image?.size = NSSize(width: 20, height: 20)
141 | }
142 |
143 | func addHotKey() {
144 |
145 | guard let keyCombo = KeyCombo(doubledCocoaModifiers: .command) else { return }
146 | let hotKey = HotKey(identifier: "CommandDoubleTap",
147 | keyCombo: keyCombo,
148 | target: self,
149 | action: #selector(AppDelegate.tappedDoubleCommandKey))
150 | hotKey.register()
151 |
152 |
153 | }
154 | @objc func tappedDoubleCommandKey() {
155 | print("11111111111111")
156 | if self.popover.isShown {
157 | self.popover.close()
158 | }else{
159 | leftMouseDownAction()
160 | }
161 | }
162 | func removeHotKey() {
163 |
164 | }
165 |
166 | func applicationWillTerminate(_ aNotification: Notification) {
167 | // Insert code here to tear down your application
168 | }
169 |
170 |
171 | }
172 |
173 |
--------------------------------------------------------------------------------
/MinaOTP-MAC/Magnet/HotKeyCenter.swift:
--------------------------------------------------------------------------------
1 | //
2 | // HotKeyCenter.swift
3 | // Magnet
4 | //
5 | // Created by 古林俊佑 on 2016/03/09.
6 | // Copyright © 2016年 Shunsuke Furubayashi. All rights reserved.
7 | //
8 |
9 | import Cocoa
10 | import Carbon
11 |
12 | public final class HotKeyCenter {
13 |
14 | // MARK: - Properties
15 | public static let shared = HotKeyCenter()
16 | fileprivate var hotKeys = [String: HotKey]()
17 | fileprivate var hotKeyMap = [NSNumber: HotKey]()
18 | fileprivate var hotKeyCount: UInt32 = 0
19 |
20 | fileprivate var tappedModifierKey = NSEvent.ModifierFlags(rawValue: 0)
21 | fileprivate var multiModifiers = false
22 |
23 | // MARK: - Initialize
24 | init() {
25 | installEventHandler()
26 | observeApplicationTerminate()
27 | }
28 |
29 | }
30 |
31 | // MARK: - Register & Unregister
32 | public extension HotKeyCenter {
33 | func register(with hotKey: HotKey) -> Bool {
34 | guard !hotKeys.keys.contains(hotKey.identifier) else { return false }
35 | guard !hotKeys.values.contains(hotKey) else { return false }
36 |
37 | if !hotKey.keyCombo.doubledModifiers {
38 | // Normal HotKey
39 | let hotKeyId = EventHotKeyID(signature: UTGetOSTypeFromString("Magnet" as CFString), id: hotKeyCount)
40 | var carbonHotKey: EventHotKeyRef? = nil
41 | let error = RegisterEventHotKey(UInt32(hotKey.keyCombo.keyCode),
42 | UInt32(hotKey.keyCombo.modifiers),
43 | hotKeyId,
44 | GetEventDispatcherTarget(),
45 | 0,
46 | &carbonHotKey)
47 | if error != 0 { return false }
48 |
49 | hotKey.hotKeyId = hotKeyId.id
50 | hotKey.hotKeyRef = carbonHotKey
51 | }
52 |
53 | let kId = NSNumber(value: hotKeyCount as UInt32)
54 | hotKeyMap[kId] = hotKey
55 | hotKeyCount += 1
56 |
57 | hotKeys[hotKey.identifier] = hotKey
58 |
59 | return true
60 | }
61 |
62 | func unregister(with hotKey: HotKey) {
63 | guard hotKeys.values.contains(hotKey) else { return }
64 |
65 | if !hotKey.keyCombo.doubledModifiers {
66 | // Notmal HotKey
67 | guard let carbonHotKey = hotKey.hotKeyRef else { return }
68 | UnregisterEventHotKey(carbonHotKey)
69 | }
70 |
71 | hotKeys.removeValue(forKey: hotKey.identifier)
72 |
73 | hotKey.hotKeyId = nil
74 | hotKey.hotKeyRef = nil
75 |
76 | hotKeyMap
77 | .filter { $1 == hotKey }
78 | .map { $0.0 }
79 | .forEach { hotKeyMap.removeValue(forKey: $0) }
80 | }
81 |
82 | func unregisterHotKey(with identifier: String) {
83 | guard let hotKey = hotKeys[identifier] else { return }
84 | unregister(with: hotKey)
85 | }
86 |
87 | func unregisterAll() {
88 | hotKeys.forEach { unregister(with: $1) }
89 | }
90 | }
91 |
92 | // MARK: - Terminate
93 | extension HotKeyCenter {
94 | private func observeApplicationTerminate() {
95 | NotificationCenter.default.addObserver(self,
96 | selector: #selector(HotKeyCenter.applicationWillTerminate),
97 | name: NSApplication.willTerminateNotification,
98 | object: nil)
99 | }
100 |
101 | @objc func applicationWillTerminate() {
102 | unregisterAll()
103 | }
104 | }
105 |
106 | // MARK: - HotKey Events
107 | private extension HotKeyCenter {
108 | func installEventHandler() {
109 | // Press HotKey Event
110 | var pressedEventType = EventTypeSpec()
111 | pressedEventType.eventClass = OSType(kEventClassKeyboard)
112 | pressedEventType.eventKind = OSType(kEventHotKeyPressed)
113 | InstallEventHandler(GetEventDispatcherTarget(), { (_, inEvent, _) -> OSStatus in
114 | return HotKeyCenter.shared.sendCarbonEvent(inEvent!)
115 | }, 1, &pressedEventType, nil, nil)
116 |
117 | // Press Modifiers Event
118 | let mask = CGEventMask((1 << CGEventType.flagsChanged.rawValue))
119 | let event = CGEvent.tapCreate(tap: .cghidEventTap,
120 | place: .headInsertEventTap,
121 | options: .listenOnly,
122 | eventsOfInterest: mask,
123 | callback: { (_, _, event, _) in return HotKeyCenter.shared.sendModifiersEvent(event) },
124 | userInfo: nil)
125 | if event == nil { return }
126 | let source = CFMachPortCreateRunLoopSource(kCFAllocatorDefault, event!, 0)
127 | CFRunLoopAddSource(CFRunLoopGetCurrent(), source, CFRunLoopMode.commonModes)
128 | CGEvent.tapEnable(tap: event!, enable: true)
129 | }
130 |
131 | func sendCarbonEvent(_ event: EventRef) -> OSStatus {
132 | assert(Int(GetEventClass(event)) == kEventClassKeyboard, "Unknown event class")
133 |
134 | var hotKeyId = EventHotKeyID()
135 | let error = GetEventParameter(event,
136 | EventParamName(kEventParamDirectObject),
137 | EventParamName(typeEventHotKeyID),
138 | nil,
139 | MemoryLayout.size,
140 | nil,
141 | &hotKeyId)
142 |
143 | if error != 0 { return error }
144 |
145 | assert(hotKeyId.signature == UTGetOSTypeFromString("Magnet" as CFString), "Invalid hot key id")
146 |
147 | let kId = NSNumber(value: hotKeyId.id as UInt32)
148 | let hotKey = hotKeyMap[kId]
149 |
150 | switch GetEventKind(event) {
151 | case EventParamName(kEventHotKeyPressed):
152 | hotKeyDown(hotKey)
153 | default:
154 | assert(false, "Unknown event kind")
155 | }
156 |
157 | return noErr
158 | }
159 |
160 | func hotKeyDown(_ hotKey: HotKey?) {
161 | guard let hotKey = hotKey else { return }
162 | hotKey.invoke()
163 | }
164 | }
165 |
166 | // MARK: - Double Tap Modifier Event
167 | private extension HotKeyCenter {
168 | func sendModifiersEvent(_ event: CGEvent) -> Unmanaged? {
169 | let flags = event.flags
170 |
171 | let commandTapped = flags.contains(.maskCommand)
172 | let shiftTapped = flags.contains(.maskShift)
173 | let controlTapped = flags.contains(.maskControl)
174 | let altTapped = flags.contains(.maskAlternate)
175 |
176 | // Only one modifier key
177 | let totalHash = commandTapped.intValue + altTapped.intValue + shiftTapped.intValue + controlTapped.intValue
178 | if totalHash == 0 { return Unmanaged.passUnretained(event) }
179 | if totalHash > 1 {
180 | multiModifiers = true
181 | return Unmanaged.passUnretained(event)
182 | }
183 | if multiModifiers {
184 | multiModifiers = false
185 | return Unmanaged.passUnretained(event)
186 | }
187 |
188 | if (tappedModifierKey.contains(.command) && commandTapped) ||
189 | (tappedModifierKey.contains(.shift) && shiftTapped) ||
190 | (tappedModifierKey.contains(.control) && controlTapped) ||
191 | (tappedModifierKey.contains(.option) && altTapped) {
192 | doubleTapped(with: KeyTransformer.carbonFlags(from: tappedModifierKey))
193 | tappedModifierKey = NSEvent.ModifierFlags(rawValue: 0)
194 | } else {
195 | if commandTapped {
196 | tappedModifierKey = .command
197 | } else if shiftTapped {
198 | tappedModifierKey = .shift
199 | } else if controlTapped {
200 | tappedModifierKey = .control
201 | } else if altTapped {
202 | tappedModifierKey = .option
203 | } else {
204 | tappedModifierKey = NSEvent.ModifierFlags(rawValue: 0)
205 | }
206 | }
207 |
208 | // Clean Flag
209 | let delay = 0.3 * Double(NSEC_PER_SEC)
210 | let time = DispatchTime.now() + Double(Int64(delay)) / Double(NSEC_PER_SEC)
211 | DispatchQueue.main.asyncAfter(deadline: time, execute: { [weak self] in
212 | self?.tappedModifierKey = NSEvent.ModifierFlags(rawValue: 0)
213 | })
214 |
215 | return Unmanaged.passUnretained(event)
216 | }
217 |
218 | func doubleTapped(with key: Int) {
219 | hotKeys.values
220 | .filter { $0.keyCombo.doubledModifiers && $0.keyCombo.modifiers == key }
221 | .forEach { $0.invoke() }
222 | }
223 | }
224 |
--------------------------------------------------------------------------------
/MinaOTP-MAC/AddWindow.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AddWindowController.swift
3 | // MinaOTP-MAC
4 | //
5 | // Created by 武建明 on 2018/8/7.
6 | // Copyright © 2018年 Four_w. All rights reserved.
7 | //
8 |
9 | import Cocoa
10 |
11 | class AddWindow: NSWindow, NSTextFieldDelegate, ScanWindowDelegate{
12 |
13 | let cancelButton = CustomFlatButton().customFlatButton(frame: NSRect(x: 12, y: 12, width: 48, height: 24), title: NSLocalizedString("cancel", comment: ""))
14 | let saveButton = CustomFlatButton().customFlatButton(frame: NSRect(x: 420, y: 12, width: 48, height: 24), title: NSLocalizedString("save", comment: ""))
15 | let chooseButton = CustomFlatButton().customFlatButton(frame: NSRect(x: 318, y: 290, width: 150, height: 24), title: NSLocalizedString("choose_qr_image_btn_title", comment: ""))
16 |
17 | let scanButton = CustomFlatButton().customFlatButton(frame: NSRect(x: 12, y: 290, width: 150, height: 24), title: NSLocalizedString("scan_qr_image_btn_title", comment: ""))
18 |
19 |
20 | let textColor = NSColor.labelColor//.withAlphaComponent(0.8)
21 |
22 | override init(contentRect: NSRect, styleMask style: NSWindow.StyleMask, backing backingStoreType: NSWindow.BackingStoreType, defer flag: Bool) {
23 | super.init(contentRect: contentRect, styleMask: style, backing: backingStoreType, defer: flag)
24 | config()
25 | }
26 |
27 | private func config() {
28 | self.center()
29 | self.toolbar?.isVisible = false
30 |
31 | self.styleMask = [.titled, .closable]
32 | self.title = NSLocalizedString("add_title", comment: "")
33 |
34 | self.standardWindowButton(.miniaturizeButton)?.isHidden = true
35 | self.standardWindowButton(.zoomButton)?.isHidden = true
36 |
37 | self.isOpaque = false
38 | //self.backgroundColor = NSColor.clear
39 | self.titlebarAppearsTransparent = true
40 | //self.backgroundColor = NSColor(calibratedRed: 1.0, green: 1.0, blue: 1.0, alpha: 0.9)
41 | self.isMovableByWindowBackground = true
42 |
43 | self.contentView?.addSubview(cancelButton)
44 | self.contentView?.addSubview(saveButton)
45 | self.contentView?.addSubview(chooseButton)
46 | self.contentView?.addSubview(scanButton)
47 | self.contentView?.addSubview(self.remarkTextField)
48 | self.contentView?.addSubview(self.issuerTextField)
49 | self.contentView?.addSubview(self.secretTextFiled)
50 | self.contentView?.addSubview(self.remarkTitleTextField)
51 | self.contentView?.addSubview(self.issuerTitleTextField)
52 | self.contentView?.addSubview(self.secretTitleTextField)
53 |
54 | cancelButton.target = self
55 | saveButton.target = self
56 | chooseButton.target = self
57 | scanButton.target = self
58 | cancelButton.action = #selector(self.cancelButtonAction)
59 | chooseButton.action = #selector(self.chooseButtonAction)
60 | saveButton.action = #selector(self.saveButtonAction(button:))
61 | scanButton.action = #selector(self.scanButtonAction)
62 | saveButton.isEnabled = false
63 | }
64 | // MARK: - Lazy
65 | lazy var remarkTitleTextField: NSTextField = {
66 | let lab = Tools().generateTextField(frame: NSRect(x: 12, y: 270, width: 200, height: 18), textColor: textColor, text: NSLocalizedString("remark_placeholder", comment: ""), font: 12)
67 | lab.isEditable = false
68 | lab.delegate = self
69 | return lab
70 | }()
71 | lazy var remarkTextField: NSTextField = {
72 | let lab = Tools().generateTextField(frame: NSRect(x: 13, y: 230, width: 455, height: 40), textColor: textColor, text: "", font: 12)
73 | lab.delegate = self
74 | lab.isBordered = true
75 | lab.focusRingType = .none
76 | lab.isBezeled = true
77 | lab.bezelStyle = .squareBezel
78 | lab.layer?.borderWidth = 1
79 | lab.layer?.borderColor = NSColor.mainColor.cgColor
80 | return lab
81 | }()
82 | lazy var issuerTitleTextField: NSTextField = {
83 | let lab = Tools().generateTextField(frame: NSRect(x: 12, y: 200, width: 200, height: 18), textColor: textColor, text: NSLocalizedString("issuer_placeholder", comment: ""), font: 12)
84 | lab.isEditable = false
85 | lab.delegate = self
86 | return lab
87 | }()
88 | lazy var issuerTextField: NSTextField = {
89 | let lab = Tools().generateTextField(frame: NSRect(x: 13, y: 160, width: 455, height: 40), textColor: textColor, text: "", font: 12)
90 | lab.delegate = self
91 | lab.isBordered = true
92 | lab.focusRingType = .none
93 | lab.isBezeled = true
94 | lab.bezelStyle = .squareBezel
95 | lab.layer?.borderWidth = 1
96 | lab.layer?.borderColor = NSColor.mainColor.cgColor
97 | return lab
98 | }()
99 | lazy var secretTitleTextField: NSTextField = {
100 | let lab = Tools().generateTextField(frame: NSRect(x: 12, y: 130, width: 200, height: 18), textColor: textColor, text: NSLocalizedString("secret_placeholder", comment: ""), font: 12)
101 | lab.isEditable = false
102 | lab.delegate = self
103 | return lab
104 | }()
105 | lazy var secretTextFiled: NSTextField = {
106 | let lab = Tools().generateTextField(frame: NSRect(x: 13, y: 90, width: 455, height: 40), textColor: textColor, text: "", font: 12)
107 | lab.delegate = self
108 | lab.isBordered = true
109 | lab.focusRingType = .none
110 | lab.isBezeled = true
111 | lab.bezelStyle = .squareBezel
112 | lab.layer?.borderWidth = 1
113 | lab.layer?.borderColor = NSColor.mainColor.cgColor
114 | return lab
115 | }()
116 | // MARK: - FUNC
117 | func controlTextDidChange(_ obj: Notification) {
118 | if self.remarkTextField.stringValue.count == 0 || self.issuerTextField.stringValue.count == 0 || self.secretTextFiled.stringValue.count == 0{
119 | saveButton.isEnabled = false
120 | }else{
121 | saveButton.isEnabled = true
122 | }
123 |
124 | }
125 | @objc func cancelButtonAction() {
126 | self.close()
127 | }
128 | @objc func scanButtonAction() {
129 | print("扫描")
130 | // ScanQRCodeOnScreen()
131 | let temWindow = ScanWindow.init(contentRect: NSRect(x: 0, y: 0, width: 300, height: 300), styleMask: .closable, backing: .buffered, defer: true)
132 | let windowVC = NSWindowController.init(window: temWindow)
133 | temWindow.scanDelegate = self
134 | windowVC.window?.makeKeyAndOrderFront(nil)
135 | NSApp.activate(ignoringOtherApps: true)
136 | }
137 | @objc func saveButtonAction(button: NSButton) {
138 | let otp = Tools().totpStringFormat(remark: self.remarkTextField.stringValue, issuer: self.issuerTextField.stringValue, secret: self.secretTextFiled.stringValue)
139 |
140 | // 将数据保存到UserDefaults
141 | var allItems = DataManager.get()
142 | allItems.append(otp)
143 | DataManager.save(allItems)
144 | ShowTips().showTip(message: NSLocalizedString("add_success_tip", comment: ""), view: self.contentView!)
145 | DispatchQueue.main.asyncAfter(deadline: .now() + 1.5) {
146 | self.close()
147 | }
148 | }
149 |
150 | @objc func chooseButtonAction() {
151 | let openPanel = NSOpenPanel.init()
152 | openPanel.prompt = NSLocalizedString("choose_title", comment: "")
153 | openPanel.allowedFileTypes = NSImage.imageTypes
154 | let code = openPanel.runModal()
155 | if code.rawValue == 1{
156 | self.readQRCode(qrImage: CIImage.init(contentsOf: openPanel.url!)!)
157 | }
158 | }
159 |
160 | func readQRCode(qrImage: CIImage) {
161 |
162 | let context = CIContext.init(options: nil)
163 | let detector = CIDetector.init(ofType: CIDetectorTypeQRCode, context: context, options: [CIDetectorAccuracy:CIDetectorAccuracyHigh])
164 | let features = detector?.features(in: qrImage)
165 | if features?.count == 1{
166 | let feature: CIQRCodeFeature = features?.first as! CIQRCodeFeature
167 | guard let result = feature.messageString else { return }
168 | self.otpFormat(code: result)
169 | }else{
170 | Tools().showAlert(message: NSLocalizedString("image_error_tip", comment: ""))
171 | }
172 | }
173 | func otpFormat(code: String) {
174 | // otpauth://totp/UCloud:wujianming@licaifan.com?algorithm=SHA1&digits=6&issuer=UCloud&period=30&secret=N2IGDW2Y6XW3PCBX
175 | if code.contains("otpauth://totp/") == false || code.contains("secret=") == false || code.contains("issuer=") == false{
176 | Tools().showAlert(message: NSLocalizedString("image_content_error_tip", comment: ""))
177 | return
178 | }
179 | let totpDic = Tools().totpDictionaryFormat(code: code)
180 | remarkTextField.stringValue = totpDic["remark"] as! String
181 | issuerTextField.stringValue = totpDic["issuer"] as! String
182 | secretTextFiled.stringValue = totpDic["secret"] as! String
183 | saveButton.isEnabled = true
184 | }
185 | //MARK: - ScanWindowDelegate
186 | func scanSuccess(code: String) {
187 | self.becomeFirstResponder()
188 | self.otpFormat(code: code)
189 | }
190 | }
191 |
--------------------------------------------------------------------------------
/MinaOTP-MAC/OTPGenerator/Base32Addition.m:
--------------------------------------------------------------------------------
1 | //
2 | // Base32Addition.m
3 | // Mina_OTP_OC
4 | //
5 | // Created by 武建明 on 2018/4/11.
6 | // Copyright © 2018年 Four_w. All rights reserved.
7 | //
8 |
9 | #import "Base32Addition.h"
10 |
11 | @implementation Base32Addition
12 | +(NSData *)dataFromBase32String:(NSString *)encoding
13 | {
14 | NSData *data = nil;
15 | unsigned char *decodedBytes = NULL;
16 | @try {
17 | #define __ 255
18 | static char decodingTable[256] = {
19 | __,__,__,__, __,__,__,__, __,__,__,__, __,__,__,__, // 0x00 - 0x0F
20 | __,__,__,__, __,__,__,__, __,__,__,__, __,__,__,__, // 0x10 - 0x1F
21 | __,__,__,__, __,__,__,__, __,__,__,__, __,__,__,__, // 0x20 - 0x2F
22 | __,__,26,27, 28,29,30,31, __,__,__,__, __, 0,__,__, // 0x30 - 0x3F
23 | __, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10, 11,12,13,14, // 0x40 - 0x4F
24 | 15,16,17,18, 19,20,21,22, 23,24,25,__, __,__,__,__, // 0x50 - 0x5F
25 | __, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10, 11,12,13,14, // 0x60 - 0x6F
26 | 15,16,17,18, 19,20,21,22, 23,24,25,__, __,__,__,__, // 0x70 - 0x7F
27 | __,__,__,__, __,__,__,__, __,__,__,__, __,__,__,__, // 0x80 - 0x8F
28 | __,__,__,__, __,__,__,__, __,__,__,__, __,__,__,__, // 0x90 - 0x9F
29 | __,__,__,__, __,__,__,__, __,__,__,__, __,__,__,__, // 0xA0 - 0xAF
30 | __,__,__,__, __,__,__,__, __,__,__,__, __,__,__,__, // 0xB0 - 0xBF
31 | __,__,__,__, __,__,__,__, __,__,__,__, __,__,__,__, // 0xC0 - 0xCF
32 | __,__,__,__, __,__,__,__, __,__,__,__, __,__,__,__, // 0xD0 - 0xDF
33 | __,__,__,__, __,__,__,__, __,__,__,__, __,__,__,__, // 0xE0 - 0xEF
34 | __,__,__,__, __,__,__,__, __,__,__,__, __,__,__,__, // 0xF0 - 0xFF
35 | };
36 | static NSUInteger paddingAdjustment[8] = {0,1,1,1,2,3,3,4};
37 | encoding = [encoding stringByReplacingOccurrencesOfString:@"=" withString:@""];
38 | NSData *encodedData = [encoding dataUsingEncoding:NSASCIIStringEncoding];
39 | unsigned char *encodedBytes = (unsigned char *)[encodedData bytes];
40 |
41 | NSUInteger encodedLength = [encodedData length];
42 | NSUInteger encodedBlocks = (encodedLength * 5) / 40;
43 | if( encodedLength % 8 != 0 ) {
44 | encodedBlocks++;
45 | }
46 | NSUInteger expectedDataLength = encodedBlocks * 5;
47 |
48 | decodedBytes = malloc(expectedDataLength);
49 | if( decodedBytes != NULL ) {
50 |
51 | unsigned char encodedByte1, encodedByte2, encodedByte3, encodedByte4;
52 | unsigned char encodedByte5, encodedByte6, encodedByte7, encodedByte8;
53 | NSUInteger encodedBytesToProcess = encodedLength;
54 | NSUInteger encodedBaseIndex = 0;
55 | NSUInteger decodedBaseIndex = 0;
56 | unsigned char encodedBlock[8] = {0,0,0,0,0,0,0,0};
57 | NSUInteger encodedBlockIndex = 0;
58 | unsigned char c;
59 | while( encodedBytesToProcess-- >= 1 ) {
60 | c = encodedBytes[encodedBaseIndex++];
61 | if( c == '=' ) break; // padding...
62 |
63 | c = decodingTable[c];
64 | if( c == __ ) continue;
65 |
66 | encodedBlock[encodedBlockIndex++] = c;
67 | if( encodedBlockIndex == 8 ) {
68 | encodedByte1 = encodedBlock[0];
69 | encodedByte2 = encodedBlock[1];
70 | encodedByte3 = encodedBlock[2];
71 | encodedByte4 = encodedBlock[3];
72 | encodedByte5 = encodedBlock[4];
73 | encodedByte6 = encodedBlock[5];
74 | encodedByte7 = encodedBlock[6];
75 | encodedByte8 = encodedBlock[7];
76 | decodedBytes[decodedBaseIndex] = ((encodedByte1 << 3) & 0xF8) | ((encodedByte2 >> 2) & 0x07);
77 | decodedBytes[decodedBaseIndex+1] = ((encodedByte2 << 6) & 0xC0) | ((encodedByte3 << 1) & 0x3E) | ((encodedByte4 >> 4) & 0x01);
78 | decodedBytes[decodedBaseIndex+2] = ((encodedByte4 << 4) & 0xF0) | ((encodedByte5 >> 1) & 0x0F);
79 | decodedBytes[decodedBaseIndex+3] = ((encodedByte5 << 7) & 0x80) | ((encodedByte6 << 2) & 0x7C) | ((encodedByte7 >> 3) & 0x03);
80 | decodedBytes[decodedBaseIndex+4] = ((encodedByte7 << 5) & 0xE0) | (encodedByte8 & 0x1F);
81 | decodedBaseIndex += 5;
82 | encodedBlockIndex = 0;
83 | }
84 | }
85 | encodedByte7 = 0;
86 | encodedByte6 = 0;
87 | encodedByte5 = 0;
88 | encodedByte4 = 0;
89 | encodedByte3 = 0;
90 | encodedByte2 = 0;
91 | switch (encodedBlockIndex) {
92 | case 7:
93 | encodedByte7 = encodedBlock[6];
94 | case 6:
95 | encodedByte6 = encodedBlock[5];
96 | case 5:
97 | encodedByte5 = encodedBlock[4];
98 | case 4:
99 | encodedByte4 = encodedBlock[3];
100 | case 3:
101 | encodedByte3 = encodedBlock[2];
102 | case 2:
103 | encodedByte2 = encodedBlock[1];
104 | case 1:
105 | encodedByte1 = encodedBlock[0];
106 | decodedBytes[decodedBaseIndex] = ((encodedByte1 << 3) & 0xF8) | ((encodedByte2 >> 2) & 0x07);
107 | decodedBytes[decodedBaseIndex+1] = ((encodedByte2 << 6) & 0xC0) | ((encodedByte3 << 1) & 0x3E) | ((encodedByte4 >> 4) & 0x01);
108 | decodedBytes[decodedBaseIndex+2] = ((encodedByte4 << 4) & 0xF0) | ((encodedByte5 >> 1) & 0x0F);
109 | decodedBytes[decodedBaseIndex+3] = ((encodedByte5 << 7) & 0x80) | ((encodedByte6 << 2) & 0x7C) | ((encodedByte7 >> 3) & 0x03);
110 | decodedBytes[decodedBaseIndex+4] = ((encodedByte7 << 5) & 0xE0);
111 | }
112 | decodedBaseIndex += paddingAdjustment[encodedBlockIndex];
113 | data = [[NSData alloc] initWithBytes:decodedBytes length:decodedBaseIndex];
114 | }
115 | }
116 | @catch (NSException *exception) {
117 | data = nil;
118 | NSLog(@"WARNING: error occured while decoding base 32 string: %@", exception);
119 | }
120 | @finally {
121 | if( decodedBytes != NULL ) {
122 | free( decodedBytes );
123 | }
124 | }
125 | return data;
126 | }
127 | +(NSString *)base32StringFromData:(NSData *)data
128 | {
129 | NSString *encoding = nil;
130 | unsigned char *encodingBytes = NULL;
131 | @try {
132 | static char encodingTable[32] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
133 | static NSUInteger paddingTable[] = {0,6,4,3,1};
134 |
135 | // Table 3: The Base 32 Alphabet
136 | //
137 | // Value Encoding Value Encoding Value Encoding Value Encoding
138 | // 0 A 9 J 18 S 27 3
139 | // 1 B 10 K 19 T 28 4
140 | // 2 C 11 L 20 U 29 5
141 | // 3 D 12 M 21 V 30 6
142 | // 4 E 13 N 22 W 31 7
143 | // 5 F 14 O 23 X
144 | // 6 G 15 P 24 Y (pad) =
145 | // 7 H 16 Q 25 Z
146 | // 8 I 17 R 26 2
147 |
148 | NSUInteger dataLength = [data length];
149 | NSUInteger encodedBlocks = (dataLength * 8) / 40;
150 | NSUInteger padding = paddingTable[dataLength % 5];
151 | if( padding > 0 ) encodedBlocks++;
152 | NSUInteger encodedLength = encodedBlocks * 8;
153 |
154 | encodingBytes = malloc(encodedLength);
155 | if( encodingBytes != NULL ) {
156 | NSUInteger rawBytesToProcess = dataLength;
157 | NSUInteger rawBaseIndex = 0;
158 | NSUInteger encodingBaseIndex = 0;
159 | unsigned char *rawBytes = (unsigned char *)[data bytes];
160 | unsigned char rawByte1, rawByte2, rawByte3, rawByte4, rawByte5;
161 | while( rawBytesToProcess >= 5 ) {
162 | rawByte1 = rawBytes[rawBaseIndex];
163 | rawByte2 = rawBytes[rawBaseIndex+1];
164 | rawByte3 = rawBytes[rawBaseIndex+2];
165 | rawByte4 = rawBytes[rawBaseIndex+3];
166 | rawByte5 = rawBytes[rawBaseIndex+4];
167 | encodingBytes[encodingBaseIndex] = encodingTable[((rawByte1 >> 3) & 0x1F)];
168 | encodingBytes[encodingBaseIndex+1] = encodingTable[((rawByte1 << 2) & 0x1C) | ((rawByte2 >> 6) & 0x03) ];
169 | encodingBytes[encodingBaseIndex+2] = encodingTable[((rawByte2 >> 1) & 0x1F)];
170 | encodingBytes[encodingBaseIndex+3] = encodingTable[((rawByte2 << 4) & 0x10) | ((rawByte3 >> 4) & 0x0F)];
171 | encodingBytes[encodingBaseIndex+4] = encodingTable[((rawByte3 << 1) & 0x1E) | ((rawByte4 >> 7) & 0x01)];
172 | encodingBytes[encodingBaseIndex+5] = encodingTable[((rawByte4 >> 2) & 0x1F)];
173 | encodingBytes[encodingBaseIndex+6] = encodingTable[((rawByte4 << 3) & 0x18) | ((rawByte5 >> 5) & 0x07)];
174 | encodingBytes[encodingBaseIndex+7] = encodingTable[rawByte5 & 0x1F];
175 |
176 | rawBaseIndex += 5;
177 | encodingBaseIndex += 8;
178 | rawBytesToProcess -= 5;
179 | }
180 | rawByte4 = 0;
181 | rawByte3 = 0;
182 | rawByte2 = 0;
183 | switch (dataLength-rawBaseIndex) {
184 | case 4:
185 | rawByte4 = rawBytes[rawBaseIndex+3];
186 | case 3:
187 | rawByte3 = rawBytes[rawBaseIndex+2];
188 | case 2:
189 | rawByte2 = rawBytes[rawBaseIndex+1];
190 | case 1:
191 | rawByte1 = rawBytes[rawBaseIndex];
192 | encodingBytes[encodingBaseIndex] = encodingTable[((rawByte1 >> 3) & 0x1F)];
193 | encodingBytes[encodingBaseIndex+1] = encodingTable[((rawByte1 << 2) & 0x1C) | ((rawByte2 >> 6) & 0x03) ];
194 | encodingBytes[encodingBaseIndex+2] = encodingTable[((rawByte2 >> 1) & 0x1F)];
195 | encodingBytes[encodingBaseIndex+3] = encodingTable[((rawByte2 << 4) & 0x10) | ((rawByte3 >> 4) & 0x0F)];
196 | encodingBytes[encodingBaseIndex+4] = encodingTable[((rawByte3 << 1) & 0x1E) | ((rawByte4 >> 7) & 0x01)];
197 | encodingBytes[encodingBaseIndex+5] = encodingTable[((rawByte4 >> 2) & 0x1F)];
198 | encodingBytes[encodingBaseIndex+6] = encodingTable[((rawByte4 << 3) & 0x18)];
199 | // we can skip rawByte5 since we have a partial block it would always be 0
200 | break;
201 | }
202 | // compute location from where to begin inserting padding, it may overwrite some bytes from the partial block encoding
203 | // if their value was 0 (cases 1-3).
204 | encodingBaseIndex = encodedLength - padding;
205 | while( padding-- > 0 ) {
206 | encodingBytes[encodingBaseIndex++] = '=';
207 | }
208 | encoding = [[NSString alloc] initWithBytes:encodingBytes length:encodedLength encoding:NSASCIIStringEncoding];
209 | }
210 | }
211 | @catch (NSException *exception) {
212 | encoding = nil;
213 | NSLog(@"WARNING: error occured while tring to encode base 32 data: %@", exception);
214 | }
215 | @finally {
216 | if( encodingBytes != NULL ) {
217 | free( encodingBytes );
218 | }
219 | }
220 | return encoding;
221 | }
222 |
223 | @end
224 |
225 | @implementation NSString (Base32Addition)
226 | -(NSString *)base32String
227 | {
228 | NSData *utf8encoding = [self dataUsingEncoding:NSUTF8StringEncoding];
229 | return [Base32Addition base32StringFromData:utf8encoding];
230 | }
231 | +(NSString *)stringFromBase32String:(NSString *)base32String
232 | {
233 | NSData *utf8encoding = [Base32Addition dataFromBase32String:base32String];
234 | return [[NSString alloc] initWithData:utf8encoding encoding:NSUTF8StringEncoding];
235 | }
236 | @end
237 |
238 | @implementation NSData (Base32Addition)
239 | +(NSData *)dataWithBase32String:(NSString *)base32String
240 | {
241 | return [Base32Addition dataFromBase32String:base32String];
242 | }
243 | -(NSString *)base32String
244 | {
245 | return [Base32Addition base32StringFromData:self];
246 | }
247 | @end
248 |
249 |
--------------------------------------------------------------------------------
/MinaOTP-MAC/FlatButton.swift:
--------------------------------------------------------------------------------
1 | //
2 | // FlatButton.swift
3 | // Disk Sensei
4 | //
5 | // Created by Oskar Groth on 02/08/16.
6 | // Copyright © 2016 Cindori. All rights reserved.
7 | //
8 | import Cocoa
9 | import QuartzCore
10 |
11 | internal extension CALayer {
12 | func animate(color: CGColor, keyPath: String, duration: Double) {
13 | if value(forKey: keyPath) as! CGColor? != color {
14 | let animation = CABasicAnimation(keyPath: keyPath)
15 | animation.toValue = color
16 | animation.fromValue = value(forKey: keyPath)
17 | animation.duration = duration
18 | animation.isRemovedOnCompletion = false
19 | animation.fillMode = CAMediaTimingFillMode.forwards
20 | add(animation, forKey: keyPath)
21 | setValue(color, forKey: keyPath)
22 | }
23 | }
24 | }
25 |
26 | //unused for now
27 | internal extension NSColor {
28 | func tintedColor() -> NSColor {
29 | var h = CGFloat(), s = CGFloat(), b = CGFloat(), a = CGFloat()
30 | let rgbColor = usingColorSpaceName(NSColorSpaceName.calibratedRGB)
31 | rgbColor?.getHue(&h, saturation: &s, brightness: &b, alpha: &a)
32 | return NSColor(hue: h, saturation: s, brightness: b == 0 ? 0.2 : b * 0.8, alpha: a)
33 | }
34 | }
35 |
36 | open class FlatButton: NSButton, CALayerDelegate {
37 |
38 | internal var containerLayer = CALayer()
39 | internal var iconLayer = CAShapeLayer()
40 | internal var alternateIconLayer = CAShapeLayer()
41 | internal var titleLayer = CATextLayer()
42 | internal var mouseDown = Bool()
43 | @IBInspectable public var momentary: Bool = true {
44 | didSet {
45 | animateColor(state == .on)
46 | }
47 | }
48 | @IBInspectable public var onAnimationDuration: Double = 0
49 | @IBInspectable public var offAnimationDuration: Double = 0.1
50 | @IBInspectable public var glowRadius: CGFloat = 0 {
51 | didSet {
52 | containerLayer.shadowRadius = glowRadius
53 | animateColor(state == .on)
54 | }
55 | }
56 | @IBInspectable public var glowOpacity: Float = 0 {
57 | didSet {
58 | containerLayer.shadowOpacity = glowOpacity
59 | animateColor(state == .on)
60 | }
61 | }
62 | @IBInspectable public var cornerRadius: CGFloat = 4 {
63 | didSet {
64 | layer?.cornerRadius = cornerRadius
65 | }
66 | }
67 | @IBInspectable public var borderWidth: CGFloat = 1 {
68 | didSet {
69 | layer?.borderWidth = borderWidth
70 | }
71 | }
72 | @IBInspectable public var borderColor: NSColor = NSColor.darkGray {
73 | didSet {
74 | animateColor(state == .on)
75 | }
76 | }
77 | @IBInspectable public var activeBorderColor: NSColor = NSColor.white {
78 | didSet {
79 | animateColor(state == .on)
80 | }
81 | }
82 | @IBInspectable public var buttonColor: NSColor = NSColor.white {
83 | didSet {
84 | animateColor(state == .on)
85 | }
86 | }
87 | @IBInspectable public var activeButtonColor: NSColor = NSColor.white {
88 | didSet {
89 | animateColor(state == .on)
90 | }
91 | }
92 | @IBInspectable public var iconColor: NSColor = NSColor.gray {
93 | didSet {
94 | animateColor(state == .on)
95 | }
96 | }
97 | @IBInspectable public var activeIconColor: NSColor = NSColor.black {
98 | didSet {
99 | animateColor(state == .on)
100 | }
101 | }
102 | @IBInspectable public var textColor: NSColor = NSColor.gray {
103 | didSet {
104 | animateColor(state == .on)
105 | }
106 | }
107 | @IBInspectable public var activeTextColor: NSColor = NSColor.gray {
108 | didSet {
109 | animateColor(state == .on)
110 | }
111 | }
112 |
113 | override open var title: String {
114 | didSet {
115 | setupTitle()
116 | }
117 | }
118 | override open var font: NSFont? {
119 | didSet {
120 | setupTitle()
121 | }
122 | }
123 | override open var frame: NSRect {
124 | didSet {
125 | positionTitleAndImage()
126 | }
127 | }
128 | override open var image: NSImage? {
129 | didSet {
130 | setupImage()
131 | }
132 | }
133 | override open var alternateImage: NSImage? {
134 | didSet {
135 | setupImage()
136 | }
137 | }
138 | override open var isEnabled: Bool {
139 | didSet {
140 | alphaValue = isEnabled ? 1 : 0.5
141 | }
142 | }
143 |
144 |
145 | // MARK: Setup & Initialization
146 |
147 | required public init?(coder: NSCoder) {
148 | super.init(coder: coder)
149 | setup()
150 | }
151 |
152 | override init(frame: NSRect) {
153 | super.init(frame: frame)
154 | setup()
155 | }
156 |
157 | internal func setup() {
158 | wantsLayer = true
159 | layer?.masksToBounds = false
160 | containerLayer.masksToBounds = false
161 | layer?.cornerRadius = 4
162 | layer?.borderWidth = 1
163 | layer?.delegate = self
164 | //containerLayer.backgroundColor = NSColor.blue.withAlphaComponent(0.1).cgColor
165 | //titleLayer.backgroundColor = NSColor.red.withAlphaComponent(0.2).cgColor
166 | titleLayer.delegate = self
167 | if let scale = window?.backingScaleFactor {
168 | titleLayer.contentsScale = scale
169 | }
170 | iconLayer.delegate = self
171 | alternateIconLayer.delegate = self
172 | iconLayer.masksToBounds = true
173 | alternateIconLayer.masksToBounds = true
174 | containerLayer.shadowOffset = NSSize.zero
175 | containerLayer.shadowColor = NSColor.clear.cgColor
176 | containerLayer.frame = NSMakeRect(0, 0, bounds.width, bounds.height)
177 | containerLayer.addSublayer(iconLayer)
178 | containerLayer.addSublayer(alternateIconLayer)
179 | containerLayer.addSublayer(titleLayer)
180 | layer?.addSublayer(containerLayer)
181 | setupTitle()
182 | setupImage()
183 | }
184 |
185 | internal func setupTitle() {
186 | guard let font = font else {
187 | return
188 | }
189 | titleLayer.string = title
190 | titleLayer.font = font
191 | titleLayer.fontSize = font.pointSize
192 | positionTitleAndImage()
193 | }
194 |
195 | func positionTitleAndImage() {
196 | let attributes = [NSAttributedString.Key.font: font as Any]
197 | let titleSize = title.size(withAttributes: attributes)
198 | var titleRect = NSMakeRect(0, 0, titleSize.width, titleSize.height)
199 | var imageRect = iconLayer.frame
200 | let hSpacing = round((bounds.width-(imageRect.width+titleSize.width))/3)
201 | let vSpacing = round((bounds.height-(imageRect.height+titleSize.height))/3)
202 |
203 | switch imagePosition {
204 | case .imageAbove:
205 | titleRect.origin.y = bounds.height-titleRect.height - 2
206 | titleRect.origin.x = round((bounds.width - titleSize.width)/2)
207 | imageRect.origin.y = vSpacing
208 | imageRect.origin.x = round((bounds.width - imageRect.width)/2)
209 | break
210 | case .imageBelow:
211 | titleRect.origin.y = 2
212 | titleRect.origin.x = round((bounds.width - titleSize.width)/2)
213 | imageRect.origin.y = bounds.height-vSpacing-imageRect.height
214 | imageRect.origin.x = round((bounds.width - imageRect.width)/2)
215 | break
216 | case .imageLeft:
217 | titleRect.origin.y = round((bounds.height - titleSize.height)/2)
218 | titleRect.origin.x = bounds.width - titleSize.width - 6
219 | imageRect.origin.y = round((bounds.height - imageRect.height)/2)
220 | imageRect.origin.x = hSpacing
221 | break
222 | case .imageRight:
223 | titleRect.origin.y = round((bounds.height - titleSize.height)/2)
224 | titleRect.origin.x = 2
225 | imageRect.origin.y = round((bounds.height - imageRect.height)/2)
226 | imageRect.origin.x = bounds.width - imageRect.width - hSpacing
227 | break
228 | default:
229 | titleRect.origin.y = round((bounds.height - titleSize.height)/2)
230 | titleRect.origin.x = round((bounds.width - titleSize.width)/2)
231 | }
232 | iconLayer.frame = imageRect
233 | alternateIconLayer.frame = imageRect
234 | titleLayer.frame = titleRect
235 | }
236 |
237 | internal func setupImage() {
238 | guard let image = image else {
239 | return
240 | }
241 | let maskLayer = CALayer()
242 | let imageSize = image.size
243 | var imageRect:CGRect = NSMakeRect(0, 0, imageSize.width, imageSize.height)
244 | let imageRef = image.cgImage(forProposedRect: &imageRect, context: nil, hints: nil)
245 | maskLayer.contents = imageRef
246 | iconLayer.frame = imageRect
247 | maskLayer.frame = imageRect
248 | iconLayer.mask = maskLayer
249 |
250 | if let alternateImage = alternateImage {
251 | let altMaskLayer = CALayer()
252 | let altImageSize = alternateImage.size
253 | var altImageRect:CGRect = NSMakeRect(0, 0, altImageSize.width, altImageSize.height)
254 | let altImageRef = alternateImage.cgImage(forProposedRect: &altImageRect, context: nil, hints: nil)
255 | altMaskLayer.contents = altImageRef
256 | alternateIconLayer.frame = altImageRect
257 | altMaskLayer.frame = altImageRect
258 | alternateIconLayer.mask = altMaskLayer
259 | alternateIconLayer.frame = altImageRect
260 | }
261 | positionTitleAndImage()
262 | }
263 |
264 | override open func awakeFromNib() {
265 | super.awakeFromNib()
266 | let trackingArea = NSTrackingArea(rect: bounds, options: [.activeAlways, .inVisibleRect, .mouseEnteredAndExited], owner: self, userInfo: nil)
267 | addTrackingArea(trackingArea)
268 | }
269 |
270 | // MARK: Animations
271 |
272 | internal func removeAnimations() {
273 | layer?.removeAllAnimations()
274 | if layer?.sublayers != nil {
275 | for subLayer in (layer?.sublayers)! {
276 | subLayer.removeAllAnimations()
277 | }
278 | }
279 | }
280 |
281 | public func animateColor(_ isOn: Bool) {
282 | removeAnimations()
283 | let duration = isOn ? onAnimationDuration : offAnimationDuration
284 | let bgColor = isOn ? activeButtonColor : buttonColor
285 | let titleColor = isOn ? activeTextColor : textColor
286 | let imageColor = isOn ? activeIconColor : iconColor
287 | let borderColor = isOn ? activeBorderColor : self.borderColor
288 | layer?.animate(color: bgColor.cgColor, keyPath: "backgroundColor", duration: duration)
289 | layer?.animate(color: borderColor.cgColor, keyPath: "borderColor", duration: duration)
290 |
291 | /* I started seeing high (~5%) background CPU usage in apps using
292 | FlatButton, and was able to track it down to background CATextLayer animation calls
293 | happening constantly, originating from the call below. It could be a CATextLayer bug.
294 | For now I'm going with setting the color instantly as it fixes this issue. */
295 | //titleLayer.animate(color: titleColor.cgColor, keyPath: "foregroundColor", duration: duration)
296 | titleLayer.foregroundColor = titleColor.cgColor
297 |
298 | if alternateImage == nil {
299 | iconLayer.animate(color: imageColor.cgColor, keyPath: "backgroundColor", duration: duration)
300 | } else {
301 | iconLayer.animate(color: isOn ? NSColor.clear.cgColor : iconColor.cgColor, keyPath: "backgroundColor", duration: duration)
302 | alternateIconLayer.animate(color: isOn ? activeIconColor.cgColor : NSColor.clear.cgColor, keyPath: "backgroundColor", duration: duration)
303 | }
304 |
305 | // Shadows
306 |
307 | if glowRadius > 0, glowOpacity > 0 {
308 | containerLayer.animate(color: isOn ? activeIconColor.cgColor : NSColor.clear.cgColor, keyPath: "shadowColor", duration: duration)
309 | }
310 | }
311 |
312 | // MARK: Interaction
313 |
314 | public func setOn(_ isOn: Bool) {
315 | // let nextState = isOn ? .on : .off
316 | let nextState = isOn ? NSControl.StateValue.on : NSControl.StateValue.off
317 | if nextState != state {
318 | state = nextState
319 | animateColor(state == .on)
320 | }
321 | }
322 |
323 | override open func hitTest(_ point: NSPoint) -> NSView? {
324 | return isEnabled ? super.hitTest(point) : nil
325 | }
326 |
327 | override open func mouseDown(with event: NSEvent) {
328 | if isEnabled {
329 | mouseDown = true
330 | setOn(state == .on ? false : true)
331 | }
332 | }
333 |
334 | override open func mouseEntered(with event: NSEvent) {
335 | if mouseDown {
336 | setOn(state == .on ? false : true)
337 | }
338 | }
339 |
340 | override open func mouseExited(with event: NSEvent) {
341 | if mouseDown {
342 | setOn(state == .on ? false : true)
343 | mouseDown = false
344 | }
345 | }
346 |
347 | override open func mouseUp(with event: NSEvent) {
348 | if mouseDown {
349 | mouseDown = false
350 | if momentary {
351 | setOn(state == .on ? false : true)
352 | }
353 | _ = target?.perform(action, with: self)
354 | }
355 | }
356 |
357 | // MARK: Drawing
358 |
359 | override open func viewDidChangeBackingProperties() {
360 | super.viewDidChangeBackingProperties()
361 | if let scale = window?.backingScaleFactor {
362 | titleLayer.contentsScale = scale
363 | layer?.contentsScale = scale
364 | iconLayer.contentsScale = scale
365 | }
366 | }
367 |
368 | open func layer(_ layer: CALayer, shouldInheritContentsScale newScale: CGFloat, from window: NSWindow) -> Bool {
369 | return true
370 | }
371 |
372 | override open func draw(_ dirtyRect: NSRect) {
373 |
374 | }
375 |
376 | override open func layout() {
377 | super.layout()
378 | positionTitleAndImage()
379 | }
380 |
381 | override open func updateLayer() {
382 | super.updateLayer()
383 | }
384 |
385 |
386 | }
387 |
--------------------------------------------------------------------------------
/MinaOTP-MAC/en.lproj/MainMenu.strings:
--------------------------------------------------------------------------------
1 |
2 | /* Class = "NSMenuItem"; title = "Customize Toolbar…"; ObjectID = "1UK-8n-QPP"; */
3 | "1UK-8n-QPP.title" = "Customize Toolbar…";
4 |
5 | /* Class = "NSMenuItem"; title = "MinaOTP-MAC"; ObjectID = "1Xt-HY-uBw"; */
6 | "1Xt-HY-uBw.title" = "MinaOTP-MAC";
7 |
8 | /* Class = "NSMenu"; title = "Find"; ObjectID = "1b7-l0-nxx"; */
9 | "1b7-l0-nxx.title" = "Find";
10 |
11 | /* Class = "NSMenuItem"; title = "Lower"; ObjectID = "1tx-W0-xDw"; */
12 | "1tx-W0-xDw.title" = "Lower";
13 |
14 | /* Class = "NSMenuItem"; title = "Raise"; ObjectID = "2h7-ER-AoG"; */
15 | "2h7-ER-AoG.title" = "Raise";
16 |
17 | /* Class = "NSMenuItem"; title = "Transformations"; ObjectID = "2oI-Rn-ZJC"; */
18 | "2oI-Rn-ZJC.title" = "Transformations";
19 |
20 | /* Class = "NSMenu"; title = "Spelling"; ObjectID = "3IN-sU-3Bg"; */
21 | "3IN-sU-3Bg.title" = "Spelling";
22 |
23 | /* Class = "NSMenuItem"; title = "Use Default"; ObjectID = "3Om-Ey-2VK"; */
24 | "3Om-Ey-2VK.title" = "Use Default";
25 |
26 | /* Class = "NSMenu"; title = "Speech"; ObjectID = "3rS-ZA-NoH"; */
27 | "3rS-ZA-NoH.title" = "Speech";
28 |
29 | /* Class = "NSMenuItem"; title = "Tighten"; ObjectID = "46P-cB-AYj"; */
30 | "46P-cB-AYj.title" = "Tighten";
31 |
32 | /* Class = "NSMenuItem"; title = "Find"; ObjectID = "4EN-yA-p0u"; */
33 | "4EN-yA-p0u.title" = "Find";
34 |
35 | /* Class = "NSMenuItem"; title = "Enter Full Screen"; ObjectID = "4J7-dP-txa"; */
36 | "4J7-dP-txa.title" = "Enter Full Screen";
37 |
38 | /* Class = "NSMenuItem"; title = "Quit MinaOTP-MAC"; ObjectID = "4sb-4s-VLi"; */
39 | "4sb-4s-VLi.title" = "Quit MinaOTP-MAC";
40 |
41 | /* Class = "NSMenuItem"; title = "Edit"; ObjectID = "5QF-Oa-p0T"; */
42 | "5QF-Oa-p0T.title" = "Edit";
43 |
44 | /* Class = "NSMenuItem"; title = "Copy Style"; ObjectID = "5Vv-lz-BsD"; */
45 | "5Vv-lz-BsD.title" = "Copy Style";
46 |
47 | /* Class = "NSMenuItem"; title = "About MinaOTP-MAC"; ObjectID = "5kV-Vb-QxS"; */
48 | "5kV-Vb-QxS.title" = "About MinaOTP-MAC";
49 |
50 | /* Class = "NSMenuItem"; title = "Redo"; ObjectID = "6dh-zS-Vam"; */
51 | "6dh-zS-Vam.title" = "Redo";
52 |
53 | /* Class = "NSMenuItem"; title = "Correct Spelling Automatically"; ObjectID = "78Y-hA-62v"; */
54 | "78Y-hA-62v.title" = "Correct Spelling Automatically";
55 |
56 | /* Class = "NSMenu"; title = "Writing Direction"; ObjectID = "8mr-sm-Yjd"; */
57 | "8mr-sm-Yjd.title" = "Writing Direction";
58 |
59 | /* Class = "NSMenuItem"; title = "Substitutions"; ObjectID = "9ic-FL-obx"; */
60 | "9ic-FL-obx.title" = "Substitutions";
61 |
62 | /* Class = "NSMenuItem"; title = "Smart Copy/Paste"; ObjectID = "9yt-4B-nSM"; */
63 | "9yt-4B-nSM.title" = "Smart Copy/Paste";
64 |
65 | /* Class = "NSMenu"; title = "Main Menu"; ObjectID = "AYu-sK-qS6"; */
66 | "AYu-sK-qS6.title" = "Main Menu";
67 |
68 | /* Class = "NSMenuItem"; title = "Preferences…"; ObjectID = "BOF-NM-1cW"; */
69 | "BOF-NM-1cW.title" = "Preferences…";
70 |
71 | /* Class = "NSMenuItem"; title = "\tLeft to Right"; ObjectID = "BgM-ve-c93"; */
72 | "BgM-ve-c93.title" = "\tLeft to Right";
73 |
74 | /* Class = "NSMenuItem"; title = "Save As…"; ObjectID = "Bw7-FT-i3A"; */
75 | "Bw7-FT-i3A.title" = "Save As…";
76 |
77 | /* Class = "NSMenuItem"; title = "Close"; ObjectID = "DVo-aG-piG"; */
78 | "DVo-aG-piG.title" = "Close";
79 |
80 | /* Class = "NSMenuItem"; title = "Spelling and Grammar"; ObjectID = "Dv1-io-Yv7"; */
81 | "Dv1-io-Yv7.title" = "Spelling and Grammar";
82 |
83 | /* Class = "NSMenu"; title = "Help"; ObjectID = "F2S-fz-NVQ"; */
84 | "F2S-fz-NVQ.title" = "Help";
85 |
86 | /* Class = "NSMenuItem"; title = "MinaOTP-MAC Help"; ObjectID = "FKE-Sm-Kum"; */
87 | "FKE-Sm-Kum.title" = "MinaOTP-MAC Help";
88 |
89 | /* Class = "NSMenuItem"; title = "Text"; ObjectID = "Fal-I4-PZk"; */
90 | "Fal-I4-PZk.title" = "Text";
91 |
92 | /* Class = "NSMenu"; title = "Substitutions"; ObjectID = "FeM-D8-WVr"; */
93 | "FeM-D8-WVr.title" = "Substitutions";
94 |
95 | /* Class = "NSMenuItem"; title = "Bold"; ObjectID = "GB9-OM-e27"; */
96 | "GB9-OM-e27.title" = "Bold";
97 |
98 | /* Class = "NSMenu"; title = "Format"; ObjectID = "GEO-Iw-cKr"; */
99 | "GEO-Iw-cKr.title" = "Format";
100 |
101 | /* Class = "NSMenuItem"; title = "Use Default"; ObjectID = "GUa-eO-cwY"; */
102 | "GUa-eO-cwY.title" = "Use Default";
103 |
104 | /* Class = "NSMenuItem"; title = "Font"; ObjectID = "Gi5-1S-RQB"; */
105 | "Gi5-1S-RQB.title" = "Font";
106 |
107 | /* Class = "NSMenuItem"; title = "Writing Direction"; ObjectID = "H1b-Si-o9J"; */
108 | "H1b-Si-o9J.title" = "Writing Direction";
109 |
110 | /* Class = "NSMenuItem"; title = "View"; ObjectID = "H8h-7b-M4v"; */
111 | "H8h-7b-M4v.title" = "View";
112 |
113 | /* Class = "NSMenuItem"; title = "Text Replacement"; ObjectID = "HFQ-gK-NFA"; */
114 | "HFQ-gK-NFA.title" = "Text Replacement";
115 |
116 | /* Class = "NSMenuItem"; title = "Show Spelling and Grammar"; ObjectID = "HFo-cy-zxI"; */
117 | "HFo-cy-zxI.title" = "Show Spelling and Grammar";
118 |
119 | /* Class = "NSMenu"; title = "View"; ObjectID = "HyV-fh-RgO"; */
120 | "HyV-fh-RgO.title" = "View";
121 |
122 | /* Class = "NSMenuItem"; title = "Subscript"; ObjectID = "I0S-gh-46l"; */
123 | "I0S-gh-46l.title" = "Subscript";
124 |
125 | /* Class = "NSMenuItem"; title = "Open…"; ObjectID = "IAo-SY-fd9"; */
126 | "IAo-SY-fd9.title" = "Open…";
127 |
128 | /* Class = "NSMenuItem"; title = "Justify"; ObjectID = "J5U-5w-g23"; */
129 | "J5U-5w-g23.title" = "Justify";
130 |
131 | /* Class = "NSMenuItem"; title = "Use None"; ObjectID = "J7y-lM-qPV"; */
132 | "J7y-lM-qPV.title" = "Use None";
133 |
134 | /* Class = "NSMenuItem"; title = "Revert to Saved"; ObjectID = "KaW-ft-85H"; */
135 | "KaW-ft-85H.title" = "Revert to Saved";
136 |
137 | /* Class = "NSMenuItem"; title = "Show All"; ObjectID = "Kd2-mp-pUS"; */
138 | "Kd2-mp-pUS.title" = "Show All";
139 |
140 | /* Class = "NSMenuItem"; title = "Bring All to Front"; ObjectID = "LE2-aR-0XJ"; */
141 | "LE2-aR-0XJ.title" = "Bring All to Front";
142 |
143 | /* Class = "NSMenuItem"; title = "Paste Ruler"; ObjectID = "LVM-kO-fVI"; */
144 | "LVM-kO-fVI.title" = "Paste Ruler";
145 |
146 | /* Class = "NSMenuItem"; title = "\tLeft to Right"; ObjectID = "Lbh-J2-qVU"; */
147 | "Lbh-J2-qVU.title" = "\tLeft to Right";
148 |
149 | /* Class = "NSMenuItem"; title = "Copy Ruler"; ObjectID = "MkV-Pr-PK5"; */
150 | "MkV-Pr-PK5.title" = "Copy Ruler";
151 |
152 | /* Class = "NSMenuItem"; title = "Services"; ObjectID = "NMo-om-nkz"; */
153 | "NMo-om-nkz.title" = "Services";
154 |
155 | /* Class = "NSMenuItem"; title = "\tDefault"; ObjectID = "Nop-cj-93Q"; */
156 | "Nop-cj-93Q.title" = "\tDefault";
157 |
158 | /* Class = "NSMenuItem"; title = "Minimize"; ObjectID = "OY7-WF-poV"; */
159 | "OY7-WF-poV.title" = "Minimize";
160 |
161 | /* Class = "NSMenuItem"; title = "Baseline"; ObjectID = "OaQ-X3-Vso"; */
162 | "OaQ-X3-Vso.title" = "Baseline";
163 |
164 | /* Class = "NSMenuItem"; title = "Hide MinaOTP-MAC"; ObjectID = "Olw-nP-bQN"; */
165 | "Olw-nP-bQN.title" = "Hide MinaOTP-MAC";
166 |
167 | /* Class = "NSMenuItem"; title = "Find Previous"; ObjectID = "OwM-mh-QMV"; */
168 | "OwM-mh-QMV.title" = "Find Previous";
169 |
170 | /* Class = "NSMenuItem"; title = "Stop Speaking"; ObjectID = "Oyz-dy-DGm"; */
171 | "Oyz-dy-DGm.title" = "Stop Speaking";
172 |
173 | /* Class = "NSMenuItem"; title = "Bigger"; ObjectID = "Ptp-SP-VEL"; */
174 | "Ptp-SP-VEL.title" = "Bigger";
175 |
176 | /* Class = "NSMenuItem"; title = "Show Fonts"; ObjectID = "Q5e-8K-NDq"; */
177 | "Q5e-8K-NDq.title" = "Show Fonts";
178 |
179 | /* Class = "NSMenuItem"; title = "Zoom"; ObjectID = "R4o-n2-Eq4"; */
180 | "R4o-n2-Eq4.title" = "Zoom";
181 |
182 | /* Class = "NSMenuItem"; title = "\tRight to Left"; ObjectID = "RB4-Sm-HuC"; */
183 | "RB4-Sm-HuC.title" = "\tRight to Left";
184 |
185 | /* Class = "NSMenuItem"; title = "Superscript"; ObjectID = "Rqc-34-cIF"; */
186 | "Rqc-34-cIF.title" = "Superscript";
187 |
188 | /* Class = "NSMenuItem"; title = "Select All"; ObjectID = "Ruw-6m-B2m"; */
189 | "Ruw-6m-B2m.title" = "Select All";
190 |
191 | /* Class = "NSMenuItem"; title = "Jump to Selection"; ObjectID = "S0p-oC-mLd"; */
192 | "S0p-oC-mLd.title" = "Jump to Selection";
193 |
194 | /* Class = "NSMenu"; title = "Window"; ObjectID = "Td7-aD-5lo"; */
195 | "Td7-aD-5lo.title" = "Window";
196 |
197 | /* Class = "NSMenuItem"; title = "Capitalize"; ObjectID = "UEZ-Bs-lqG"; */
198 | "UEZ-Bs-lqG.title" = "Capitalize";
199 |
200 | /* Class = "NSMenuItem"; title = "Center"; ObjectID = "VIY-Ag-zcb"; */
201 | "VIY-Ag-zcb.title" = "Center";
202 |
203 | /* Class = "NSMenuItem"; title = "Hide Others"; ObjectID = "Vdr-fp-XzO"; */
204 | "Vdr-fp-XzO.title" = "Hide Others";
205 |
206 | /* Class = "NSMenuItem"; title = "Italic"; ObjectID = "Vjx-xi-njq"; */
207 | "Vjx-xi-njq.title" = "Italic";
208 |
209 | /* Class = "NSMenu"; title = "Edit"; ObjectID = "W48-6f-4Dl"; */
210 | "W48-6f-4Dl.title" = "Edit";
211 |
212 | /* Class = "NSMenuItem"; title = "Underline"; ObjectID = "WRG-CD-K1S"; */
213 | "WRG-CD-K1S.title" = "Underline";
214 |
215 | /* Class = "NSMenuItem"; title = "New"; ObjectID = "Was-JA-tGl"; */
216 | "Was-JA-tGl.title" = "New";
217 |
218 | /* Class = "NSMenuItem"; title = "Paste and Match Style"; ObjectID = "WeT-3V-zwk"; */
219 | "WeT-3V-zwk.title" = "Paste and Match Style";
220 |
221 | /* Class = "NSMenuItem"; title = "Find…"; ObjectID = "Xz5-n4-O0W"; */
222 | "Xz5-n4-O0W.title" = "Find…";
223 |
224 | /* Class = "NSMenuItem"; title = "Find and Replace…"; ObjectID = "YEy-JH-Tfz"; */
225 | "YEy-JH-Tfz.title" = "Find and Replace…";
226 |
227 | /* Class = "NSMenuItem"; title = "\tDefault"; ObjectID = "YGs-j5-SAR"; */
228 | "YGs-j5-SAR.title" = "\tDefault";
229 |
230 | /* Class = "NSMenuItem"; title = "Start Speaking"; ObjectID = "Ynk-f8-cLZ"; */
231 | "Ynk-f8-cLZ.title" = "Start Speaking";
232 |
233 | /* Class = "NSMenuItem"; title = "Align Left"; ObjectID = "ZM1-6Q-yy1"; */
234 | "ZM1-6Q-yy1.title" = "Align Left";
235 |
236 | /* Class = "NSMenuItem"; title = "Paragraph"; ObjectID = "ZvO-Gk-QUH"; */
237 | "ZvO-Gk-QUH.title" = "Paragraph";
238 |
239 | /* Class = "NSMenuItem"; title = "Print…"; ObjectID = "aTl-1u-JFS"; */
240 | "aTl-1u-JFS.title" = "Print…";
241 |
242 | /* Class = "NSMenuItem"; title = "Window"; ObjectID = "aUF-d1-5bR"; */
243 | "aUF-d1-5bR.title" = "Window";
244 |
245 | /* Class = "NSMenu"; title = "Font"; ObjectID = "aXa-aM-Jaq"; */
246 | "aXa-aM-Jaq.title" = "Font";
247 |
248 | /* Class = "NSMenuItem"; title = "Use Default"; ObjectID = "agt-UL-0e3"; */
249 | "agt-UL-0e3.title" = "Use Default";
250 |
251 | /* Class = "NSMenuItem"; title = "Show Colors"; ObjectID = "bgn-CT-cEk"; */
252 | "bgn-CT-cEk.title" = "Show Colors";
253 |
254 | /* Class = "NSMenu"; title = "File"; ObjectID = "bib-Uj-vzu"; */
255 | "bib-Uj-vzu.title" = "File";
256 |
257 | /* Class = "NSMenuItem"; title = "Use Selection for Find"; ObjectID = "buJ-ug-pKt"; */
258 | "buJ-ug-pKt.title" = "Use Selection for Find";
259 |
260 | /* Class = "NSMenu"; title = "Transformations"; ObjectID = "c8a-y6-VQd"; */
261 | "c8a-y6-VQd.title" = "Transformations";
262 |
263 | /* Class = "NSMenuItem"; title = "Use None"; ObjectID = "cDB-IK-hbR"; */
264 | "cDB-IK-hbR.title" = "Use None";
265 |
266 | /* Class = "NSMenuItem"; title = "Selection"; ObjectID = "cqv-fj-IhA"; */
267 | "cqv-fj-IhA.title" = "Selection";
268 |
269 | /* Class = "NSMenuItem"; title = "Smart Links"; ObjectID = "cwL-P1-jid"; */
270 | "cwL-P1-jid.title" = "Smart Links";
271 |
272 | /* Class = "NSMenuItem"; title = "Make Lower Case"; ObjectID = "d9M-CD-aMd"; */
273 | "d9M-CD-aMd.title" = "Make Lower Case";
274 |
275 | /* Class = "NSMenu"; title = "Text"; ObjectID = "d9c-me-L2H"; */
276 | "d9c-me-L2H.title" = "Text";
277 |
278 | /* Class = "NSMenuItem"; title = "File"; ObjectID = "dMs-cI-mzQ"; */
279 | "dMs-cI-mzQ.title" = "File";
280 |
281 | /* Class = "NSMenuItem"; title = "Undo"; ObjectID = "dRJ-4n-Yzg"; */
282 | "dRJ-4n-Yzg.title" = "Undo";
283 |
284 | /* Class = "NSMenuItem"; title = "Paste"; ObjectID = "gVA-U4-sdL"; */
285 | "gVA-U4-sdL.title" = "Paste";
286 |
287 | /* Class = "NSMenuItem"; title = "Smart Quotes"; ObjectID = "hQb-2v-fYv"; */
288 | "hQb-2v-fYv.title" = "Smart Quotes";
289 |
290 | /* Class = "NSMenuItem"; title = "Check Document Now"; ObjectID = "hz2-CU-CR7"; */
291 | "hz2-CU-CR7.title" = "Check Document Now";
292 |
293 | /* Class = "NSMenu"; title = "Services"; ObjectID = "hz9-B4-Xy5"; */
294 | "hz9-B4-Xy5.title" = "Services";
295 |
296 | /* Class = "NSMenuItem"; title = "Smaller"; ObjectID = "i1d-Er-qST"; */
297 | "i1d-Er-qST.title" = "Smaller";
298 |
299 | /* Class = "NSMenu"; title = "Baseline"; ObjectID = "ijk-EB-dga"; */
300 | "ijk-EB-dga.title" = "Baseline";
301 |
302 | /* Class = "NSMenuItem"; title = "Kern"; ObjectID = "jBQ-r6-VK2"; */
303 | "jBQ-r6-VK2.title" = "Kern";
304 |
305 | /* Class = "NSMenuItem"; title = "\tRight to Left"; ObjectID = "jFq-tB-4Kx"; */
306 | "jFq-tB-4Kx.title" = "\tRight to Left";
307 |
308 | /* Class = "NSMenuItem"; title = "Format"; ObjectID = "jxT-CU-nIS"; */
309 | "jxT-CU-nIS.title" = "Format";
310 |
311 | /* Class = "NSMenuItem"; title = "Show Sidebar"; ObjectID = "kIP-vf-haE"; */
312 | "kIP-vf-haE.title" = "Show Sidebar";
313 |
314 | /* Class = "NSMenuItem"; title = "Check Grammar With Spelling"; ObjectID = "mK6-2p-4JG"; */
315 | "mK6-2p-4JG.title" = "Check Grammar With Spelling";
316 |
317 | /* Class = "NSMenuItem"; title = "Ligatures"; ObjectID = "o6e-r0-MWq"; */
318 | "o6e-r0-MWq.title" = "Ligatures";
319 |
320 | /* Class = "NSMenu"; title = "Open Recent"; ObjectID = "oas-Oc-fiZ"; */
321 | "oas-Oc-fiZ.title" = "Open Recent";
322 |
323 | /* Class = "NSMenuItem"; title = "Loosen"; ObjectID = "ogc-rX-tC1"; */
324 | "ogc-rX-tC1.title" = "Loosen";
325 |
326 | /* Class = "NSMenuItem"; title = "Delete"; ObjectID = "pa3-QI-u2k"; */
327 | "pa3-QI-u2k.title" = "Delete";
328 |
329 | /* Class = "NSMenuItem"; title = "Save…"; ObjectID = "pxx-59-PXV"; */
330 | "pxx-59-PXV.title" = "Save…";
331 |
332 | /* Class = "NSMenuItem"; title = "Find Next"; ObjectID = "q09-fT-Sye"; */
333 | "q09-fT-Sye.title" = "Find Next";
334 |
335 | /* Class = "NSMenuItem"; title = "Page Setup…"; ObjectID = "qIS-W8-SiK"; */
336 | "qIS-W8-SiK.title" = "Page Setup…";
337 |
338 | /* Class = "NSMenuItem"; title = "Check Spelling While Typing"; ObjectID = "rbD-Rh-wIN"; */
339 | "rbD-Rh-wIN.title" = "Check Spelling While Typing";
340 |
341 | /* Class = "NSMenuItem"; title = "Smart Dashes"; ObjectID = "rgM-f4-ycn"; */
342 | "rgM-f4-ycn.title" = "Smart Dashes";
343 |
344 | /* Class = "NSMenuItem"; title = "Show Toolbar"; ObjectID = "snW-S8-Cw5"; */
345 | "snW-S8-Cw5.title" = "Show Toolbar";
346 |
347 | /* Class = "NSMenuItem"; title = "Data Detectors"; ObjectID = "tRr-pd-1PS"; */
348 | "tRr-pd-1PS.title" = "Data Detectors";
349 |
350 | /* Class = "NSMenuItem"; title = "Open Recent"; ObjectID = "tXI-mr-wws"; */
351 | "tXI-mr-wws.title" = "Open Recent";
352 |
353 | /* Class = "NSMenu"; title = "Kern"; ObjectID = "tlD-Oa-oAM"; */
354 | "tlD-Oa-oAM.title" = "Kern";
355 |
356 | /* Class = "NSMenu"; title = "MinaOTP-MAC"; ObjectID = "uQy-DD-JDr"; */
357 | "uQy-DD-JDr.title" = "MinaOTP-MAC";
358 |
359 | /* Class = "NSMenuItem"; title = "Cut"; ObjectID = "uRl-iY-unG"; */
360 | "uRl-iY-unG.title" = "Cut";
361 |
362 | /* Class = "NSMenuItem"; title = "Paste Style"; ObjectID = "vKC-jM-MkH"; */
363 | "vKC-jM-MkH.title" = "Paste Style";
364 |
365 | /* Class = "NSMenuItem"; title = "Show Ruler"; ObjectID = "vLm-3I-IUL"; */
366 | "vLm-3I-IUL.title" = "Show Ruler";
367 |
368 | /* Class = "NSMenuItem"; title = "Clear Menu"; ObjectID = "vNY-rz-j42"; */
369 | "vNY-rz-j42.title" = "Clear Menu";
370 |
371 | /* Class = "NSMenuItem"; title = "Make Upper Case"; ObjectID = "vmV-6d-7jI"; */
372 | "vmV-6d-7jI.title" = "Make Upper Case";
373 |
374 | /* Class = "NSMenu"; title = "Ligatures"; ObjectID = "w0m-vy-SC9"; */
375 | "w0m-vy-SC9.title" = "Ligatures";
376 |
377 | /* Class = "NSMenuItem"; title = "Align Right"; ObjectID = "wb2-vD-lq4"; */
378 | "wb2-vD-lq4.title" = "Align Right";
379 |
380 | /* Class = "NSMenuItem"; title = "Help"; ObjectID = "wpr-3q-Mcd"; */
381 | "wpr-3q-Mcd.title" = "Help";
382 |
383 | /* Class = "NSMenuItem"; title = "Copy"; ObjectID = "x3v-GG-iWU"; */
384 | "x3v-GG-iWU.title" = "Copy";
385 |
386 | /* Class = "NSMenuItem"; title = "Use All"; ObjectID = "xQD-1f-W4t"; */
387 | "xQD-1f-W4t.title" = "Use All";
388 |
389 | /* Class = "NSMenuItem"; title = "Speech"; ObjectID = "xrE-MZ-jX0"; */
390 | "xrE-MZ-jX0.title" = "Speech";
391 |
392 | /* Class = "NSMenuItem"; title = "Show Substitutions"; ObjectID = "z6F-FW-3nz"; */
393 | "z6F-FW-3nz.title" = "Show Substitutions";
394 |
--------------------------------------------------------------------------------
/MinaOTP-MAC/zh-Hans.lproj/MainMenu.strings:
--------------------------------------------------------------------------------
1 |
2 | /* Class = "NSMenuItem"; title = "Customize Toolbar…"; ObjectID = "1UK-8n-QPP"; */
3 | "1UK-8n-QPP.title" = "Customize Toolbar…";
4 |
5 | /* Class = "NSMenuItem"; title = "MinaOTP-MAC"; ObjectID = "1Xt-HY-uBw"; */
6 | "1Xt-HY-uBw.title" = "MinaOTP-MAC";
7 |
8 | /* Class = "NSMenu"; title = "Find"; ObjectID = "1b7-l0-nxx"; */
9 | "1b7-l0-nxx.title" = "Find";
10 |
11 | /* Class = "NSMenuItem"; title = "Lower"; ObjectID = "1tx-W0-xDw"; */
12 | "1tx-W0-xDw.title" = "Lower";
13 |
14 | /* Class = "NSMenuItem"; title = "Raise"; ObjectID = "2h7-ER-AoG"; */
15 | "2h7-ER-AoG.title" = "Raise";
16 |
17 | /* Class = "NSMenuItem"; title = "Transformations"; ObjectID = "2oI-Rn-ZJC"; */
18 | "2oI-Rn-ZJC.title" = "Transformations";
19 |
20 | /* Class = "NSMenu"; title = "Spelling"; ObjectID = "3IN-sU-3Bg"; */
21 | "3IN-sU-3Bg.title" = "Spelling";
22 |
23 | /* Class = "NSMenuItem"; title = "Use Default"; ObjectID = "3Om-Ey-2VK"; */
24 | "3Om-Ey-2VK.title" = "Use Default";
25 |
26 | /* Class = "NSMenu"; title = "Speech"; ObjectID = "3rS-ZA-NoH"; */
27 | "3rS-ZA-NoH.title" = "Speech";
28 |
29 | /* Class = "NSMenuItem"; title = "Tighten"; ObjectID = "46P-cB-AYj"; */
30 | "46P-cB-AYj.title" = "Tighten";
31 |
32 | /* Class = "NSMenuItem"; title = "Find"; ObjectID = "4EN-yA-p0u"; */
33 | "4EN-yA-p0u.title" = "Find";
34 |
35 | /* Class = "NSMenuItem"; title = "Enter Full Screen"; ObjectID = "4J7-dP-txa"; */
36 | "4J7-dP-txa.title" = "Enter Full Screen";
37 |
38 | /* Class = "NSMenuItem"; title = "Quit MinaOTP-MAC"; ObjectID = "4sb-4s-VLi"; */
39 | "4sb-4s-VLi.title" = "Quit MinaOTP-MAC";
40 |
41 | /* Class = "NSMenuItem"; title = "Edit"; ObjectID = "5QF-Oa-p0T"; */
42 | "5QF-Oa-p0T.title" = "Edit";
43 |
44 | /* Class = "NSMenuItem"; title = "Copy Style"; ObjectID = "5Vv-lz-BsD"; */
45 | "5Vv-lz-BsD.title" = "Copy Style";
46 |
47 | /* Class = "NSMenuItem"; title = "About MinaOTP-MAC"; ObjectID = "5kV-Vb-QxS"; */
48 | "5kV-Vb-QxS.title" = "About MinaOTP-MAC";
49 |
50 | /* Class = "NSMenuItem"; title = "Redo"; ObjectID = "6dh-zS-Vam"; */
51 | "6dh-zS-Vam.title" = "Redo";
52 |
53 | /* Class = "NSMenuItem"; title = "Correct Spelling Automatically"; ObjectID = "78Y-hA-62v"; */
54 | "78Y-hA-62v.title" = "Correct Spelling Automatically";
55 |
56 | /* Class = "NSMenu"; title = "Writing Direction"; ObjectID = "8mr-sm-Yjd"; */
57 | "8mr-sm-Yjd.title" = "Writing Direction";
58 |
59 | /* Class = "NSMenuItem"; title = "Substitutions"; ObjectID = "9ic-FL-obx"; */
60 | "9ic-FL-obx.title" = "Substitutions";
61 |
62 | /* Class = "NSMenuItem"; title = "Smart Copy/Paste"; ObjectID = "9yt-4B-nSM"; */
63 | "9yt-4B-nSM.title" = "Smart Copy/Paste";
64 |
65 | /* Class = "NSMenu"; title = "Main Menu"; ObjectID = "AYu-sK-qS6"; */
66 | "AYu-sK-qS6.title" = "Main Menu";
67 |
68 | /* Class = "NSMenuItem"; title = "Preferences…"; ObjectID = "BOF-NM-1cW"; */
69 | "BOF-NM-1cW.title" = "Preferences…";
70 |
71 | /* Class = "NSMenuItem"; title = "\tLeft to Right"; ObjectID = "BgM-ve-c93"; */
72 | "BgM-ve-c93.title" = "\tLeft to Right";
73 |
74 | /* Class = "NSMenuItem"; title = "Save As…"; ObjectID = "Bw7-FT-i3A"; */
75 | "Bw7-FT-i3A.title" = "Save As…";
76 |
77 | /* Class = "NSMenuItem"; title = "Close"; ObjectID = "DVo-aG-piG"; */
78 | "DVo-aG-piG.title" = "Close";
79 |
80 | /* Class = "NSMenuItem"; title = "Spelling and Grammar"; ObjectID = "Dv1-io-Yv7"; */
81 | "Dv1-io-Yv7.title" = "Spelling and Grammar";
82 |
83 | /* Class = "NSMenu"; title = "Help"; ObjectID = "F2S-fz-NVQ"; */
84 | "F2S-fz-NVQ.title" = "Help";
85 |
86 | /* Class = "NSMenuItem"; title = "MinaOTP-MAC Help"; ObjectID = "FKE-Sm-Kum"; */
87 | "FKE-Sm-Kum.title" = "MinaOTP-MAC Help";
88 |
89 | /* Class = "NSMenuItem"; title = "Text"; ObjectID = "Fal-I4-PZk"; */
90 | "Fal-I4-PZk.title" = "Text";
91 |
92 | /* Class = "NSMenu"; title = "Substitutions"; ObjectID = "FeM-D8-WVr"; */
93 | "FeM-D8-WVr.title" = "Substitutions";
94 |
95 | /* Class = "NSMenuItem"; title = "Bold"; ObjectID = "GB9-OM-e27"; */
96 | "GB9-OM-e27.title" = "Bold";
97 |
98 | /* Class = "NSMenu"; title = "Format"; ObjectID = "GEO-Iw-cKr"; */
99 | "GEO-Iw-cKr.title" = "Format";
100 |
101 | /* Class = "NSMenuItem"; title = "Use Default"; ObjectID = "GUa-eO-cwY"; */
102 | "GUa-eO-cwY.title" = "Use Default";
103 |
104 | /* Class = "NSMenuItem"; title = "Font"; ObjectID = "Gi5-1S-RQB"; */
105 | "Gi5-1S-RQB.title" = "Font";
106 |
107 | /* Class = "NSMenuItem"; title = "Writing Direction"; ObjectID = "H1b-Si-o9J"; */
108 | "H1b-Si-o9J.title" = "Writing Direction";
109 |
110 | /* Class = "NSMenuItem"; title = "View"; ObjectID = "H8h-7b-M4v"; */
111 | "H8h-7b-M4v.title" = "View";
112 |
113 | /* Class = "NSMenuItem"; title = "Text Replacement"; ObjectID = "HFQ-gK-NFA"; */
114 | "HFQ-gK-NFA.title" = "Text Replacement";
115 |
116 | /* Class = "NSMenuItem"; title = "Show Spelling and Grammar"; ObjectID = "HFo-cy-zxI"; */
117 | "HFo-cy-zxI.title" = "Show Spelling and Grammar";
118 |
119 | /* Class = "NSMenu"; title = "View"; ObjectID = "HyV-fh-RgO"; */
120 | "HyV-fh-RgO.title" = "View";
121 |
122 | /* Class = "NSMenuItem"; title = "Subscript"; ObjectID = "I0S-gh-46l"; */
123 | "I0S-gh-46l.title" = "Subscript";
124 |
125 | /* Class = "NSMenuItem"; title = "Open…"; ObjectID = "IAo-SY-fd9"; */
126 | "IAo-SY-fd9.title" = "Open…";
127 |
128 | /* Class = "NSMenuItem"; title = "Justify"; ObjectID = "J5U-5w-g23"; */
129 | "J5U-5w-g23.title" = "Justify";
130 |
131 | /* Class = "NSMenuItem"; title = "Use None"; ObjectID = "J7y-lM-qPV"; */
132 | "J7y-lM-qPV.title" = "Use None";
133 |
134 | /* Class = "NSMenuItem"; title = "Revert to Saved"; ObjectID = "KaW-ft-85H"; */
135 | "KaW-ft-85H.title" = "Revert to Saved";
136 |
137 | /* Class = "NSMenuItem"; title = "Show All"; ObjectID = "Kd2-mp-pUS"; */
138 | "Kd2-mp-pUS.title" = "Show All";
139 |
140 | /* Class = "NSMenuItem"; title = "Bring All to Front"; ObjectID = "LE2-aR-0XJ"; */
141 | "LE2-aR-0XJ.title" = "Bring All to Front";
142 |
143 | /* Class = "NSMenuItem"; title = "Paste Ruler"; ObjectID = "LVM-kO-fVI"; */
144 | "LVM-kO-fVI.title" = "Paste Ruler";
145 |
146 | /* Class = "NSMenuItem"; title = "\tLeft to Right"; ObjectID = "Lbh-J2-qVU"; */
147 | "Lbh-J2-qVU.title" = "\tLeft to Right";
148 |
149 | /* Class = "NSMenuItem"; title = "Copy Ruler"; ObjectID = "MkV-Pr-PK5"; */
150 | "MkV-Pr-PK5.title" = "Copy Ruler";
151 |
152 | /* Class = "NSMenuItem"; title = "Services"; ObjectID = "NMo-om-nkz"; */
153 | "NMo-om-nkz.title" = "Services";
154 |
155 | /* Class = "NSMenuItem"; title = "\tDefault"; ObjectID = "Nop-cj-93Q"; */
156 | "Nop-cj-93Q.title" = "\tDefault";
157 |
158 | /* Class = "NSMenuItem"; title = "Minimize"; ObjectID = "OY7-WF-poV"; */
159 | "OY7-WF-poV.title" = "Minimize";
160 |
161 | /* Class = "NSMenuItem"; title = "Baseline"; ObjectID = "OaQ-X3-Vso"; */
162 | "OaQ-X3-Vso.title" = "Baseline";
163 |
164 | /* Class = "NSMenuItem"; title = "Hide MinaOTP-MAC"; ObjectID = "Olw-nP-bQN"; */
165 | "Olw-nP-bQN.title" = "Hide MinaOTP-MAC";
166 |
167 | /* Class = "NSMenuItem"; title = "Find Previous"; ObjectID = "OwM-mh-QMV"; */
168 | "OwM-mh-QMV.title" = "Find Previous";
169 |
170 | /* Class = "NSMenuItem"; title = "Stop Speaking"; ObjectID = "Oyz-dy-DGm"; */
171 | "Oyz-dy-DGm.title" = "Stop Speaking";
172 |
173 | /* Class = "NSMenuItem"; title = "Bigger"; ObjectID = "Ptp-SP-VEL"; */
174 | "Ptp-SP-VEL.title" = "Bigger";
175 |
176 | /* Class = "NSMenuItem"; title = "Show Fonts"; ObjectID = "Q5e-8K-NDq"; */
177 | "Q5e-8K-NDq.title" = "Show Fonts";
178 |
179 | /* Class = "NSMenuItem"; title = "Zoom"; ObjectID = "R4o-n2-Eq4"; */
180 | "R4o-n2-Eq4.title" = "Zoom";
181 |
182 | /* Class = "NSMenuItem"; title = "\tRight to Left"; ObjectID = "RB4-Sm-HuC"; */
183 | "RB4-Sm-HuC.title" = "\tRight to Left";
184 |
185 | /* Class = "NSMenuItem"; title = "Superscript"; ObjectID = "Rqc-34-cIF"; */
186 | "Rqc-34-cIF.title" = "Superscript";
187 |
188 | /* Class = "NSMenuItem"; title = "Select All"; ObjectID = "Ruw-6m-B2m"; */
189 | "Ruw-6m-B2m.title" = "Select All";
190 |
191 | /* Class = "NSMenuItem"; title = "Jump to Selection"; ObjectID = "S0p-oC-mLd"; */
192 | "S0p-oC-mLd.title" = "Jump to Selection";
193 |
194 | /* Class = "NSMenu"; title = "Window"; ObjectID = "Td7-aD-5lo"; */
195 | "Td7-aD-5lo.title" = "Window";
196 |
197 | /* Class = "NSMenuItem"; title = "Capitalize"; ObjectID = "UEZ-Bs-lqG"; */
198 | "UEZ-Bs-lqG.title" = "Capitalize";
199 |
200 | /* Class = "NSMenuItem"; title = "Center"; ObjectID = "VIY-Ag-zcb"; */
201 | "VIY-Ag-zcb.title" = "Center";
202 |
203 | /* Class = "NSMenuItem"; title = "Hide Others"; ObjectID = "Vdr-fp-XzO"; */
204 | "Vdr-fp-XzO.title" = "Hide Others";
205 |
206 | /* Class = "NSMenuItem"; title = "Italic"; ObjectID = "Vjx-xi-njq"; */
207 | "Vjx-xi-njq.title" = "Italic";
208 |
209 | /* Class = "NSMenu"; title = "Edit"; ObjectID = "W48-6f-4Dl"; */
210 | "W48-6f-4Dl.title" = "Edit";
211 |
212 | /* Class = "NSMenuItem"; title = "Underline"; ObjectID = "WRG-CD-K1S"; */
213 | "WRG-CD-K1S.title" = "Underline";
214 |
215 | /* Class = "NSMenuItem"; title = "New"; ObjectID = "Was-JA-tGl"; */
216 | "Was-JA-tGl.title" = "New";
217 |
218 | /* Class = "NSMenuItem"; title = "Paste and Match Style"; ObjectID = "WeT-3V-zwk"; */
219 | "WeT-3V-zwk.title" = "Paste and Match Style";
220 |
221 | /* Class = "NSMenuItem"; title = "Find…"; ObjectID = "Xz5-n4-O0W"; */
222 | "Xz5-n4-O0W.title" = "Find…";
223 |
224 | /* Class = "NSMenuItem"; title = "Find and Replace…"; ObjectID = "YEy-JH-Tfz"; */
225 | "YEy-JH-Tfz.title" = "Find and Replace…";
226 |
227 | /* Class = "NSMenuItem"; title = "\tDefault"; ObjectID = "YGs-j5-SAR"; */
228 | "YGs-j5-SAR.title" = "\tDefault";
229 |
230 | /* Class = "NSMenuItem"; title = "Start Speaking"; ObjectID = "Ynk-f8-cLZ"; */
231 | "Ynk-f8-cLZ.title" = "Start Speaking";
232 |
233 | /* Class = "NSMenuItem"; title = "Align Left"; ObjectID = "ZM1-6Q-yy1"; */
234 | "ZM1-6Q-yy1.title" = "Align Left";
235 |
236 | /* Class = "NSMenuItem"; title = "Paragraph"; ObjectID = "ZvO-Gk-QUH"; */
237 | "ZvO-Gk-QUH.title" = "Paragraph";
238 |
239 | /* Class = "NSMenuItem"; title = "Print…"; ObjectID = "aTl-1u-JFS"; */
240 | "aTl-1u-JFS.title" = "Print…";
241 |
242 | /* Class = "NSMenuItem"; title = "Window"; ObjectID = "aUF-d1-5bR"; */
243 | "aUF-d1-5bR.title" = "Window";
244 |
245 | /* Class = "NSMenu"; title = "Font"; ObjectID = "aXa-aM-Jaq"; */
246 | "aXa-aM-Jaq.title" = "Font";
247 |
248 | /* Class = "NSMenuItem"; title = "Use Default"; ObjectID = "agt-UL-0e3"; */
249 | "agt-UL-0e3.title" = "Use Default";
250 |
251 | /* Class = "NSMenuItem"; title = "Show Colors"; ObjectID = "bgn-CT-cEk"; */
252 | "bgn-CT-cEk.title" = "Show Colors";
253 |
254 | /* Class = "NSMenu"; title = "File"; ObjectID = "bib-Uj-vzu"; */
255 | "bib-Uj-vzu.title" = "File";
256 |
257 | /* Class = "NSMenuItem"; title = "Use Selection for Find"; ObjectID = "buJ-ug-pKt"; */
258 | "buJ-ug-pKt.title" = "Use Selection for Find";
259 |
260 | /* Class = "NSMenu"; title = "Transformations"; ObjectID = "c8a-y6-VQd"; */
261 | "c8a-y6-VQd.title" = "Transformations";
262 |
263 | /* Class = "NSMenuItem"; title = "Use None"; ObjectID = "cDB-IK-hbR"; */
264 | "cDB-IK-hbR.title" = "Use None";
265 |
266 | /* Class = "NSMenuItem"; title = "Selection"; ObjectID = "cqv-fj-IhA"; */
267 | "cqv-fj-IhA.title" = "Selection";
268 |
269 | /* Class = "NSMenuItem"; title = "Smart Links"; ObjectID = "cwL-P1-jid"; */
270 | "cwL-P1-jid.title" = "Smart Links";
271 |
272 | /* Class = "NSMenuItem"; title = "Make Lower Case"; ObjectID = "d9M-CD-aMd"; */
273 | "d9M-CD-aMd.title" = "Make Lower Case";
274 |
275 | /* Class = "NSMenu"; title = "Text"; ObjectID = "d9c-me-L2H"; */
276 | "d9c-me-L2H.title" = "Text";
277 |
278 | /* Class = "NSMenuItem"; title = "File"; ObjectID = "dMs-cI-mzQ"; */
279 | "dMs-cI-mzQ.title" = "File";
280 |
281 | /* Class = "NSMenuItem"; title = "Undo"; ObjectID = "dRJ-4n-Yzg"; */
282 | "dRJ-4n-Yzg.title" = "Undo";
283 |
284 | /* Class = "NSMenuItem"; title = "Paste"; ObjectID = "gVA-U4-sdL"; */
285 | "gVA-U4-sdL.title" = "Paste";
286 |
287 | /* Class = "NSMenuItem"; title = "Smart Quotes"; ObjectID = "hQb-2v-fYv"; */
288 | "hQb-2v-fYv.title" = "Smart Quotes";
289 |
290 | /* Class = "NSMenuItem"; title = "Check Document Now"; ObjectID = "hz2-CU-CR7"; */
291 | "hz2-CU-CR7.title" = "Check Document Now";
292 |
293 | /* Class = "NSMenu"; title = "Services"; ObjectID = "hz9-B4-Xy5"; */
294 | "hz9-B4-Xy5.title" = "Services";
295 |
296 | /* Class = "NSMenuItem"; title = "Smaller"; ObjectID = "i1d-Er-qST"; */
297 | "i1d-Er-qST.title" = "Smaller";
298 |
299 | /* Class = "NSMenu"; title = "Baseline"; ObjectID = "ijk-EB-dga"; */
300 | "ijk-EB-dga.title" = "Baseline";
301 |
302 | /* Class = "NSMenuItem"; title = "Kern"; ObjectID = "jBQ-r6-VK2"; */
303 | "jBQ-r6-VK2.title" = "Kern";
304 |
305 | /* Class = "NSMenuItem"; title = "\tRight to Left"; ObjectID = "jFq-tB-4Kx"; */
306 | "jFq-tB-4Kx.title" = "\tRight to Left";
307 |
308 | /* Class = "NSMenuItem"; title = "Format"; ObjectID = "jxT-CU-nIS"; */
309 | "jxT-CU-nIS.title" = "Format";
310 |
311 | /* Class = "NSMenuItem"; title = "Show Sidebar"; ObjectID = "kIP-vf-haE"; */
312 | "kIP-vf-haE.title" = "Show Sidebar";
313 |
314 | /* Class = "NSMenuItem"; title = "Check Grammar With Spelling"; ObjectID = "mK6-2p-4JG"; */
315 | "mK6-2p-4JG.title" = "Check Grammar With Spelling";
316 |
317 | /* Class = "NSMenuItem"; title = "Ligatures"; ObjectID = "o6e-r0-MWq"; */
318 | "o6e-r0-MWq.title" = "Ligatures";
319 |
320 | /* Class = "NSMenu"; title = "Open Recent"; ObjectID = "oas-Oc-fiZ"; */
321 | "oas-Oc-fiZ.title" = "Open Recent";
322 |
323 | /* Class = "NSMenuItem"; title = "Loosen"; ObjectID = "ogc-rX-tC1"; */
324 | "ogc-rX-tC1.title" = "Loosen";
325 |
326 | /* Class = "NSMenuItem"; title = "Delete"; ObjectID = "pa3-QI-u2k"; */
327 | "pa3-QI-u2k.title" = "Delete";
328 |
329 | /* Class = "NSMenuItem"; title = "Save…"; ObjectID = "pxx-59-PXV"; */
330 | "pxx-59-PXV.title" = "Save…";
331 |
332 | /* Class = "NSMenuItem"; title = "Find Next"; ObjectID = "q09-fT-Sye"; */
333 | "q09-fT-Sye.title" = "Find Next";
334 |
335 | /* Class = "NSMenuItem"; title = "Page Setup…"; ObjectID = "qIS-W8-SiK"; */
336 | "qIS-W8-SiK.title" = "Page Setup…";
337 |
338 | /* Class = "NSMenuItem"; title = "Check Spelling While Typing"; ObjectID = "rbD-Rh-wIN"; */
339 | "rbD-Rh-wIN.title" = "Check Spelling While Typing";
340 |
341 | /* Class = "NSMenuItem"; title = "Smart Dashes"; ObjectID = "rgM-f4-ycn"; */
342 | "rgM-f4-ycn.title" = "Smart Dashes";
343 |
344 | /* Class = "NSMenuItem"; title = "Show Toolbar"; ObjectID = "snW-S8-Cw5"; */
345 | "snW-S8-Cw5.title" = "Show Toolbar";
346 |
347 | /* Class = "NSMenuItem"; title = "Data Detectors"; ObjectID = "tRr-pd-1PS"; */
348 | "tRr-pd-1PS.title" = "Data Detectors";
349 |
350 | /* Class = "NSMenuItem"; title = "Open Recent"; ObjectID = "tXI-mr-wws"; */
351 | "tXI-mr-wws.title" = "Open Recent";
352 |
353 | /* Class = "NSMenu"; title = "Kern"; ObjectID = "tlD-Oa-oAM"; */
354 | "tlD-Oa-oAM.title" = "Kern";
355 |
356 | /* Class = "NSMenu"; title = "MinaOTP-MAC"; ObjectID = "uQy-DD-JDr"; */
357 | "uQy-DD-JDr.title" = "MinaOTP-MAC";
358 |
359 | /* Class = "NSMenuItem"; title = "Cut"; ObjectID = "uRl-iY-unG"; */
360 | "uRl-iY-unG.title" = "Cut";
361 |
362 | /* Class = "NSMenuItem"; title = "Paste Style"; ObjectID = "vKC-jM-MkH"; */
363 | "vKC-jM-MkH.title" = "Paste Style";
364 |
365 | /* Class = "NSMenuItem"; title = "Show Ruler"; ObjectID = "vLm-3I-IUL"; */
366 | "vLm-3I-IUL.title" = "Show Ruler";
367 |
368 | /* Class = "NSMenuItem"; title = "Clear Menu"; ObjectID = "vNY-rz-j42"; */
369 | "vNY-rz-j42.title" = "Clear Menu";
370 |
371 | /* Class = "NSMenuItem"; title = "Make Upper Case"; ObjectID = "vmV-6d-7jI"; */
372 | "vmV-6d-7jI.title" = "Make Upper Case";
373 |
374 | /* Class = "NSMenu"; title = "Ligatures"; ObjectID = "w0m-vy-SC9"; */
375 | "w0m-vy-SC9.title" = "Ligatures";
376 |
377 | /* Class = "NSMenuItem"; title = "Align Right"; ObjectID = "wb2-vD-lq4"; */
378 | "wb2-vD-lq4.title" = "Align Right";
379 |
380 | /* Class = "NSMenuItem"; title = "Help"; ObjectID = "wpr-3q-Mcd"; */
381 | "wpr-3q-Mcd.title" = "Help";
382 |
383 | /* Class = "NSMenuItem"; title = "Copy"; ObjectID = "x3v-GG-iWU"; */
384 | "x3v-GG-iWU.title" = "Copy";
385 |
386 | /* Class = "NSMenuItem"; title = "Use All"; ObjectID = "xQD-1f-W4t"; */
387 | "xQD-1f-W4t.title" = "Use All";
388 |
389 | /* Class = "NSMenuItem"; title = "Speech"; ObjectID = "xrE-MZ-jX0"; */
390 | "xrE-MZ-jX0.title" = "Speech";
391 |
392 | /* Class = "NSMenuItem"; title = "Show Substitutions"; ObjectID = "z6F-FW-3nz"; */
393 | "z6F-FW-3nz.title" = "Show Substitutions";
394 |
--------------------------------------------------------------------------------
/MinaOTP-MAC/PopoverViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // PopoverViewController.swift
3 | // MinaOTP-MAC
4 | //
5 | // Created by 武建明 on 2018/8/1.
6 | // Copyright © 2018年 Four_w. All rights reserved.
7 | //
8 |
9 | import Cocoa
10 |
11 | class PopoverViewController: NSViewController, NSTableViewDelegate, NSTableViewDataSource, NSMenuDelegate, NSPopoverDelegate{
12 |
13 |
14 | let importButton = CustomFlatButton().customFlatButton(frame: CGRect(x: 12, y: 365, width: 48, height: 24), title: NSLocalizedString("import", comment: ""))
15 | let exportButton = CustomFlatButton().customFlatButton(frame: CGRect(x: 72, y: 365, width: 48, height: 24), title: NSLocalizedString("export", comment: ""))
16 | let addButton = CustomFlatButton().customFlatButton(frame: CGRect(x: 240, y: 365, width: 48, height: 24), title: NSLocalizedString("add", comment: "add"))
17 | var totpArray = [String]()
18 | var oldTimeStamp = 0
19 | let kCellHeight = CGFloat(60)
20 | var clickedRow: Int = -1
21 |
22 | override func loadView() {
23 | self.view = NSView(frame: CGRect(x: 0, y: 0, width: 300, height: 400))
24 | self.view.wantsLayer = true
25 | self.view.layer?.backgroundColor = NSColor.clear.cgColor
26 | }
27 |
28 | override func viewDidLoad() {
29 | super.viewDidLoad()
30 | self.config()
31 | DataManager.initial { [weak self] in
32 | self?.reloadData()
33 | }
34 |
35 | NotificationCenter.default.addObserver(self, selector: #selector(notificationAction), name: NSNotification.Name(rawValue:"reloadData"), object: nil)
36 | NotificationCenter.default.addObserver(self, selector: #selector(notificationAction), name: NSNotification.Name(rawValue:"addData"), object: nil)
37 | // NotificationCenter.default.addObserver(self, selector: #selector(didScroll), name: NSView.boundsDidChangeNotification, object: totpTableView.enclosingScrollView?.contentView)
38 | }
39 | override func awakeFromNib() {
40 | }
41 | //MARK: - Lazy
42 | struct OtpModel:Codable {
43 | var secret:String
44 | var issuer:String
45 | var remark:String
46 | }
47 | lazy var editPopoverView : NSPopover = {
48 | let pop = NSPopover()
49 | pop.behavior = .transient
50 | //pop.appearance = NSAppearance.init(named: .vibrantLight)
51 | pop.delegate = self
52 | return pop
53 | }()
54 | lazy var totpTableView: NSTableView = {
55 | let tab = NSTableView.init(frame: CGRect(x: 0, y: 0, width: 0, height: 0))
56 | tab.delegate = self;
57 | tab.dataSource = self;
58 | tab.enclosingScrollView?.drawsBackground = false
59 | tab.backgroundColor = .clear
60 | tab.headerView = NSTableHeaderView.init(frame: CGRect(x: 0, y: 0, width: 0, height: 0))
61 | let column1 = NSTableColumn(identifier: NSUserInterfaceItemIdentifier(rawValue: "column1"))
62 | column1.width = 300
63 | tab.addTableColumn(column1)
64 | tab.target = self
65 | tab.doubleAction = #selector(self.tableViewDoubleClick)
66 | let menu = NSMenu()
67 | menu.delegate = self
68 | tab.menu = menu
69 | tab.draggingDestinationFeedbackStyle = .gap
70 | return tab
71 | }()
72 | lazy var bgScrollView: NSScrollView = {
73 | let scrollView = NSScrollView.init(frame: CGRect(x: 0, y: 0, width: 300, height: 350))
74 | scrollView.contentView.backgroundColor = .clear
75 | scrollView.documentView = totpTableView
76 | totpTableView.enclosingScrollView?.drawsBackground = false
77 | return scrollView
78 | }()
79 | lazy var progressView:ProgressView = {
80 | let view = ProgressView.init(frame: CGRect(x: 0, y: 350, width: 300, height: 2))
81 | view.layer?.backgroundColor = NSColor.clear.cgColor
82 | return view
83 | }()
84 | lazy var timer:Timer! = {
85 | let t = Timer.scheduledTimer(timeInterval: 0.1, target: self, selector: #selector(timerAction), userInfo: nil, repeats: true)
86 | RunLoop.main.add(t, forMode: RunLoop.Mode.common)
87 | return t
88 | }()
89 | private func config() {
90 | self.view.addSubview(importButton)
91 | self.view.addSubview(exportButton)
92 | self.view.addSubview(addButton)
93 | self.view.addSubview(self.bgScrollView)
94 | self.view.addSubview(self.progressView)
95 | importButton.target = self
96 | exportButton.target = self
97 | addButton.target = self
98 | importButton.action = #selector(self.importButtonAction)
99 | exportButton.action = #selector(self.exportButtonAction)
100 | addButton.action = #selector(self.addButtonAction(button:))
101 |
102 |
103 | totpTableView.registerForDraggedTypes([NSPasteboard.PasteboardType.string])
104 | }
105 | // MARK: - Action
106 | func menuNeedsUpdate(_ menu: NSMenu) {
107 | menu.removeAllItems()
108 | clickedRow = totpTableView.clickedRow
109 | menu.addItem(withTitle: NSLocalizedString("delete", comment: ""), action: #selector(deleteRowAction), keyEquivalent: "")
110 | menu.addItem(withTitle: NSLocalizedString("edit", comment: ""), action: #selector(editRowAction), keyEquivalent: "")
111 | }
112 | func menuWillOpen(_ menu: NSMenu) {
113 | print("menuWillOpen")
114 | stopTimer()
115 | }
116 | func menuDidClose(_ menu: NSMenu) {
117 | print("menuDidClose")
118 | startTimer()
119 | }
120 | func reloadData() {
121 | totpArray.removeAll()
122 | totpArray = DataManager.get()
123 | totpTableView.reloadData()
124 | }
125 | func startTimer() {
126 | print("计时器开始")
127 | self.reloadData()
128 | if totpArray.count > 0 {
129 | totpTableView.reloadData()
130 | oldTimeStamp = Int(Date().timeIntervalSince1970)/30
131 | self.timer.fireDate = Date.distantPast
132 | }else{
133 | self.progressView.setProgress(value: 0.00)
134 | }
135 | }
136 | func stopTimer() {
137 | print("计时器停止")
138 | self.timer.fireDate = Date.distantFuture
139 | }
140 | @objc func notificationAction(nofi : Notification){
141 | let type = nofi.userInfo!["type"]
142 | if (type as! String) == "edit_save" {
143 | ShowTips().showTip(message: "修改成功", view: self.view)
144 | editPopoverView.close()
145 | }else if (type as! String) == "reload" {
146 | startTimer()
147 | }else if (type as! String) == "close" {
148 | stopTimer()
149 | }else if (type as! String) == "edit_cancel" {
150 | editPopoverView.close()
151 | }else if (type as! String) == "add_success_from_textfield" {
152 | self.totpTableView.scrollRowToVisible(totpArray.count-1)
153 | }
154 | }
155 | func popoverWillClose(_ notification: Notification) {
156 | print("popoverWillClose")
157 | startTimer()
158 | }
159 | func popoverWillShow(_ notification: Notification) {
160 | print("popoverWillShow")
161 | stopTimer()
162 | }
163 | @objc func timerAction() {
164 | let nowTimeStamp = Int(Date().timeIntervalSince1970)/30
165 | let progress = Date().timeIntervalSince1970/30-Double(nowTimeStamp)
166 | self.progressView.setProgress(value: CGFloat(progress))
167 | if nowTimeStamp != self.oldTimeStamp{
168 | totpTableView.reloadData()
169 | oldTimeStamp = nowTimeStamp
170 | }
171 | }
172 | @objc func deleteRowAction() {
173 | print("deleteRowAction")
174 | print(clickedRow)
175 | totpArray.remove(at: clickedRow)
176 | totpTableView.removeRows(at: [clickedRow], withAnimation: .slideRight)
177 | DataManager.save(self.totpArray)
178 | }
179 | @objc func editRowAction() {
180 | stopTimer()
181 | let rect = totpTableView.rect(ofRow: clickedRow)
182 | let super_rect = totpTableView.convert(rect, to: self.view)
183 | let editVC = EditPopoverViewController()
184 | editVC.editRow = clickedRow
185 | self.editPopoverView.contentViewController = editVC
186 | self.editPopoverView.show(relativeTo: super_rect, of: self.view, preferredEdge: NSRectEdge.maxX)
187 |
188 | }
189 | @objc func tableViewDoubleClick() {
190 | print(totpTableView.selectedRow)
191 | let view = totpTableView.view(atColumn: 0, row: totpTableView.selectedRow, makeIfNecessary: false)
192 | let pasteboard = NSPasteboard.general
193 | pasteboard.declareTypes([.string], owner: nil)
194 | let res = pasteboard.setString((view as! CellView).codeTextField.stringValue, forType: .string)
195 | if res {
196 | ShowTips().showTip(message: NSLocalizedString("copy_success", comment: ""), view: self.view)
197 | }
198 | }
199 | @objc func importButtonAction() {
200 | let openPanel = NSOpenPanel.init()
201 | openPanel.prompt = NSLocalizedString("choose_title", comment: "")
202 | openPanel.allowedFileTypes = ["json"]
203 | openPanel.allowsMultipleSelection = false
204 | openPanel.canChooseDirectories = false
205 | openPanel.canCreateDirectories = false
206 | openPanel.canChooseFiles = true
207 | let code = openPanel.runModal()
208 | if code.rawValue == 1{
209 | let jsonData = NSData.init(contentsOf: openPanel.url!)
210 | let decoder = JSONDecoder()
211 | do {
212 | let otpModelArray = try decoder.decode([OtpModel].self, from: jsonData! as Data)
213 |
214 | // 将数据保存到UserDefaults
215 | var allItems = DataManager.get()
216 |
217 | for item in otpModelArray{
218 | let otp = Tools().totpStringFormat(remark: item.remark, issuer: item.issuer, secret: item.secret)
219 | allItems.append(otp)
220 | }
221 | DataManager.save(allItems)
222 | Tools().showAlert(message: NSLocalizedString("add_success_tip", comment: ""))
223 |
224 | } catch let error{
225 | print(error)
226 | Tools().showAlert(message: NSLocalizedString("parse_failed", comment: ""))
227 | }
228 | }
229 | }
230 |
231 | @objc func exportButtonAction() {
232 | let savePanel = NSSavePanel()
233 | savePanel.title = NSLocalizedString("export_title", comment: "")
234 | savePanel.nameFieldStringValue = "minaOTP.json"
235 | savePanel.canCreateDirectories = true
236 | savePanel.isExtensionHidden = false
237 | let i = savePanel.runModal()
238 | if i == NSApplication.ModalResponse.OK {
239 |
240 | // 将数据保存到UserDefaults
241 | let allItems = DataManager.get()
242 | var temArray = [Any]()
243 | for item in allItems{
244 | let otpDic = Tools().totpDictionaryFormat(code: item)
245 | temArray.append(otpDic)
246 | }
247 | let temJsonData = try! JSONSerialization.data(withJSONObject: temArray, options: .prettyPrinted)
248 | let temJsonStr = String.init(data: temJsonData, encoding: .utf8)
249 |
250 | do {
251 | try temJsonStr?.write(to: savePanel.url!, atomically: true, encoding: .utf8)
252 | Tools().showAlert(message: NSLocalizedString("export_sucess", comment: ""))
253 | } catch {
254 | Tools().showAlert(message: NSLocalizedString("export_failure", comment: ""))
255 | }
256 | }
257 | }
258 | @objc func addButtonAction(button: NSButton) {
259 | print("addButtonAction")
260 | let temWindow = AddWindow.init(contentRect: NSRect(x: 0, y: 0, width: 480, height: 320), styleMask: .closable, backing: .buffered, defer: true)
261 | let windowVC = NSWindowController.init(window: temWindow)
262 | windowVC.window?.makeKeyAndOrderFront(nil)
263 | NSApp.activate(ignoringOtherApps: true)
264 | }
265 |
266 | // MARK: - TableViewDelegate,TableViewDataSource
267 | func numberOfRows(in tableView: NSTableView) -> Int {
268 | return totpArray.count
269 | }
270 | func tableView(_ tableView: NSTableView, heightOfRow row: Int) -> CGFloat {
271 | return kCellHeight
272 | }
273 | func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? {
274 |
275 | var view = tableView.makeView(withIdentifier: NSUserInterfaceItemIdentifier(rawValue: "cellIdentifier"), owner: self)
276 | if (view==nil) {
277 | view = CellView.init(frame: CGRect(x: 0, y: 0, width: 400, height: kCellHeight))
278 | (view as! CellView).identifier = NSUserInterfaceItemIdentifier(rawValue: "cellIdentifier");
279 | }
280 | let totpDic = Tools().totpDictionaryFormat(code: totpArray[row])
281 | (view as! CellView).remarkTextField.stringValue = (totpDic["remark"] as? String)!
282 | (view as! CellView).issuerTextField.stringValue = (totpDic["issuer"] as? String)!
283 | (view as! CellView).codeTextField.stringValue = GeneratorTotp.generateOTP(forSecret: totpDic["secret"] as? String)
284 | // (view as! CellView).hotKeyTextField.stringValue = "⌘\(row)"
285 |
286 | return view;
287 | }
288 |
289 | func tableView(_ tableView: NSTableView, rowViewForRow row: Int) -> NSTableRowView? {
290 | let tableRowView = CSTableRowView()
291 | tableRowView.isGroupRowStyle = false
292 | return tableRowView
293 | }
294 |
295 | func tableView(_ tableView: NSTableView, shouldEdit tableColumn: NSTableColumn?, row: Int) -> Bool {
296 | return true
297 | }
298 | //
299 | // func tableView(_ tableView: NSTableView, didDrag tableColumn: NSTableColumn) {
300 | // print("拖动")
301 | // }
302 |
303 | func tableView(_ tableView: NSTableView, writeRowsWith rowIndexes: IndexSet, to pboard: NSPasteboard) -> Bool {
304 | let data = NSKeyedArchiver.archivedData(withRootObject: rowIndexes)
305 | pboard.declareTypes([NSPasteboard.PasteboardType.string], owner: self)
306 | pboard.setData(data, forType: NSPasteboard.PasteboardType.string)
307 | return true
308 | }
309 | func tableView(tableView: NSTableView, writeRowsWithIndexes rowIndexes: NSIndexSet, toPasteboard pboard: NSPasteboard) -> Bool {
310 | pboard.declareTypes([NSPasteboard.PasteboardType.string], owner: self)
311 | pboard.setString("currencyCode", forType: NSPasteboard.PasteboardType.string)
312 | return true
313 | }
314 |
315 | func tableView(_ tableView: NSTableView, didDrag tableColumn: NSTableColumn) {
316 | print("asasasdad")
317 | }
318 | func tableView(_ tableView: NSTableView, draggingSession session: NSDraggingSession, willBeginAt screenPoint: NSPoint, forRowIndexes rowIndexes: IndexSet) {
319 | stopTimer()
320 | print("开始拖动")
321 | }
322 | func tableView(_ tableView: NSTableView, updateDraggingItemsForDrag draggingInfo: NSDraggingInfo) {
323 | print("拖动结束")
324 | }
325 | func tableView(_ tableView: NSTableView, validateDrop info: NSDraggingInfo, proposedRow row: Int, proposedDropOperation dropOperation: NSTableView.DropOperation) -> NSDragOperation {
326 |
327 | if dropOperation == .above {
328 | print("插入")
329 | return .move
330 | }
331 | return []
332 | }
333 | func tableView(_ tableView: NSTableView, acceptDrop info: NSDraggingInfo, row: Int, dropOperation: NSTableView.DropOperation) -> Bool {
334 |
335 | if info.draggingSource as! NSTableView == totpTableView{
336 | let data = info.draggingPasteboard.data(forType: NSPasteboard.PasteboardType.string)
337 | let rowIndexes:NSIndexSet = NSKeyedUnarchiver.unarchiveObject(with: data!) as! NSIndexSet
338 |
339 | if rowIndexes.firstIndex == row{
340 | totpTableView.reloadData()
341 | startTimer()
342 | return true
343 | }
344 | let value:String = totpArray[rowIndexes.firstIndex]
345 | totpArray.remove(at: rowIndexes.firstIndex)
346 | if rowIndexes.firstIndex < row{
347 | totpArray.insert(value, at: row-1)
348 | }else{
349 | totpArray.insert(value, at: row)
350 | }
351 | DataManager.save(totpArray)
352 | totpTableView.reloadData()
353 | startTimer()
354 | return true
355 | }
356 | return false
357 |
358 | }
359 | }
360 |
--------------------------------------------------------------------------------