diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c15ea64 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +.vscode +build +Accento.zip +diacritics-macos.xcodeproj/project.xcworkspace/xcuserdata/ +diacritics-macos.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..c578986 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2021 Matyáš Kříž + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..81bff76 --- /dev/null +++ b/README.md @@ -0,0 +1,22 @@ +
+ +
+ +# Acčento +##### [Ak·čento] + +--- + +Acčento is an easy-to-use tool for adding Czech diacritics to copied text. The app lives in the menu bar and is activated using a global hot key. + +The app also contains preferences allowing you to customize the hot key as well as other quality of life features. + +Props to [](https://www.nechybujte.cz/nastroje) for providing the incredible diacritics service. + +## Installation +Visit [Releases](https://github.com/MatyasKriz/Accento/releases) and download a `.zip` file of the latest version, open it, move the app into the `Applications` folder and launch it. + +### Contributing +If you'd like to see a feature implemented, feel free to open a pull request or just a proposal as an issue. + +This project isn't following any best practices; that said, please try to keep the code clean and simple. Same with commit messages. Thanks! diff --git a/diacritics-macos.xcodeproj/project.pbxproj b/diacritics-macos.xcodeproj/project.pbxproj new file mode 100644 index 0000000..4c101db --- /dev/null +++ b/diacritics-macos.xcodeproj/project.pbxproj @@ -0,0 +1,485 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 52; + objects = { + +/* Begin PBXBuildFile section */ + 68B1C7F926DD3A4400A7E552 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 68B1C7F826DD3A4400A7E552 /* AppDelegate.swift */; }; + 68B1C7FB26DD3A4400A7E552 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 68B1C7FA26DD3A4400A7E552 /* ContentView.swift */; }; + 68B1C7FD26DD3A4600A7E552 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 68B1C7FC26DD3A4600A7E552 /* Assets.xcassets */; }; + 68B1C80026DD3A4600A7E552 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 68B1C7FF26DD3A4600A7E552 /* Preview Assets.xcassets */; }; + 68B1C80326DD3A4600A7E552 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 68B1C80126DD3A4600A7E552 /* Main.storyboard */; }; + 68B1C80E26DDF00500A7E552 /* GlobalHotKeyService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 68B1C80D26DDF00500A7E552 /* GlobalHotKeyService.swift */; }; + 68B1C81026DDF00F00A7E552 /* DiacriticsService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 68B1C80F26DDF00F00A7E552 /* DiacriticsService.swift */; }; + 68B1C81226DDF07500A7E552 /* String+NSRange.swift in Sources */ = {isa = PBXBuildFile; fileRef = 68B1C81126DDF07500A7E552 /* String+NSRange.swift */; }; + 68B1C81926DE06C200A7E552 /* StorageService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 68B1C81826DE06C200A7E552 /* StorageService.swift */; }; + 68B1C81B26DE06D600A7E552 /* HotKey.swift in Sources */ = {isa = PBXBuildFile; fileRef = 68B1C81A26DE06D600A7E552 /* HotKey.swift */; }; + 68C063D226E0A8BD00151BCA /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 68C063D426E0A8BD00151BCA /* Localizable.strings */; }; + 68C063D626E0A9F100151BCA /* RoundedCorners.swift in Sources */ = {isa = PBXBuildFile; fileRef = 68C063D526E0A9F100151BCA /* RoundedCorners.swift */; }; + 68C063D826E0AEC800151BCA /* Shape+extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 68C063D726E0AEC800151BCA /* Shape+extensions.swift */; }; + 68C063DA26E1291B00151BCA /* Color+assets.swift in Sources */ = {isa = PBXBuildFile; fileRef = 68C063D926E1291B00151BCA /* Color+assets.swift */; }; + 68C063DC26E129D500151BCA /* String+localized.swift in Sources */ = {isa = PBXBuildFile; fileRef = 68C063DB26E129D500151BCA /* String+localized.swift */; }; + 68C063DF26E1F4A500151BCA /* HotKeyBinderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 68C063DE26E1F4A500151BCA /* HotKeyBinderView.swift */; }; + 68C063E226E73F6400151BCA /* LaunchAtLogin in Frameworks */ = {isa = PBXBuildFile; productRef = 68C063E126E73F6400151BCA /* LaunchAtLogin */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + 68B1C7F526DD3A4300A7E552 /* diacritics-macos.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "diacritics-macos.app"; sourceTree = BUILT_PRODUCTS_DIR; }; + 68B1C7F826DD3A4400A7E552 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 68B1C7FA26DD3A4400A7E552 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; + 68B1C7FC26DD3A4600A7E552 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 68B1C7FF26DD3A4600A7E552 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; + 68B1C80226DD3A4600A7E552 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; + 68B1C80426DD3A4600A7E552 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 68B1C80526DD3A4600A7E552 /* diacritics_macos.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = diacritics_macos.entitlements; sourceTree = ""; }; + 68B1C80D26DDF00500A7E552 /* GlobalHotKeyService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GlobalHotKeyService.swift; sourceTree = ""; }; + 68B1C80F26DDF00F00A7E552 /* DiacriticsService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DiacriticsService.swift; sourceTree = ""; }; + 68B1C81126DDF07500A7E552 /* String+NSRange.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+NSRange.swift"; sourceTree = ""; }; + 68B1C81826DE06C200A7E552 /* StorageService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StorageService.swift; sourceTree = ""; }; + 68B1C81A26DE06D600A7E552 /* HotKey.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HotKey.swift; sourceTree = ""; }; + 68C063D326E0A8BD00151BCA /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = ""; }; + 68C063D526E0A9F100151BCA /* RoundedCorners.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoundedCorners.swift; sourceTree = ""; }; + 68C063D726E0AEC800151BCA /* Shape+extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Shape+extensions.swift"; sourceTree = ""; }; + 68C063D926E1291B00151BCA /* Color+assets.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Color+assets.swift"; sourceTree = ""; }; + 68C063DB26E129D500151BCA /* String+localized.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+localized.swift"; sourceTree = ""; }; + 68C063DD26E12D4800151BCA /* cs */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = cs; path = cs.lproj/Localizable.strings; sourceTree = ""; }; + 68C063DE26E1F4A500151BCA /* HotKeyBinderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HotKeyBinderView.swift; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 68B1C7F226DD3A4300A7E552 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 68C063E226E73F6400151BCA /* LaunchAtLogin in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 68B1C7EC26DD3A4300A7E552 = { + isa = PBXGroup; + children = ( + 68B1C7F726DD3A4300A7E552 /* diacritics-macos */, + 68B1C7F626DD3A4300A7E552 /* Products */, + ); + sourceTree = ""; + }; + 68B1C7F626DD3A4300A7E552 /* Products */ = { + isa = PBXGroup; + children = ( + 68B1C7F526DD3A4300A7E552 /* diacritics-macos.app */, + ); + name = Products; + sourceTree = ""; + }; + 68B1C7F726DD3A4300A7E552 /* diacritics-macos */ = { + isa = PBXGroup; + children = ( + 68B1C7F826DD3A4400A7E552 /* AppDelegate.swift */, + 68B1C7FA26DD3A4400A7E552 /* ContentView.swift */, + 68C063DE26E1F4A500151BCA /* HotKeyBinderView.swift */, + 68B1C80C26DDEFFA00A7E552 /* Services */, + 68C063CE26E0A8A900151BCA /* Resources */, + 68B1C80B26DDEFF800A7E552 /* Utils */, + 68B1C7FC26DD3A4600A7E552 /* Assets.xcassets */, + 68B1C80126DD3A4600A7E552 /* Main.storyboard */, + 68B1C80426DD3A4600A7E552 /* Info.plist */, + 68B1C80526DD3A4600A7E552 /* diacritics_macos.entitlements */, + 68B1C7FE26DD3A4600A7E552 /* Preview Content */, + ); + path = "diacritics-macos"; + sourceTree = ""; + }; + 68B1C7FE26DD3A4600A7E552 /* Preview Content */ = { + isa = PBXGroup; + children = ( + 68B1C7FF26DD3A4600A7E552 /* Preview Assets.xcassets */, + ); + path = "Preview Content"; + sourceTree = ""; + }; + 68B1C80B26DDEFF800A7E552 /* Utils */ = { + isa = PBXGroup; + children = ( + 68B1C81126DDF07500A7E552 /* String+NSRange.swift */, + 68B1C81A26DE06D600A7E552 /* HotKey.swift */, + 68C063D526E0A9F100151BCA /* RoundedCorners.swift */, + 68C063D726E0AEC800151BCA /* Shape+extensions.swift */, + 68C063D926E1291B00151BCA /* Color+assets.swift */, + 68C063DB26E129D500151BCA /* String+localized.swift */, + ); + path = Utils; + sourceTree = ""; + }; + 68B1C80C26DDEFFA00A7E552 /* Services */ = { + isa = PBXGroup; + children = ( + 68B1C80D26DDF00500A7E552 /* GlobalHotKeyService.swift */, + 68B1C80F26DDF00F00A7E552 /* DiacriticsService.swift */, + 68B1C81826DE06C200A7E552 /* StorageService.swift */, + ); + path = Services; + sourceTree = ""; + }; + 68C063CE26E0A8A900151BCA /* Resources */ = { + isa = PBXGroup; + children = ( + 68C063D426E0A8BD00151BCA /* Localizable.strings */, + ); + path = Resources; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 68B1C7F426DD3A4300A7E552 /* diacritics-macos */ = { + isa = PBXNativeTarget; + buildConfigurationList = 68B1C80826DD3A4600A7E552 /* Build configuration list for PBXNativeTarget "diacritics-macos" */; + buildPhases = ( + 68B1C7F126DD3A4300A7E552 /* Sources */, + 68B1C7F226DD3A4300A7E552 /* Frameworks */, + 68B1C7F326DD3A4300A7E552 /* Resources */, + 68AD2E9726E8C2C700EF31FC /* Copy "Launch at Login Helper" */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = "diacritics-macos"; + packageProductDependencies = ( + 68C063E126E73F6400151BCA /* LaunchAtLogin */, + ); + productName = "diacritics-macos"; + productReference = 68B1C7F526DD3A4300A7E552 /* diacritics-macos.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 68B1C7ED26DD3A4300A7E552 /* Project object */ = { + isa = PBXProject; + attributes = { + LastSwiftUpdateCheck = 1250; + LastUpgradeCheck = 1250; + TargetAttributes = { + 68B1C7F426DD3A4300A7E552 = { + CreatedOnToolsVersion = 12.5.1; + }; + }; + }; + buildConfigurationList = 68B1C7F026DD3A4300A7E552 /* Build configuration list for PBXProject "diacritics-macos" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + cs, + ); + mainGroup = 68B1C7EC26DD3A4300A7E552; + packageReferences = ( + 68C063E026E73F6400151BCA /* XCRemoteSwiftPackageReference "LaunchAtLogin" */, + ); + productRefGroup = 68B1C7F626DD3A4300A7E552 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 68B1C7F426DD3A4300A7E552 /* diacritics-macos */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 68B1C7F326DD3A4300A7E552 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 68B1C80326DD3A4600A7E552 /* Main.storyboard in Resources */, + 68C063D226E0A8BD00151BCA /* Localizable.strings in Resources */, + 68B1C80026DD3A4600A7E552 /* Preview Assets.xcassets in Resources */, + 68B1C7FD26DD3A4600A7E552 /* Assets.xcassets in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 68AD2E9726E8C2C700EF31FC /* Copy "Launch at Login Helper" */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + name = "Copy \"Launch at Login Helper\""; + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${BUILT_PRODUCTS_DIR}/LaunchAtLogin_LaunchAtLogin.bundle/Contents/Resources/copy-helper-swiftpm.sh\"\n"; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 68B1C7F126DD3A4300A7E552 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 68B1C81226DDF07500A7E552 /* String+NSRange.swift in Sources */, + 68B1C7FB26DD3A4400A7E552 /* ContentView.swift in Sources */, + 68C063DC26E129D500151BCA /* String+localized.swift in Sources */, + 68C063DA26E1291B00151BCA /* Color+assets.swift in Sources */, + 68C063DF26E1F4A500151BCA /* HotKeyBinderView.swift in Sources */, + 68B1C81B26DE06D600A7E552 /* HotKey.swift in Sources */, + 68C063D826E0AEC800151BCA /* Shape+extensions.swift in Sources */, + 68B1C80E26DDF00500A7E552 /* GlobalHotKeyService.swift in Sources */, + 68C063D626E0A9F100151BCA /* RoundedCorners.swift in Sources */, + 68B1C81026DDF00F00A7E552 /* DiacriticsService.swift in Sources */, + 68B1C7F926DD3A4400A7E552 /* AppDelegate.swift in Sources */, + 68B1C81926DE06C200A7E552 /* StorageService.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXVariantGroup section */ + 68B1C80126DD3A4600A7E552 /* Main.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 68B1C80226DD3A4600A7E552 /* Base */, + ); + name = Main.storyboard; + sourceTree = ""; + }; + 68C063D426E0A8BD00151BCA /* Localizable.strings */ = { + isa = PBXVariantGroup; + children = ( + 68C063D326E0A8BD00151BCA /* en */, + 68C063DD26E12D4800151BCA /* cs */, + ); + name = Localizable.strings; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 68B1C80626DD3A4600A7E552 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 11.3; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = macosx; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + 68B1C80726DD3A4600A7E552 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 11.3; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = macosx; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + }; + name = Release; + }; + 68B1C80926DD3A4600A7E552 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_ENTITLEMENTS = "diacritics-macos/diacritics_macos.entitlements"; + CODE_SIGN_IDENTITY = "-"; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + DEVELOPMENT_ASSET_PATHS = "\"diacritics-macos/Preview Content\""; + DEVELOPMENT_TEAM = Y3NT3WUF44; + ENABLE_HARDENED_RUNTIME = YES; + ENABLE_PREVIEWS = YES; + INFOPLIST_FILE = "diacritics-macos/Info.plist"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + MACOSX_DEPLOYMENT_TARGET = 11.0; + PRODUCT_BUNDLE_IDENTIFIER = "cz.tyas.diacritics-macos"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + }; + name = Debug; + }; + 68B1C80A26DD3A4600A7E552 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_ENTITLEMENTS = "diacritics-macos/diacritics_macos.entitlements"; + CODE_SIGN_IDENTITY = "-"; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + DEVELOPMENT_ASSET_PATHS = "\"diacritics-macos/Preview Content\""; + DEVELOPMENT_TEAM = Y3NT3WUF44; + ENABLE_HARDENED_RUNTIME = YES; + ENABLE_PREVIEWS = YES; + INFOPLIST_FILE = "diacritics-macos/Info.plist"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + MACOSX_DEPLOYMENT_TARGET = 11.0; + PRODUCT_BUNDLE_IDENTIFIER = "cz.tyas.diacritics-macos"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 68B1C7F026DD3A4300A7E552 /* Build configuration list for PBXProject "diacritics-macos" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 68B1C80626DD3A4600A7E552 /* Debug */, + 68B1C80726DD3A4600A7E552 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 68B1C80826DD3A4600A7E552 /* Build configuration list for PBXNativeTarget "diacritics-macos" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 68B1C80926DD3A4600A7E552 /* Debug */, + 68B1C80A26DD3A4600A7E552 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + +/* Begin XCRemoteSwiftPackageReference section */ + 68C063E026E73F6400151BCA /* XCRemoteSwiftPackageReference "LaunchAtLogin" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/sindresorhus/LaunchAtLogin"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 4.1.0; + }; + }; +/* End XCRemoteSwiftPackageReference section */ + +/* Begin XCSwiftPackageProductDependency section */ + 68C063E126E73F6400151BCA /* LaunchAtLogin */ = { + isa = XCSwiftPackageProductDependency; + package = 68C063E026E73F6400151BCA /* XCRemoteSwiftPackageReference "LaunchAtLogin" */; + productName = LaunchAtLogin; + }; +/* End XCSwiftPackageProductDependency section */ + }; + rootObject = 68B1C7ED26DD3A4300A7E552 /* Project object */; +} diff --git a/diacritics-macos.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/diacritics-macos.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved new file mode 100644 index 0000000..1f5ee3f --- /dev/null +++ b/diacritics-macos.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -0,0 +1,16 @@ +{ + "object": { + "pins": [ + { + "package": "LaunchAtLogin", + "repositoryURL": "https://github.com/sindresorhus/LaunchAtLogin", + "state": { + "branch": null, + "revision": "6b16bcdf7d45a9d76a768a5c4912dde925cf0e95", + "version": "4.1.0" + } + } + ] + }, + "version": 1 +} diff --git a/diacritics-macos.xcodeproj/xcshareddata/xcschemes/diacritics-macos.xcscheme b/diacritics-macos.xcodeproj/xcshareddata/xcschemes/diacritics-macos.xcscheme new file mode 100644 index 0000000..14cd26f --- /dev/null +++ b/diacritics-macos.xcodeproj/xcshareddata/xcschemes/diacritics-macos.xcscheme @@ -0,0 +1,78 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/diacritics-macos.xcodeproj/xcuserdata/matyas.xcuserdatad/xcschemes/xcschememanagement.plist b/diacritics-macos.xcodeproj/xcuserdata/matyas.xcuserdatad/xcschemes/xcschememanagement.plist new file mode 100644 index 0000000..432ce86 --- /dev/null +++ b/diacritics-macos.xcodeproj/xcuserdata/matyas.xcuserdatad/xcschemes/xcschememanagement.plist @@ -0,0 +1,22 @@ + + + + + SchemeUserState + + diacritics-macos.xcscheme_^#shared#^_ + + orderHint + 0 + + + SuppressBuildableAutocreation + + 68B1C7F426DD3A4300A7E552 + + primary + + + + + diff --git a/diacritics-macos/AppDelegate.swift b/diacritics-macos/AppDelegate.swift new file mode 100644 index 0000000..07a1b96 --- /dev/null +++ b/diacritics-macos/AppDelegate.swift @@ -0,0 +1,80 @@ +import Cocoa +import Carbon +import SwiftUI + +@main +class AppDelegate: NSObject, NSApplicationDelegate { + + private var window: NSWindow! + + private var popover: NSPopover! + private var statusBarItem: NSStatusItem! + + func applicationDidFinishLaunching(_ aNotification: Notification) { + GlobalHotKeyService.installHandler() + + let isInitialRegistrationSuccessful: Bool + if let initialHotKey = StorageService.hotKey { + isInitialRegistrationSuccessful = GlobalHotKeyService.registerHotKey(hotKey: initialHotKey) + } else { + isInitialRegistrationSuccessful = true + } + + // Create the SwiftUI view that provides the window contents. + let contentView = ContentView( + hotKey: StorageService.hotKey, + isErrored: !isInitialRegistrationSuccessful + ) + + popover = NSPopover() + popover.contentSize = NSSize(width: 240, height: 360) + popover.behavior = .transient + popover.contentViewController = NSHostingController(rootView: contentView) + + statusBarItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.squareLength) + constructMenu() + + if let button = statusBarItem.button { + button.image = NSImage(named: "logo") + button.image?.size = NSSize(width: 18.0, height: 18.0) + } + } + + private func constructMenu() { + let menu = NSMenu() + + menu.addItem(NSMenuItem(title: "Menu.Diacriticize".localized, action: #selector(AppDelegate.addDiacritics(_:)), keyEquivalent: "")) + menu.addItem(NSMenuItem(title: "Menu.Preferences".localized, action: #selector(AppDelegate.togglePopover(_:)), keyEquivalent: ",")) + menu.addItem(NSMenuItem.separator()) + menu.addItem(NSMenuItem(title: "Menu.Quit".localized, action: #selector(NSApplication.terminate(_:)), keyEquivalent: "q")) + + statusBarItem.menu = menu + } + + func applicationWillTerminate(_ aNotification: Notification) { + // Insert code here to tear down your application + } + + @objc + func togglePopover(_ sender: AnyObject?) { + if let button = statusBarItem.button { + if popover.isShown { + popover.performClose(sender) + } else { + popover.show(relativeTo: button.bounds, of: button, preferredEdge: NSRectEdge.minY) + popover.contentViewController?.view.window?.becomeKey() + } + } + } + + @objc + func addDiacritics(_ sender: AnyObject?) { + DiacriticsService.addDiacriticsToClipboardText() + } +} + +func hotKeyHandler(nextHandler: EventHandlerCallRef?, eventRef: EventRef?, userData: UnsafeMutableRawPointer?) -> OSStatus { + DiacriticsService.addDiacriticsToClipboardText() + + return noErr +} diff --git a/diacritics-macos/Assets.xcassets/AccentColor.colorset/Contents.json b/diacritics-macos/Assets.xcassets/AccentColor.colorset/Contents.json new file mode 100644 index 0000000..eb87897 --- /dev/null +++ b/diacritics-macos/Assets.xcassets/AccentColor.colorset/Contents.json @@ -0,0 +1,11 @@ +{ + "colors" : [ + { + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/diacritics-macos/Assets.xcassets/AppIcon.appiconset/AppIcon-1024.png b/diacritics-macos/Assets.xcassets/AppIcon.appiconset/AppIcon-1024.png new file mode 100644 index 0000000..1926522 Binary files /dev/null and b/diacritics-macos/Assets.xcassets/AppIcon.appiconset/AppIcon-1024.png differ diff --git a/diacritics-macos/Assets.xcassets/AppIcon.appiconset/AppIcon-128.png b/diacritics-macos/Assets.xcassets/AppIcon.appiconset/AppIcon-128.png new file mode 100644 index 0000000..3d62d31 Binary files /dev/null and b/diacritics-macos/Assets.xcassets/AppIcon.appiconset/AppIcon-128.png differ diff --git a/diacritics-macos/Assets.xcassets/AppIcon.appiconset/AppIcon-16.png b/diacritics-macos/Assets.xcassets/AppIcon.appiconset/AppIcon-16.png new file mode 100644 index 0000000..2920411 Binary files /dev/null and b/diacritics-macos/Assets.xcassets/AppIcon.appiconset/AppIcon-16.png differ diff --git a/diacritics-macos/Assets.xcassets/AppIcon.appiconset/AppIcon-256.png b/diacritics-macos/Assets.xcassets/AppIcon.appiconset/AppIcon-256.png new file mode 100644 index 0000000..c06847d Binary files /dev/null and b/diacritics-macos/Assets.xcassets/AppIcon.appiconset/AppIcon-256.png differ diff --git a/diacritics-macos/Assets.xcassets/AppIcon.appiconset/AppIcon-257.png b/diacritics-macos/Assets.xcassets/AppIcon.appiconset/AppIcon-257.png new file mode 100644 index 0000000..c06847d Binary files /dev/null and b/diacritics-macos/Assets.xcassets/AppIcon.appiconset/AppIcon-257.png differ diff --git a/diacritics-macos/Assets.xcassets/AppIcon.appiconset/AppIcon-32.png b/diacritics-macos/Assets.xcassets/AppIcon.appiconset/AppIcon-32.png new file mode 100644 index 0000000..7d7819f Binary files /dev/null and b/diacritics-macos/Assets.xcassets/AppIcon.appiconset/AppIcon-32.png differ diff --git a/diacritics-macos/Assets.xcassets/AppIcon.appiconset/AppIcon-33.png b/diacritics-macos/Assets.xcassets/AppIcon.appiconset/AppIcon-33.png new file mode 100644 index 0000000..7d7819f Binary files /dev/null and b/diacritics-macos/Assets.xcassets/AppIcon.appiconset/AppIcon-33.png differ diff --git a/diacritics-macos/Assets.xcassets/AppIcon.appiconset/AppIcon-512.png b/diacritics-macos/Assets.xcassets/AppIcon.appiconset/AppIcon-512.png new file mode 100644 index 0000000..33a26d4 Binary files /dev/null and b/diacritics-macos/Assets.xcassets/AppIcon.appiconset/AppIcon-512.png differ diff --git a/diacritics-macos/Assets.xcassets/AppIcon.appiconset/AppIcon-513.png b/diacritics-macos/Assets.xcassets/AppIcon.appiconset/AppIcon-513.png new file mode 100644 index 0000000..33a26d4 Binary files /dev/null and b/diacritics-macos/Assets.xcassets/AppIcon.appiconset/AppIcon-513.png differ diff --git a/diacritics-macos/Assets.xcassets/AppIcon.appiconset/AppIcon-64.png b/diacritics-macos/Assets.xcassets/AppIcon.appiconset/AppIcon-64.png new file mode 100644 index 0000000..5adc96e Binary files /dev/null and b/diacritics-macos/Assets.xcassets/AppIcon.appiconset/AppIcon-64.png differ diff --git a/diacritics-macos/Assets.xcassets/AppIcon.appiconset/Contents.json b/diacritics-macos/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..b9ec67d --- /dev/null +++ b/diacritics-macos/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,68 @@ +{ + "images" : [ + { + "filename" : "AppIcon-16.png", + "idiom" : "mac", + "scale" : "1x", + "size" : "16x16" + }, + { + "filename" : "AppIcon-32.png", + "idiom" : "mac", + "scale" : "2x", + "size" : "16x16" + }, + { + "filename" : "AppIcon-33.png", + "idiom" : "mac", + "scale" : "1x", + "size" : "32x32" + }, + { + "filename" : "AppIcon-64.png", + "idiom" : "mac", + "scale" : "2x", + "size" : "32x32" + }, + { + "filename" : "AppIcon-128.png", + "idiom" : "mac", + "scale" : "1x", + "size" : "128x128" + }, + { + "filename" : "AppIcon-256.png", + "idiom" : "mac", + "scale" : "2x", + "size" : "128x128" + }, + { + "filename" : "AppIcon-257.png", + "idiom" : "mac", + "scale" : "1x", + "size" : "256x256" + }, + { + "filename" : "AppIcon-513.png", + "idiom" : "mac", + "scale" : "2x", + "size" : "256x256" + }, + { + "filename" : "AppIcon-512.png", + "idiom" : "mac", + "scale" : "1x", + "size" : "512x512" + }, + { + "filename" : "AppIcon-1024.png", + "idiom" : "mac", + "scale" : "2x", + "size" : "512x512" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/diacritics-macos/Assets.xcassets/Contents.json b/diacritics-macos/Assets.xcassets/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/diacritics-macos/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/diacritics-macos/Assets.xcassets/blurple.colorset/Contents.json b/diacritics-macos/Assets.xcassets/blurple.colorset/Contents.json new file mode 100644 index 0000000..15035b0 --- /dev/null +++ b/diacritics-macos/Assets.xcassets/blurple.colorset/Contents.json @@ -0,0 +1,29 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0xF2", + "green" : "0x65", + "red" : "0x58" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/diacritics-macos/Assets.xcassets/crimson.colorset/Contents.json b/diacritics-macos/Assets.xcassets/crimson.colorset/Contents.json new file mode 100644 index 0000000..3e80f83 --- /dev/null +++ b/diacritics-macos/Assets.xcassets/crimson.colorset/Contents.json @@ -0,0 +1,29 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0x3C", + "green" : "0x14", + "red" : "0xDC" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/diacritics-macos/Assets.xcassets/lingea.imageset/Contents.json b/diacritics-macos/Assets.xcassets/lingea.imageset/Contents.json new file mode 100644 index 0000000..7d40d34 --- /dev/null +++ b/diacritics-macos/Assets.xcassets/lingea.imageset/Contents.json @@ -0,0 +1,26 @@ +{ + "images" : [ + { + "filename" : "lingea.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "lingea-1.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "lingea-2.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "template-rendering-intent" : "template" + } +} diff --git a/diacritics-macos/Assets.xcassets/lingea.imageset/lingea-1.png b/diacritics-macos/Assets.xcassets/lingea.imageset/lingea-1.png new file mode 100644 index 0000000..0a701a1 Binary files /dev/null and b/diacritics-macos/Assets.xcassets/lingea.imageset/lingea-1.png differ diff --git a/diacritics-macos/Assets.xcassets/lingea.imageset/lingea-2.png b/diacritics-macos/Assets.xcassets/lingea.imageset/lingea-2.png new file mode 100644 index 0000000..0a701a1 Binary files /dev/null and b/diacritics-macos/Assets.xcassets/lingea.imageset/lingea-2.png differ diff --git a/diacritics-macos/Assets.xcassets/lingea.imageset/lingea.png b/diacritics-macos/Assets.xcassets/lingea.imageset/lingea.png new file mode 100644 index 0000000..0a701a1 Binary files /dev/null and b/diacritics-macos/Assets.xcassets/lingea.imageset/lingea.png differ diff --git a/diacritics-macos/Assets.xcassets/logo.imageset/Contents.json b/diacritics-macos/Assets.xcassets/logo.imageset/Contents.json new file mode 100644 index 0000000..02d1de3 --- /dev/null +++ b/diacritics-macos/Assets.xcassets/logo.imageset/Contents.json @@ -0,0 +1,26 @@ +{ + "images" : [ + { + "filename" : "logo.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "logo@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "logo@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "template-rendering-intent" : "template" + } +} diff --git a/diacritics-macos/Assets.xcassets/logo.imageset/logo.png b/diacritics-macos/Assets.xcassets/logo.imageset/logo.png new file mode 100644 index 0000000..46f96f0 Binary files /dev/null and b/diacritics-macos/Assets.xcassets/logo.imageset/logo.png differ diff --git a/diacritics-macos/Assets.xcassets/logo.imageset/logo@2x.png b/diacritics-macos/Assets.xcassets/logo.imageset/logo@2x.png new file mode 100644 index 0000000..e0e7b26 Binary files /dev/null and b/diacritics-macos/Assets.xcassets/logo.imageset/logo@2x.png differ diff --git a/diacritics-macos/Assets.xcassets/logo.imageset/logo@3x.png b/diacritics-macos/Assets.xcassets/logo.imageset/logo@3x.png new file mode 100644 index 0000000..b664283 Binary files /dev/null and b/diacritics-macos/Assets.xcassets/logo.imageset/logo@3x.png differ diff --git a/diacritics-macos/Base.lproj/Main.storyboard b/diacritics-macos/Base.lproj/Main.storyboard new file mode 100644 index 0000000..d7ebe12 --- /dev/null +++ b/diacritics-macos/Base.lproj/Main.storyboard @@ -0,0 +1,683 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Default + + + + + + + Left to Right + + + + + + + Right to Left + + + + + + + + + + + Default + + + + + + + Left to Right + + + + + + + Right to Left + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/diacritics-macos/ContentView.swift b/diacritics-macos/ContentView.swift new file mode 100644 index 0000000..93375f0 --- /dev/null +++ b/diacritics-macos/ContentView.swift @@ -0,0 +1,61 @@ +import SwiftUI +import LaunchAtLogin + +struct ContentView: View { + private(set) var hotKey: HotKey? + private(set) var isErrored: Bool + + @State + private var areNotificationsEnabled = StorageService.areNotificationsEnabled + + @ObservedObject + private var launchAtLogin = LaunchAtLogin.observable + + private static let columns = Array(repeating: GridItem(.flexible(), alignment: .leading), count: 2) + + var body: some View { + VStack { + HStack { + Text("Preferences.HotKey") + HotKeyBinderView( + hotKey: hotKey, + isErrored: isErrored + ) + .frame(maxWidth: 128, maxHeight: 26) + } + .frame(maxWidth: .infinity) + + Toggle("Preferences.LaunchAtLogin", isOn: $launchAtLogin.isEnabled) + .frame(maxWidth: .infinity, alignment: .leading) + .padding(.leading) + + Toggle("Preferences.Notification", isOn: $areNotificationsEnabled) + .frame(maxWidth: .infinity, alignment: .leading) + .padding(.leading) + .onChange(of: areNotificationsEnabled, perform: { + StorageService.areNotificationsEnabled = $0 + }) + + HStack { + Text("Preferences.PoweredBy") + Link(destination: URL(string: "https://www.nechybujte.cz/nastroje")!) { + Image("lingea") + .resizable() + .scaledToFit() + .frame(height: 20) + .foregroundColor(Color.primary) + } + .onHover { isInside in + if isInside { + NSCursor.pointingHand.push() + } else { + NSCursor.pop() + } + } + } + .frame(maxWidth: .infinity, alignment: .trailing) + .padding(.top) + } + .padding() + } +} diff --git a/diacritics-macos/HotKeyBinderView.swift b/diacritics-macos/HotKeyBinderView.swift new file mode 100644 index 0000000..fdc6043 --- /dev/null +++ b/diacritics-macos/HotKeyBinderView.swift @@ -0,0 +1,123 @@ +import SwiftUI + +struct HotKeyBinderView: View { + @State + private(set) var hotKey: HotKey? + + @State + private(set) var isErrored: Bool = false + + @State + private var isFocused: Bool = false + + var body: some View { + let cornerRadius: CGFloat = 8 + + ZStack { + // Using space to not let the Text's height shrink when empty. + Text(hotKey?.description ?? " ") + .padding(4) + KeyEventHandling(hotKey: $hotKey, isFocused: $isFocused, isErrored: $isErrored) + Image(systemName: "xmark") + .frame(maxHeight: .infinity) + .padding(.horizontal, 6) + .background(RoundedCorners(color: .clear, strokeColor: borderColor(), tl: cornerRadius, tr: cornerRadius, bl: cornerRadius, br: cornerRadius)) + .frame(maxWidth: .infinity, alignment: .trailing) + .onTapGesture { + hotKey = nil + } + } + .background(RoundedCorners(color: .white, strokeColor: borderColor(), tl: cornerRadius, tr: cornerRadius, bl: cornerRadius, br: cornerRadius)) + .onChange(of: hotKey, perform: { newHotKey in + StorageService.hotKey = newHotKey + if let newHotKey = newHotKey { + isErrored = !GlobalHotKeyService.registerHotKey(hotKey: newHotKey) + } else { + isErrored = !GlobalHotKeyService.unregisterHotKey() + } + }) + } + + private func borderColor() -> Color { + if isErrored { + return .crimson + } else if isFocused { + return .blurple + } else { + return .gray + } + } +} + +private struct KeyEventHandling: NSViewRepresentable { + private let hotKey: Binding + private let isFocused: Binding + private let isErrored: Binding + + init(hotKey: Binding, isFocused: Binding, isErrored: Binding) { + self.hotKey = hotKey + self.isFocused = isFocused + self.isErrored = isErrored + } + + func makeNSView(context: Context) -> NSView { + let view = KeyView() + view.delegate = context.coordinator + return view + } + + func updateNSView(_ nsView: NSView, context: Context) {} + + func makeCoordinator() -> Coordinator { + return Coordinator(self) + } + + class KeyView: NSView { + weak var delegate: KeyViewDelegate? + + override var acceptsFirstResponder: Bool { true } + + override func keyDown(with event: NSEvent) { + let hotKey = HotKey( + keyCode: Int(event.keyCode), + character: event.charactersIgnoringModifiers, + modifiers: event.modifierFlags + ) + delegate?.hotKeyChanged(hotKey: hotKey) + } + + override func becomeFirstResponder() -> Bool { + let result = super.becomeFirstResponder() + delegate?.focusChanged(isFocused: true) + return result + } + + override func resignFirstResponder() -> Bool { + let result = super.resignFirstResponder() + delegate?.focusChanged(isFocused: false) + return result + } + } + + class Coordinator: NSObject, KeyViewDelegate { + var parent: KeyEventHandling + + init(_ parent: KeyEventHandling) { + self.parent = parent + } + + func focusChanged(isFocused: Bool) { + parent.isFocused.wrappedValue = isFocused + } + + func hotKeyChanged(hotKey: HotKey) { + parent.hotKey.wrappedValue = hotKey + } + } +} + +private protocol KeyViewDelegate: AnyObject { + func focusChanged(isFocused: Bool) + + func hotKeyChanged(hotKey: HotKey) +} diff --git a/diacritics-macos/Info.plist b/diacritics-macos/Info.plist new file mode 100644 index 0000000..c4ecb47 --- /dev/null +++ b/diacritics-macos/Info.plist @@ -0,0 +1,34 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIconFile + + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + Acčento + CFBundlePackageType + $(PRODUCT_BUNDLE_PACKAGE_TYPE) + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + LSApplicationCategoryType + public.app-category.utilities + LSMinimumSystemVersion + $(MACOSX_DEPLOYMENT_TARGET) + LSUIElement + + NSMainStoryboardFile + Main + NSPrincipalClass + NSApplication + + diff --git a/diacritics-macos/Preview Content/Preview Assets.xcassets/Contents.json b/diacritics-macos/Preview Content/Preview Assets.xcassets/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/diacritics-macos/Preview Content/Preview Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/diacritics-macos/Resources/cs.lproj/Localizable.strings b/diacritics-macos/Resources/cs.lproj/Localizable.strings new file mode 100644 index 0000000..5a52610 --- /dev/null +++ b/diacritics-macos/Resources/cs.lproj/Localizable.strings @@ -0,0 +1,12 @@ +"Menu.Diacriticize" = "Acčento!"; +"Menu.Preferences" = "Nastavení"; +"Menu.Quit" = "Zavřít"; + +"Notification.Title" = "Acčento!"; +"Notification.Subtitle.Uncertainties" = " %@ nejasných slov."; +"Notification.Subtitle.Base" = "Diakritizace dokončena.%@"; + +"Preferences.HotKey" = "Klávesová zkratka"; +"Preferences.Notification" = "Zobrazit notifikaci"; +"Preferences.LaunchAtLogin" = "Spustit po přihlášení"; +"Preferences.PoweredBy" = "umožněno díky"; diff --git a/diacritics-macos/Resources/en.lproj/Localizable.strings b/diacritics-macos/Resources/en.lproj/Localizable.strings new file mode 100644 index 0000000..01a259e --- /dev/null +++ b/diacritics-macos/Resources/en.lproj/Localizable.strings @@ -0,0 +1,12 @@ +"Menu.Diacriticize" = "Acčento!"; +"Menu.Preferences" = "Preferences"; +"Menu.Quit" = "Quit"; + +"Notification.Title" = "Acčento!"; +"Notification.Subtitle.Uncertainties" = " %@ uncertain words."; +"Notification.Subtitle.Base" = "Accenting complete.%@"; + +"Preferences.HotKey" = "Hot key"; +"Preferences.Notification" = "Show notification"; +"Preferences.LaunchAtLogin" = "Launch at login"; +"Preferences.PoweredBy" = "powered by"; diff --git a/diacritics-macos/Services/DiacriticsService.swift b/diacritics-macos/Services/DiacriticsService.swift new file mode 100644 index 0000000..21f1670 --- /dev/null +++ b/diacritics-macos/Services/DiacriticsService.swift @@ -0,0 +1,81 @@ +import Cocoa +import UserNotifications + +final class DiacriticsService { + static func addDiacriticsToClipboardText() { + guard + let payload = NSPasteboard.general.string(forType: .string).map(Request.init(text:)), + let payloadData = try? JSONEncoder().encode(payload) else { return } + + var request = URLRequest(url: URL(string: "https://www.nechybujte.cz/adddiacritics")!) + request.httpMethod = "POST" + request.setValue("Application/json", forHTTPHeaderField: "Content-Type") + request.httpBody = payloadData + + URLSession.shared.dataTask(with: request) { (data, response, error) in + guard let responseString = data.flatMap({ String(data: $0, encoding: .utf8) }) else { return } + let cleanString = responseString + .replacingOccurrences(of: "", with: "") + .replacingOccurrences(of: "", with: "") + let range = NSRange(location: 0, length: cleanString.utf16.count) + let regex = try! NSRegularExpression(pattern: "(.*?)") + let matches = regex.matches(in: cleanString, options: [], range: range) + + var conflicts: [Conflict] = [] + var resultString = cleanString + for match in matches.reversed() { + let variantsRange = match.range(at: 1) + let wordRange = match.range(at: 2) + + conflicts.append( + .init( + variants: resultString[variantsRange].components(separatedBy: ","), + word: String(resultString[wordRange]) + ) + ) + + resultString.removeSubrange(wordRange.upperBound.. Bool { + // Unregister old hotKey. + unregisterHotKey() + + let hotKeyId = EventHotKeyID( + signature: OSType("swat".fourCharCodeValue), + id: UInt32(hotKey.keyCode) + ) + + // Register hotkey. + let status = RegisterEventHotKey( + hotKeyId.id, + hotKey.modifiers.carbonFlags, + hotKeyId, + GetApplicationEventTarget(), + 0, + &hotKeyRef + ) + + if status == noErr { + print("Successfully registered hot key '\(hotKey)'.") + } else { + print("Failed to register hot key '\(hotKey)', error: '\(status)'.") + } + + return status == noErr + } + + @discardableResult + static func unregisterHotKey() -> Bool { + if let hotKeyRef = hotKeyRef { + let status = UnregisterEventHotKey(hotKeyRef) + self.hotKeyRef = nil + return status == noErr + } else { + return true + } + } +} + +private extension String { + /// This converts string to UInt as a fourCharCode + var fourCharCodeValue: Int { + var result: Int = 0 + if let data = self.data(using: String.Encoding.macOSRoman) { + data.withUnsafeBytes({ (rawBytes) in + let bytes = rawBytes.bindMemory(to: UInt8.self) + for i in 0 ..< data.count { + result = result << 8 + Int(bytes[i]) + } + }) + } + return result + } +} + +private extension NSEvent.ModifierFlags { + var carbonFlags: UInt32 { + let flags = rawValue + var newFlags: Int = 0 + + if ((flags & NSEvent.ModifierFlags.control.rawValue) > 0) { + newFlags |= controlKey + } + + if ((flags & NSEvent.ModifierFlags.command.rawValue) > 0) { + newFlags |= cmdKey + } + + if ((flags & NSEvent.ModifierFlags.shift.rawValue) > 0) { + newFlags |= shiftKey; + } + + if ((flags & NSEvent.ModifierFlags.option.rawValue) > 0) { + newFlags |= optionKey + } + + if ((flags & NSEvent.ModifierFlags.capsLock.rawValue) > 0) { + newFlags |= alphaLock + } + + return UInt32(newFlags) + } +} diff --git a/diacritics-macos/Services/StorageService.swift b/diacritics-macos/Services/StorageService.swift new file mode 100644 index 0000000..497230c --- /dev/null +++ b/diacritics-macos/Services/StorageService.swift @@ -0,0 +1,30 @@ +import Foundation +import Carbon + +final class StorageService { + private static let hotKeyKey = "hotKey" + private static let notificationsEnabledKey = "notificationsEnabled" + + static var hotKey: HotKey? { + get { + UserDefaults.standard.data(forKey: Self.hotKeyKey).map { try! JSONDecoder().decode(HotKey.self, from: $0) } + ?? HotKey(keyCode: kVK_ANSI_D, character: "D", modifiers: [.control, .option, .command]) + } + set { + if let newHotKey = newValue { + UserDefaults.standard.setValue(try! JSONEncoder().encode(newHotKey), forKey: Self.hotKeyKey) + } else { + UserDefaults.standard.removeObject(forKey: Self.hotKeyKey) + } + } + } + + static var areNotificationsEnabled: Bool { + get { + UserDefaults.standard.bool(forKey: Self.notificationsEnabledKey) + } + set { + UserDefaults.standard.set(newValue, forKey: Self.notificationsEnabledKey) + } + } +} diff --git a/diacritics-macos/Utils/Color+assets.swift b/diacritics-macos/Utils/Color+assets.swift new file mode 100644 index 0000000..0c44d52 --- /dev/null +++ b/diacritics-macos/Utils/Color+assets.swift @@ -0,0 +1,11 @@ +import SwiftUI + +extension Color { + static var blurple: Color { + Color("blurple") + } + + static var crimson: Color { + Color("crimson") + } +} diff --git a/diacritics-macos/Utils/HotKey.swift b/diacritics-macos/Utils/HotKey.swift new file mode 100644 index 0000000..26fe7b7 --- /dev/null +++ b/diacritics-macos/Utils/HotKey.swift @@ -0,0 +1,47 @@ +import Cocoa + +struct HotKey: Equatable { + let keyCode: Int + let character: String? + private let modifiersRawValue: UInt + + var modifiers: NSEvent.ModifierFlags { + return .init(rawValue: modifiersRawValue) + } + + init(keyCode: Int, character: String?, modifiers: NSEvent.ModifierFlags) { + self.keyCode = keyCode + self.character = character + self.modifiersRawValue = modifiers.rawValue + } +} + +extension HotKey: Codable {} + +extension HotKey: CustomStringConvertible { + var description: String { + var stringBuilder = "" + if modifiers.contains(.function) { + stringBuilder += "fn" + } + if modifiers.contains(.control) { + stringBuilder += "⌃" + } + if modifiers.contains(.option) { + stringBuilder += "⌥" + } + if modifiers.contains(.command) { + stringBuilder += "⌘" + } + if modifiers.contains(.shift) { + stringBuilder += "⇧" + } + if modifiers.contains(.capsLock) { + stringBuilder += "⇪" + } + if let character = character { + stringBuilder += character.uppercased() + } + return stringBuilder + } +} diff --git a/diacritics-macos/Utils/RoundedCorners.swift b/diacritics-macos/Utils/RoundedCorners.swift new file mode 100644 index 0000000..af93a5a --- /dev/null +++ b/diacritics-macos/Utils/RoundedCorners.swift @@ -0,0 +1,38 @@ +import SwiftUI + +// Taken from https://stackoverflow.com/a/56763282/11558478 +struct RoundedCorners: View { + var color: Color = .blue + var strokeColor: Color = .gray + var tl: CGFloat = 0.0 + var tr: CGFloat = 0.0 + var bl: CGFloat = 0.0 + var br: CGFloat = 0.0 + + var body: some View { + GeometryReader { geometry in + Path { path in + let w = geometry.size.width + let h = geometry.size.height + + // Make sure we do not exceed the size of the rectangle + let tr = min(min(self.tr, h/2), w/2) + let tl = min(min(self.tl, h/2), w/2) + let bl = min(min(self.bl, h/2), w/2) + let br = min(min(self.br, h/2), w/2) + + path.move(to: CGPoint(x: w / 2.0, y: 0)) + path.addLine(to: CGPoint(x: w - tr, y: 0)) + path.addArc(center: CGPoint(x: w - tr, y: tr), radius: tr, startAngle: Angle(degrees: -90), endAngle: Angle(degrees: 0), clockwise: false) + path.addLine(to: CGPoint(x: w, y: h - br)) + path.addArc(center: CGPoint(x: w - br, y: h - br), radius: br, startAngle: Angle(degrees: 0), endAngle: Angle(degrees: 90), clockwise: false) + path.addLine(to: CGPoint(x: bl, y: h)) + path.addArc(center: CGPoint(x: bl, y: h - bl), radius: bl, startAngle: Angle(degrees: 90), endAngle: Angle(degrees: 180), clockwise: false) + path.addLine(to: CGPoint(x: 0, y: tl)) + path.addArc(center: CGPoint(x: tl, y: tl), radius: tl, startAngle: Angle(degrees: 180), endAngle: Angle(degrees: 270), clockwise: false) + path.closeSubpath() + } + .fill(color, strokeBorder: strokeColor, lineWidth: 1) + } + } +} diff --git a/diacritics-macos/Utils/Shape+extensions.swift b/diacritics-macos/Utils/Shape+extensions.swift new file mode 100644 index 0000000..7c0f698 --- /dev/null +++ b/diacritics-macos/Utils/Shape+extensions.swift @@ -0,0 +1,17 @@ +import SwiftUI + +extension Shape { + func fill(_ fillStyle: Fill, strokeBorder strokeStyle: Stroke, lineWidth: CGFloat = 1) -> some View { + self + .stroke(strokeStyle, lineWidth: lineWidth) + .background(self.fill(fillStyle)) + } +} + +extension InsettableShape { + func fill(_ fillStyle: Fill, strokeBorder strokeStyle: Stroke, lineWidth: CGFloat = 1) -> some View { + self + .strokeBorder(strokeStyle, lineWidth: lineWidth) + .background(self.fill(fillStyle)) + } +} diff --git a/diacritics-macos/Utils/String+NSRange.swift b/diacritics-macos/Utils/String+NSRange.swift new file mode 100644 index 0000000..dbc853d --- /dev/null +++ b/diacritics-macos/Utils/String+NSRange.swift @@ -0,0 +1,19 @@ +import Cocoa + +extension String { + subscript(range: NSRange) -> Substring { + self[ + index(startIndex, offsetBy: range.lowerBound) + ..< + index(startIndex, offsetBy: range.upperBound) + ] + } + + mutating func removeSubrange(_ range: Range) { + removeSubrange( + index(startIndex, offsetBy: range.lowerBound) + ..< + index(startIndex, offsetBy: range.upperBound) + ) + } +} diff --git a/diacritics-macos/Utils/String+localized.swift b/diacritics-macos/Utils/String+localized.swift new file mode 100644 index 0000000..cd30799 --- /dev/null +++ b/diacritics-macos/Utils/String+localized.swift @@ -0,0 +1,11 @@ +import Foundation + +extension String { + var localized: String { + NSLocalizedString(self, comment: "") + } + + func localized(_ parameters: CVarArg...) -> String { + String(format: localized, arguments: parameters) + } +} diff --git a/diacritics-macos/diacritics_macos.entitlements b/diacritics-macos/diacritics_macos.entitlements new file mode 100644 index 0000000..625af03 --- /dev/null +++ b/diacritics-macos/diacritics_macos.entitlements @@ -0,0 +1,12 @@ + + + + + com.apple.security.app-sandbox + + com.apple.security.files.user-selected.read-only + + com.apple.security.network.client + + + diff --git a/img/Accento-logo.png b/img/Accento-logo.png new file mode 100644 index 0000000..5201442 Binary files /dev/null and b/img/Accento-logo.png differ diff --git a/img/Lingea-logo.png b/img/Lingea-logo.png new file mode 100644 index 0000000..c3757bc Binary files /dev/null and b/img/Lingea-logo.png differ diff --git a/zip.sh b/zip.sh new file mode 100755 index 0000000..61da63c --- /dev/null +++ b/zip.sh @@ -0,0 +1,4 @@ +#!/usr/bin/env bash + +xcodebuild -scheme diacritics-macos -configuration Release -derivedDataPath "./build" clean build +(cd build/Build/Products/Release && mv diacritics-macos.app Accento.app && zip -rX file.zip Accento.app && mv file.zip ../../../../Accento.zip)