├── SwipeSelection.plist ├── SwipeSelectionPrefs ├── Resources │ ├── Header.png │ ├── Icon@2x.png │ ├── Icon@3x.png │ ├── WVC_Header_1.png │ ├── WVC_Header_2.png │ ├── Info.plist │ └── Root.plist ├── entry.plist ├── Makefile ├── SSAppearanceSettings.m ├── Welcome.h ├── SSRoot.h ├── Welcome.m └── SSRoot.mm ├── .gitignore ├── SSSettings.h ├── SSHapticsManager.h ├── control ├── README.md ├── Makefile ├── SSHapticsManager.m ├── Tweak.h └── Tweak.xm /SwipeSelection.plist: -------------------------------------------------------------------------------- 1 | { 2 | Filter = { 3 | Bundles = ( 4 | "com.apple.UIKit", 5 | ); 6 | }; 7 | } 8 | -------------------------------------------------------------------------------- /SwipeSelectionPrefs/Resources/Header.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MiRO92/SwipeSelection/HEAD/SwipeSelectionPrefs/Resources/Header.png -------------------------------------------------------------------------------- /SwipeSelectionPrefs/Resources/Icon@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MiRO92/SwipeSelection/HEAD/SwipeSelectionPrefs/Resources/Icon@2x.png -------------------------------------------------------------------------------- /SwipeSelectionPrefs/Resources/Icon@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MiRO92/SwipeSelection/HEAD/SwipeSelectionPrefs/Resources/Icon@3x.png -------------------------------------------------------------------------------- /SwipeSelectionPrefs/Resources/WVC_Header_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MiRO92/SwipeSelection/HEAD/SwipeSelectionPrefs/Resources/WVC_Header_1.png -------------------------------------------------------------------------------- /SwipeSelectionPrefs/Resources/WVC_Header_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MiRO92/SwipeSelection/HEAD/SwipeSelectionPrefs/Resources/WVC_Header_2.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ._* 2 | *.deb 3 | .debmake 4 | _ 5 | obj 6 | .theos 7 | .DS_Store 8 | *.xcworkspace/* 9 | *.xcworkspace 10 | Tweak.m 11 | run_theos.sh 12 | run_theos_simulator.sh 13 | Release_to_Public_Repo_Shortcut.command 14 | -------------------------------------------------------------------------------- /SSSettings.h: -------------------------------------------------------------------------------- 1 | // 2 | // SSSettings.h 3 | // 4 | // 5 | // Created by MiRO on 7/9/20. 6 | // 7 | 8 | 9 | #define tweakName "SwipeSelection" 10 | #define packageID "com.miro.swipeselection" 11 | #define bundle "/Library/PreferenceBundles/SwipeSelectionPrefs.bundle" 12 | #define prefFile "/var/mobile/Library/Preferences/com.miro.swipeselection.plist" 13 | #define tweakTintColor [UIColor colorWithRed:16/255.0 green:160/255.0 blue:227/255.0 alpha:1] 14 | #define tweakTintColorHex @"10a0e3" 15 | -------------------------------------------------------------------------------- /SwipeSelectionPrefs/entry.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | entry 6 | 7 | bundle 8 | SwipeSelectionPrefs 9 | cell 10 | PSLinkCell 11 | detail 12 | SSRoot 13 | icon 14 | Icon.png 15 | isController 16 | 17 | label 18 | SwipeSelection 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /SSHapticsManager.h: -------------------------------------------------------------------------------- 1 | // 2 | // SSHapticsManager.h 3 | 4 | 5 | #import 6 | #import 7 | #import 8 | #import "Tweak.h" 9 | 10 | @interface SSHapticsManager : NSObject 11 | @property (nonatomic, assign) int feedbackTypeSegment; 12 | @property (nonatomic, assign) int tapticStrength; 13 | @property (nonatomic, assign) int hapticStrength; 14 | 15 | + (instancetype)sharedManager; 16 | - (void) triggerFeedback; 17 | - (void)actuateHapticsForType:(int)feedbackType; 18 | - (void)hapticFeedback:(int)type tapticStrength:(int)tapticStrength hapticStrength:(int)hapticStrength; 19 | 20 | @end 21 | -------------------------------------------------------------------------------- /control: -------------------------------------------------------------------------------- 1 | Package: com.miro.swipeselection 2 | Name: SwipeSelection 3 | Depends: firmware (>=13.0), mobilesubstrate, preferenceloader, ws.hbang.common (>= 1.11) 4 | Conflicts: com.iky1e.swipeselection, com.iky1e.swipeselection-pro, com.yourepo.restiveconch.swipeselectionfix 5 | Version: 1.0~beta9 6 | Depiction: https://miro92.com/repo/depictions/?p=com.miro.swipeselection 7 | Description: Select text and move the cursor using gestures on the keyboard. 8 | Architecture: iphoneos-arm 9 | Maintainer: MiRO 10 | Author: MiRO 11 | Section: Tweaks 12 | Tag: purpose::extension, compatible_min::ios13, compatible_max::ios14 13 | Icon: file:////Library/PreferenceBundles/SwipeSelectionPrefs.bundle/Header.png 14 | -------------------------------------------------------------------------------- /SwipeSelectionPrefs/Makefile: -------------------------------------------------------------------------------- 1 | include $(THEOS)/makefiles/common.mk 2 | 3 | BUNDLE_NAME = SwipeSelectionPrefs 4 | SwipeSelectionPrefs_FILES = $(wildcard *.mm *.m) ../SSHapticsManager.m 5 | SwipeSelectionPrefs_INSTALL_PATH = /Library/PreferenceBundles 6 | SwipeSelectionPrefs_FRAMEWORKS = UIKit MessageUI 7 | SwipeSelectionPrefs_PRIVATE_FRAMEWORKS = Preferences OnBoardingKit CoreGraphics 8 | SwipeSelectionPrefs_EXTRA_FRAMEWORKS += Cephei CepheiPrefs 9 | 10 | SwipeSelectionPrefs_LDFLAGS = -lMobileGestalt 11 | SwipeSelectionPrefs_CFLAGS += -fobjc-arc 12 | 13 | include $(THEOS_MAKE_PATH)/bundle.mk 14 | 15 | internal-stage:: 16 | $(ECHO_NOTHING)mkdir -p $(THEOS_STAGING_DIR)/Library/PreferenceLoader/Preferences$(ECHO_END) 17 | $(ECHO_NOTHING)cp entry.plist $(THEOS_STAGING_DIR)/Library/PreferenceLoader/Preferences/SwipeSelection.plist$(ECHO_END) 18 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | SwipeSelection 2 | ============== 3 | 4 | A new way to edit text on iOS using gestures on the keyboard to move the cursor and select text. 5 | 6 | Features 7 | - Swipe left/right to move the cursor left/right. 8 | - Swipe up/down to move the cursor up/down. 9 | - Swipe from the shift (or delete) keys to select text. 10 | 11 | 12 | This version is an updated version of the official SwipeSelection 13 | 14 | The difference from the official version: 15 | - iOS 14 compatibility. 16 | - Compatible with "SwipeExtenderX". 17 | - Swipe up/down to move the cursor up/down. 18 | - Ability to control Cursor Speed. 19 | - Support Haptic/Taptic Feedback. 20 | - Small fixes. 21 | 22 | Compatible with iOS13 & iOS14 23 | 24 | NOTE: Requires turning off the "Slide to Type" option by going to Settings -> General -> Keyboards -> Slide to Type 25 | -------------------------------------------------------------------------------- /SwipeSelectionPrefs/Resources/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | English 7 | CFBundleExecutable 8 | SwipeSelectionPrefs 9 | CFBundleIdentifier 10 | com.miro.swipeselection 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundlePackageType 14 | BNDL 15 | CFBundleShortVersionString 16 | 1.0.0 17 | CFBundleSignature 18 | ???? 19 | CFBundleVersion 20 | 1.0 21 | NSPrincipalClass 22 | SSRoot 23 | 24 | 25 | -------------------------------------------------------------------------------- /SwipeSelectionPrefs/SSAppearanceSettings.m: -------------------------------------------------------------------------------- 1 | #import "SSRoot.h" 2 | 3 | 4 | @implementation SSAppearanceSettings 5 | 6 | - (UIColor *)tintColor { 7 | return tweakTintColor; 8 | } 9 | 10 | - (UIColor *)statusBarTintColor { 11 | return [UIColor whiteColor]; 12 | } 13 | 14 | - (UIColor *)navigationBarTitleColor { 15 | return [UIColor whiteColor]; 16 | } 17 | 18 | - (UIColor *)navigationBarTintColor { 19 | return [UIColor whiteColor]; 20 | } 21 | 22 | - (UIColor *)tableViewCellSeparatorColor { 23 | return [UIColor clearColor]; 24 | } 25 | 26 | - (UIColor *)navigationBarBackgroundColor { 27 | return tweakTintColor; 28 | } 29 | 30 | - (UIColor *)tableViewCellTextColor { 31 | return tweakTintColor; 32 | } 33 | 34 | - (BOOL)translucentNavigationBar { 35 | return YES; 36 | } 37 | 38 | - (HBAppearanceSettingsLargeTitleStyle)largeTitleStyle { 39 | return 2; 40 | } 41 | @end 42 | -------------------------------------------------------------------------------- /SwipeSelectionPrefs/Welcome.h: -------------------------------------------------------------------------------- 1 | // 2 | // Welcome.h 3 | // 4 | // 5 | // Created by MiRO on 7/1/20. 6 | // 7 | 8 | #import "SSRoot.h" 9 | 10 | @interface OBButtonTray : UIView 11 | @property (nonatomic,retain) UIVisualEffectView *effectView; 12 | - (void)addButton:(id)arg1; 13 | - (void)addCaptionText:(id)arg1;; 14 | @end 15 | 16 | @interface OBBoldTrayButton : UIButton 17 | - (void)setTitle:(id)arg1 forState:(NSUInteger)arg2; 18 | + (id)buttonWithType:(NSInteger)arg1; 19 | @end 20 | 21 | @interface OBWelcomeController : UIViewController 22 | @property (nonatomic,retain) UIView *viewIfLoaded; 23 | @property (nonatomic,strong) UIColor *backgroundColor; 24 | - (OBButtonTray *)buttonTray; 25 | - (id)initWithTitle:(id)arg1 detailText:(id)arg2 icon:(id)arg3; 26 | - (void)addBulletedListItemWithTitle:(id)arg1 description:(id)arg2 image:(id)arg3; 27 | @end 28 | 29 | 30 | 31 | 32 | @interface Welcome : UIViewController 33 | - (UIImage *)imageWithTint:(UIImage *)image andTintColor:(UIColor *)tintColor; 34 | @end 35 | 36 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | ifdef sim 2 | export ARCHS = x86_64 3 | export TARGET = simulator:latest:13.0 4 | else 5 | export ARCHS = arm64 arm64e 6 | export TARGET = iphone:clang:latest:13.0 7 | endif 8 | 9 | PACKAGE_VERSION = $(THEOS_PACKAGE_BASE_VERSION) 10 | 11 | include $(THEOS)/makefiles/common.mk 12 | 13 | TWEAK_NAME = SwipeSelection 14 | SwipeSelection_FILES = $(filter-out Tweak.m, $(wildcard *.mm *.m)) Tweak.xm 15 | SwipeSelection_FRAMEWORKS = UIKit 16 | SwipeSelection_CFLAGS = -fobjc-arc 17 | 18 | include $(THEOS_MAKE_PATH)/tweak.mk 19 | 20 | ifdef sim 21 | setup:: clean all 22 | @echo "" 23 | @echo -ne "Removing old dylib... " 24 | @rm -f /opt/simject/$(TWEAK_NAME).dylib 25 | @echo "Done" 26 | @echo -ne "Copying dylib into simject folder... " 27 | @cp -v $(THEOS_OBJ_DIR)/$(TWEAK_NAME).dylib /opt/simject/$(TWEAK_NAME).dylib &>/dev/null 28 | @echo "Done" 29 | @echo -ne "Copying plist into simject folder... " 30 | @cp -v $(PWD)/$(TWEAK_NAME).plist /opt/simject &>/dev/null 31 | @echo "Done" 32 | @echo -ne "Codesigning dylib... " 33 | @codesign -f -s - /opt/simject/$(TWEAK_NAME).dylib &>/dev/null 34 | @echo "Done" 35 | @echo "" 36 | else 37 | SUBPROJECTS += SwipeSelectionPrefs 38 | endif 39 | 40 | include $(THEOS_MAKE_PATH)/aggregate.mk 41 | 42 | after-install:: 43 | install.exec "killall -9 SpringBoard" 44 | 45 | before-stage:: 46 | find . -name ".DS_Store" -type f -delete 47 | -------------------------------------------------------------------------------- /SwipeSelectionPrefs/SSRoot.h: -------------------------------------------------------------------------------- 1 | // 2 | // SSRoot.h 3 | // SwipeSelection 4 | // 5 | // 6 | 7 | #import 8 | #import 9 | #import 10 | #import 11 | #import 12 | #import 13 | #import 14 | #import 15 | #import 16 | #import 17 | #import 18 | #import 19 | 20 | #import 21 | #include 22 | #import "Welcome.h" 23 | #import "../SSSettings.h" 24 | 25 | @interface NSTask : NSObject 26 | - (id)init; 27 | - (void)launch; 28 | - (void)setArguments:(id)arg1; 29 | - (void)setLaunchPath:(id)arg1; 30 | - (void)setStandardOutput:(id)arg1; 31 | - (id)standardOutput; 32 | @end 33 | 34 | @interface NSConcreteNotification : NSNotification { // return key to dismiss the keyboard 35 | BOOL dyingObject; 36 | NSString *name; 37 | id object; 38 | NSDictionary *userInfo; 39 | } 40 | + (id)newTempNotificationWithName:(id)arg1 object:(id)arg2 userInfo:(id)arg3; 41 | - (void)dealloc; 42 | - (id)initWithName:(id)arg1 object:(id)arg2 userInfo:(id)arg3; 43 | - (id)name; 44 | - (id)object; 45 | - (void)recycle; 46 | - (id)userInfo; 47 | 48 | @end 49 | 50 | 51 | @interface HBRespringController : NSObject 52 | + (void)respring; 53 | + (void)respringAndReturnTo:(NSURL *)returnURL; 54 | @end 55 | 56 | 57 | @interface SSAppearanceSettings: HBAppearanceSettings 58 | @end 59 | 60 | 61 | #define buttonCellWithName(name) [PSSpecifier preferenceSpecifierNamed:name target:self set:NULL get:NULL detail:NULL cell:PSButtonCell edit:Nil] 62 | #define groupSpecifier(name) [PSSpecifier groupSpecifierWithName:name] 63 | #define subtitleSwitchCellWithName(name) [PSSpecifier preferenceSpecifierNamed:name target:self set:@selector(setPreferenceValue:specifier:) get:@selector(readPreferenceValue:) detail:NULL cell:PSSwitchCell edit:Nil] 64 | #define switchCellWithName(name) [PSSpecifier preferenceSpecifierNamed:name target:self set:@selector(setPreferenceValue:specifier:) get:@selector(readPreferenceValue:) detail:NULL cell:PSSwitchCell edit:Nil] 65 | #define textCellWithName(name) [PSSpecifier preferenceSpecifierNamed:name target:self set:NULL get:NULL detail:NULL cell:PSStaticTextCell edit:Nil] 66 | #define textEditCellWithName(name) [PSSpecifier preferenceSpecifierNamed:name target:self set:@selector(setPreferenceValue:specifier:) get:@selector(readPreferenceValue:) detail:NULL cell:PSEditTextCell edit:Nil] 67 | #define segmentCellWithName(name) [PSSpecifier preferenceSpecifierNamed:name target:self set:@selector(setPreferenceValue:specifier:) get:@selector(readPreferenceValue:) detail:NULL cell:PSSegmentCell edit:Nil] 68 | #define setDefaultForSpec(sDefault) [specifier setProperty:sDefault forKey:@"default"] 69 | #define setClassForSpec(className) [specifier setProperty:className forKey:@"cellClass"] 70 | #define setPlaceholderForSpec(placeholder) [specifier setProperty:placeholder forKey:@"placeholder"] 71 | 72 | #define setKeyForSpec(key) [specifier setProperty:key forKey:@"key"] 73 | #define setFooterForSpec(footer) [specifier setProperty:footer forKey:@"footerText"] 74 | -------------------------------------------------------------------------------- /SwipeSelectionPrefs/Resources/Root.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | title 6 | SwipeSelection 7 | items 8 | 9 | 10 | cell 11 | PSGroupCell 12 | label 13 | Support 14 | footerText 15 | • Having Trouble? Get in touch and I'll help when I can. 16 | 17 | 18 | action 19 | presentSupportMailController: 20 | cell 21 | PSButtonCell 22 | label 23 | Email Support 24 | 25 | 26 | cell 27 | PSGroupCell 28 | label 29 | Social Media 30 | footerText 31 | • Follow me on social media to get the latest updates about my tweaks. 32 | 33 | 34 | cellClass 35 | HBTwitterCell 36 | big 37 | 38 | height 39 | 56 40 | user 41 | MiRO92 42 | label 43 | MiRO 44 | userID 45 | 36914277 46 | showAvatar 47 | 48 | 49 | 50 | cellClass 51 | HBLinkTableCell 52 | big 53 | 54 | height 55 | 56 56 | label 57 | /u/MiRO92 58 | url 59 | https://www.reddit.com/user/MiRO92 60 | iconURL 61 | https://styles.redditmedia.com/t5_3mhgw/styles/profileIcon_18lcwor21pf51.jpg 62 | iconCircular 63 | 64 | subtitle 65 | Follow me on Reddit 66 | 67 | 68 | cell 69 | PSGroupCell 70 | footerAlignment 71 | 1 72 | footerText 73 | Updated with ❤️ 74 | 75 | 76 | cell 77 | PSGroupCell 78 | headerCellClass 79 | HBPackageNameHeaderCell 80 | packageIdentifier 81 | com.miro.swipeselection 82 | showAuthor 83 | 84 | packageNameOverride 85 | By MiRO 86 | 87 | 88 | cell 89 | PSGroupCell 90 | footerAlignment 91 | 1 92 | footerText 93 | Copyright © 2020 94 | 95 | 96 | 97 | 98 | -------------------------------------------------------------------------------- /SSHapticsManager.m: -------------------------------------------------------------------------------- 1 | // 2 | // SSHapticsManager.h 3 | 4 | 5 | #import "SSHapticsManager.h" 6 | 7 | @interface SSHapticsManager () 8 | 9 | @property (strong, nonatomic) id hapticFeedbackGenerator; 10 | 11 | @end 12 | 13 | @implementation SSHapticsManager 14 | 15 | + (instancetype)sharedManager { 16 | static SSHapticsManager *sharedManager = nil; 17 | static dispatch_once_t oncePredicate; 18 | dispatch_once(&oncePredicate, ^{ 19 | sharedManager = [[self alloc] init]; 20 | }); 21 | return sharedManager; 22 | } 23 | 24 | - (id) init { 25 | self = [super init]; 26 | if (self) { 27 | NSMutableDictionary *prefs = [[NSMutableDictionary alloc] initWithContentsOfFile:@prefFile]; 28 | 29 | _feedbackTypeSegment = ([prefs objectForKey:@"feedbackTypeSegment"] ? [[prefs objectForKey:@"feedbackTypeSegment"] intValue] : 1); 30 | _tapticStrength = ([prefs objectForKey:@"tapticStrength"] ? [[prefs objectForKey:@"tapticStrength"] intValue] : 1); 31 | _hapticStrength = ([prefs objectForKey:@"hapticStrength"] ? [[prefs objectForKey:@"hapticStrength"] intValue] : 1); 32 | 33 | } 34 | return self; 35 | } 36 | 37 | - (void) triggerFeedback { 38 | if ((_feedbackTypeSegment == 1 && _tapticStrength != 0) || (_feedbackTypeSegment == 2 && _hapticStrength != 0) ) { 39 | [self hapticFeedback:_feedbackTypeSegment tapticStrength:_tapticStrength hapticStrength:_hapticStrength]; 40 | } 41 | } 42 | 43 | - (void)hapticFeedback:(int)type tapticStrength:(int)tapticStrength hapticStrength:(int)hapticStrength { 44 | if (type == 1 && tapticStrength != 0) { 45 | UIImpactFeedbackGenerator *gen = [[UIImpactFeedbackGenerator alloc] init]; 46 | [gen prepare]; 47 | 48 | if (tapticStrength == 1) { 49 | gen = [[UIImpactFeedbackGenerator alloc] initWithStyle:UIImpactFeedbackStyleLight]; 50 | 51 | } else if (tapticStrength == 2) { 52 | gen = [[UIImpactFeedbackGenerator alloc] initWithStyle:UIImpactFeedbackStyleMedium]; 53 | 54 | } else if (tapticStrength == 3) { 55 | gen = [[UIImpactFeedbackGenerator alloc] initWithStyle:UIImpactFeedbackStyleHeavy]; 56 | 57 | } else if (tapticStrength == 4) { 58 | gen = [[UIImpactFeedbackGenerator alloc] initWithStyle:UIImpactFeedbackStyleSoft]; 59 | 60 | } else if (tapticStrength == 5) { 61 | gen = [[UIImpactFeedbackGenerator alloc] initWithStyle:UIImpactFeedbackStyleRigid]; 62 | 63 | } 64 | 65 | [gen impactOccurred]; 66 | 67 | } else if (type == 2 && hapticStrength != 0) { 68 | if (hapticStrength == 1) { 69 | AudioServicesPlaySystemSound(1519); 70 | } 71 | 72 | else if (hapticStrength == 2) { 73 | AudioServicesPlaySystemSound(1520); 74 | } 75 | 76 | else if (hapticStrength == 3) { 77 | AudioServicesPlaySystemSound(1521); 78 | } 79 | } 80 | } 81 | 82 | - (void)actuateHapticsForType:(int)feedbackType { 83 | switch (feedbackType) { 84 | case 1: 85 | [self handleHapticFeedbackForSelection]; break; 86 | case 2: 87 | [self handleHapticFeedbackForImpactStyle:UIImpactFeedbackStyleLight]; break; 88 | case 3: 89 | [self handleHapticFeedbackForImpactStyle:UIImpactFeedbackStyleMedium]; break; 90 | case 4: 91 | [self handleHapticFeedbackForImpactStyle:UIImpactFeedbackStyleHeavy]; break; 92 | case 5: 93 | [self handleHapticFeedbackForSuccess]; break; 94 | case 6: 95 | [self handleHapticFeedbackForWarning]; break; 96 | } 97 | } 98 | 99 | - (void)handleHapticFeedbackForImpactStyle:(UIImpactFeedbackStyle)style { 100 | if (@available(iOS 10.0, *)) { 101 | dispatch_async(dispatch_get_main_queue(), ^{ 102 | _hapticFeedbackGenerator = [[UIImpactFeedbackGenerator alloc] initWithStyle:style]; 103 | [_hapticFeedbackGenerator prepare]; 104 | [_hapticFeedbackGenerator impactOccurred]; 105 | _hapticFeedbackGenerator = nil; 106 | }); 107 | } 108 | } 109 | 110 | - (void)handleHapticFeedbackForError { 111 | if (@available(iOS 10.0, *)) { 112 | dispatch_async(dispatch_get_main_queue(), ^{ 113 | _hapticFeedbackGenerator = [[UINotificationFeedbackGenerator alloc] init]; 114 | [_hapticFeedbackGenerator prepare]; 115 | [_hapticFeedbackGenerator notificationOccurred:UINotificationFeedbackTypeError]; 116 | _hapticFeedbackGenerator = nil; 117 | }); 118 | } 119 | } 120 | 121 | - (void)handleHapticFeedbackForSelection { 122 | if (@available(iOS 10.0, *)) { 123 | dispatch_async(dispatch_get_main_queue(), ^{ 124 | _hapticFeedbackGenerator = [[UISelectionFeedbackGenerator alloc] init]; 125 | [_hapticFeedbackGenerator prepare]; 126 | [_hapticFeedbackGenerator selectionChanged]; 127 | _hapticFeedbackGenerator = nil; 128 | }); 129 | } 130 | } 131 | 132 | - (void)handleHapticFeedbackForSuccess { 133 | if (@available(iOS 10.0, *)) { 134 | dispatch_async(dispatch_get_main_queue(), ^{ 135 | _hapticFeedbackGenerator = [[UINotificationFeedbackGenerator alloc] init]; 136 | [_hapticFeedbackGenerator prepare]; 137 | [_hapticFeedbackGenerator notificationOccurred:UINotificationFeedbackTypeSuccess]; 138 | _hapticFeedbackGenerator = nil; 139 | }); 140 | } 141 | } 142 | 143 | - (void)handleHapticFeedbackForWarning { 144 | if (@available(iOS 10.0, *)) { 145 | dispatch_async(dispatch_get_main_queue(), ^{ 146 | _hapticFeedbackGenerator = [[UINotificationFeedbackGenerator alloc] init]; 147 | [_hapticFeedbackGenerator prepare]; 148 | [_hapticFeedbackGenerator notificationOccurred:UINotificationFeedbackTypeWarning]; 149 | _hapticFeedbackGenerator = nil; 150 | }); 151 | } 152 | } 153 | 154 | @end 155 | -------------------------------------------------------------------------------- /SwipeSelectionPrefs/Welcome.m: -------------------------------------------------------------------------------- 1 | // 2 | // Welcome.m 3 | // 4 | // 5 | // Created by MiRO on 7/1/20. 6 | // 7 | #import "Welcome.h" 8 | 9 | @interface Welcome () 10 | 11 | @end 12 | 13 | 14 | @implementation Welcome 15 | OBWelcomeController *welcomeController; // Declaring this here outside of a method will allow the use of it later, such as dismissing. 16 | 17 | - (void)viewDidLoad { 18 | [super viewDidLoad]; 19 | // Create the OBWelcomeView with a title, a desription text, and an icon if you wish. Any of this can be nil if it doesn't apply to your view. 20 | welcomeController = [[OBWelcomeController alloc] initWithTitle:[NSString stringWithFormat:@"%@", @tweakName] detailText:@"A new way to edit text on iOS using gestures on the keyboard to move the cursor and select text." icon:[UIImage imageWithContentsOfFile:[NSString stringWithFormat:@"%@/Header.png", @bundle]]]; 21 | 22 | // Create a bulleted item with a title, description, and icon. Any of the parameters can be set to nil if you wish. You can have as little or as many of these as you wish. The view automatically compensates for adjustments. 23 | // As written here, systemImageNamed is an iOS 13 feature. It is available in the UIKitCore framework publically. You are welcome to use your own images just as usual. Make sure you set them up with UIImageRenderingModeAlwaysTemplate to allow proper coloring. 24 | [welcomeController addBulletedListItemWithTitle:@"Swiping up/down" description:@"to move the cursor up/down." image:[self imageWithTint: [UIImage imageWithContentsOfFile:[NSString stringWithFormat:@"%@/WVC_Header_1.png", @bundle]] andTintColor:tweakTintColor]]; 25 | [welcomeController addBulletedListItemWithTitle:@"Swiping left/right" description:@"to move the cursor left/right." image:[self imageWithTint: [UIImage imageWithContentsOfFile:[NSString stringWithFormat:@"%@/WVC_Header_2.png", @bundle]] andTintColor:tweakTintColor]]; 26 | 27 | 28 | // Create your button here, set some properties, and add it to the controller. 29 | OBBoldTrayButton *continueButton = [OBBoldTrayButton buttonWithType:1]; 30 | [continueButton addTarget:self action:@selector(dismissWelcomeController) forControlEvents:UIControlEventTouchUpInside]; 31 | [continueButton setTitle:@"Continue" forState:UIControlStateNormal]; 32 | [continueButton setClipsToBounds:YES]; // There seems to be an internal issue with the properties, so you may need to force this to YES like so. 33 | [continueButton setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal]; // There seems to be an internal issue with the properties, so you may need to force this to be [UIColor whiteColor] like so. 34 | [continueButton.layer setCornerRadius:25]; // Set your button's corner radius. This can be whatever. If this doesn't work, make sure you make setClipsToBounds to YES. 35 | [welcomeController.buttonTray addButton:continueButton]; 36 | 37 | 38 | // Set the Blur Effect Style of the Button Tray 39 | //welcomeController.buttonTray.effectView.effect = [UIBlurEffect effectWithStyle:UIBlurEffectStyleDark]; 40 | 41 | // Create the view that will contain the blur and set the frame to the View of welcomeController 42 | UIVisualEffectView *effectWelcomeView = [[UIVisualEffectView alloc] initWithFrame:welcomeController.viewIfLoaded.bounds]; 43 | 44 | // Set the Blur Effect Style of the Blur View 45 | effectWelcomeView.effect = [UIBlurEffect effectWithStyle:UIBlurEffectStyleProminent]; 46 | 47 | // Insert the Blur View to the View of the welcomeController atIndex:0 to put it behind everything 48 | [welcomeController.viewIfLoaded insertSubview:effectWelcomeView atIndex:0]; 49 | 50 | // Set the background to the View of the welcomeController to clear so the blur will show 51 | welcomeController.viewIfLoaded.backgroundColor = [UIColor clearColor]; 52 | 53 | //The caption text goes right above the buttons, sort of like as a thank you or disclaimer. This is optional, and can be excluded from your project. 54 | [welcomeController.buttonTray addCaptionText:@"Developed by MiRO"]; 55 | 56 | welcomeController.modalPresentationStyle = UIModalPresentationPageSheet; // The same style stock iOS uses. 57 | welcomeController.modalInPresentation = YES; //Set this to yes if you don't want the user to dismiss this on a down swipe. 58 | welcomeController.view.tintColor = tweakTintColor; 59 | // If you want a different tint color. If you don't set this, the controller will take the default color. 60 | //[self presentViewController:welcomeController animated:YES completion:nil]; // Don't forget to present it! 61 | 62 | 63 | [self.view addSubview: welcomeController.view]; 64 | 65 | } 66 | 67 | - (void)dismissWelcomeController { // Say goodbye to your controller. :( 68 | [[[HBPreferences alloc] initWithIdentifier: @packageID] removeAllObjects]; 69 | [[NSFileManager defaultManager] removeItemAtPath:@prefFile error: nil]; 70 | 71 | dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.1 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{ 72 | 73 | NSMutableDictionary *prefs = [[NSMutableDictionary alloc] initWithContentsOfFile:@prefFile] ? : [NSMutableDictionary new]; 74 | 75 | [prefs setObject:@YES forKey:@"showedWelcomeVC"]; 76 | [prefs writeToFile:@prefFile atomically:YES]; 77 | 78 | [[HBPreferences preferencesForIdentifier:@packageID] setObject:@YES forKey:@"showedWelcomeVC"]; 79 | 80 | [self dismissViewControllerAnimated:YES completion:nil]; 81 | }); 82 | } 83 | 84 | - (UIImage *)imageWithTint:(UIImage *)image andTintColor:(UIColor *)tintColor { 85 | UIImage *imageNew = [image imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate]; 86 | UIImageView *imageView = [[UIImageView alloc] initWithImage:imageNew]; 87 | imageView.tintColor = tintColor; 88 | 89 | UIGraphicsBeginImageContextWithOptions(imageView.bounds.size, NO, 0.0); 90 | [imageView.layer renderInContext:UIGraphicsGetCurrentContext()]; 91 | UIImage *tintedImage = UIGraphicsGetImageFromCurrentImageContext(); 92 | UIGraphicsEndImageContext(); 93 | 94 | return tintedImage; 95 | } 96 | 97 | @end 98 | -------------------------------------------------------------------------------- /Tweak.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | #import 4 | #import 5 | #import 6 | #import 7 | 8 | #import "SSSettings.h" 9 | #import "SSHapticsManager.h" 10 | 11 | @class UIKeyboardTaskExecutionContext; 12 | 13 | @interface UIKeyboardTaskQueue : NSObject 14 | @property(retain, nonatomic) UIKeyboardTaskExecutionContext *executionContext; 15 | - (void)finishExecution; 16 | @end 17 | 18 | @interface UIKeyboardTaskExecutionContext : NSObject 19 | @property(readonly, nonatomic) UIKeyboardTaskQueue *executionQueue; 20 | @end 21 | 22 | 23 | @protocol UITextInputPrivate //, UITextInputTraits_Private, UITextSelectingContainer> 24 | - (BOOL)shouldEnableAutoShift; 25 | - (NSRange)selectionRange; 26 | - (CGRect)rectForNSRange:(NSRange)nsrange; 27 | - (NSRange)_markedTextNSRange; 28 | //-(id)selectedDOMRange; 29 | //-(id)wordInRange:(id)range; 30 | //-(void)setSelectedDOMRange:(id)range affinityDownstream:(BOOL)downstream; 31 | //-(void)replaceRangeWithTextWithoutClosingTyping:(id)textWithoutClosingTyping replacementText:(id)text; 32 | //-(CGRect)rectContainingCaretSelection; 33 | - (void)moveBackward:(unsigned)backward; 34 | - (void)moveForward:(unsigned)forward; 35 | - (unsigned short)characterBeforeCaretSelection; 36 | - (id)wordContainingCaretSelection; 37 | - (id)wordRangeContainingCaretSelection; 38 | - (id)markedText; 39 | - (void)setMarkedText:(id)text; 40 | - (BOOL)hasContent; 41 | - (void)selectAll; 42 | - (id)textColorForCaretSelection; 43 | - (id)fontForCaretSelection; 44 | - (BOOL)hasSelection; 45 | @end 46 | 47 | 48 | 49 | /** iOS 5-6 **/ 50 | @interface UIKBShape : NSObject 51 | @end 52 | 53 | @interface UIKBKey : UIKBShape 54 | @property(copy) NSString *name; 55 | @property(copy) NSString *representedString; 56 | @property(copy) NSString *displayString; 57 | @property(copy) NSString *displayType; 58 | @property(copy) NSString *interactionType; 59 | @property(copy) NSString *variantType; 60 | //@property(copy) UIKBAttributeList **attributes; 61 | @property(copy) NSString *overrideDisplayString; 62 | @property(copy) NSString *clientVariantRepresentedString; 63 | @property(copy) NSString *clientVariantActionName; 64 | @property BOOL visible; 65 | @property BOOL hidden; 66 | @property BOOL disabled; 67 | @property BOOL isGhost; 68 | @property int splitMode; 69 | @end 70 | 71 | 72 | /** iOS 7 **/ 73 | @interface UIKBTree : NSObject 74 | + (id)keyboard; 75 | + (id)key; 76 | + (id)shapesForControlKeyShapes:(id)arg1 options:(int)arg2; 77 | + (id)mergeStringForKeyName:(id)arg1; 78 | + (BOOL)shouldSkipCacheString:(id)arg1; 79 | + (id)stringForType:(int)arg1; 80 | + (id)treeOfType:(int)arg1; 81 | + (id)uniqueName; 82 | 83 | @property(retain, nonatomic) NSString *layoutTag; 84 | @property(retain, nonatomic) NSMutableDictionary *cache; 85 | @property(retain, nonatomic) NSMutableArray *subtrees; 86 | @property(retain, nonatomic) NSMutableDictionary *properties; 87 | @property(retain, nonatomic) NSString *name; 88 | @property(nonatomic) int type; 89 | 90 | - (int)flickDirection; 91 | 92 | - (BOOL)isLeafType; 93 | - (BOOL)usesKeyCharging; 94 | - (BOOL)usesAdaptiveKeys; 95 | - (BOOL)modifiesKeyplane; 96 | - (BOOL)avoidsLanguageIndicator; 97 | - (BOOL)isAlphabeticPlane; 98 | - (BOOL)noLanguageIndicator; 99 | - (BOOL)isLetters; 100 | - (BOOL)subtreesAreOrdered; 101 | 102 | @end 103 | 104 | 105 | @interface UIKeyboardLayout : UIView 106 | - (UIKBKey *)keyHitTest:(CGPoint)point; 107 | @end 108 | 109 | @interface UIKeyboardLayoutStar : UIKeyboardLayout 110 | // iOS 7 111 | - (id)keyHitTest:(CGPoint)arg1; 112 | - (id)keyHitTestWithoutCharging:(CGPoint)arg1; 113 | - (id)keyHitTestClosestToPoint:(CGPoint)arg1; 114 | - (id)keyHitTestContainingPoint:(CGPoint)arg1; 115 | - (void)willBeginIndirectSelectionGesture:(BOOL)arg1; 116 | - (void)didEndIndirectSelectionGesture:(BOOL)arg1; 117 | - (BOOL)shift; 118 | - (void)deleteAction; 119 | - (BOOL)isShiftKeyBeingHeld; 120 | - (BOOL)shift; 121 | - (BOOL)autoShift; 122 | 123 | // New 124 | - (BOOL)SS_isSpaceKey; 125 | - (BOOL)SS_shouldSelect; 126 | - (BOOL)SS_disableSwipes; 127 | 128 | @end 129 | 130 | 131 | @interface UIKeyboardImpl : UIView 132 | + (UIKeyboardImpl *)sharedInstance; 133 | + (UIKeyboardImpl *)activeInstance; 134 | @property (readonly, assign, nonatomic) UIResponder *privateInputDelegate; 135 | @property (readonly, assign, nonatomic) UIResponder *inputDelegate; 136 | - (BOOL)isLongPress; 137 | - (BOOL)isTrackpadMode; 138 | - (id)_layout; 139 | - (BOOL)callLayoutIsShiftKeyBeingHeld; 140 | - (void)handleDelete; 141 | - (void)handleDeleteAsRepeat:(BOOL)repeat; 142 | - (BOOL)canHandleDelete; 143 | - (void)deleteBackwardAndNotify:(BOOL)arg1; 144 | - (void)handleDeleteWithNonZeroInputCount; 145 | - (BOOL)isInHardwareKeyboardMode; 146 | - (void)stopAutoDelete; 147 | - (BOOL)handwritingPlane; 148 | - (id)delegateAsResponder; 149 | - (id)inputDelegate; 150 | - (id)privateInputDelegate; 151 | - (id)legacyInputDelegate; 152 | - (id)privateKeyInputDelegate; 153 | - (BOOL)isLongPress; 154 | - (void)clearAnimations; 155 | - (void)clearTransientState; 156 | - (void)setCaretBlinks:(BOOL)arg1; 157 | - (void)deleteFromInput; 158 | - (void)showSelectionCommands; 159 | - (void)updateForChangedSelection; 160 | - (BOOL)isShifted; 161 | - (BOOL)isShiftLocked; 162 | - (BOOL)isAutoShifted; 163 | - (BOOL)shiftLockedEnabled; 164 | - (BOOL)isShiftKeyBeingHeld; 165 | 166 | 167 | // SwipeSelection 168 | - (void)SSLeftRightGesture:(UIPanGestureRecognizer *)gesture; 169 | - (void)SSUpDownGesture:(UIPanGestureRecognizer *)gesture; 170 | @end 171 | 172 | 173 | @interface UIFieldEditor : NSObject 174 | + (UIFieldEditor *)sharedFieldEditor; 175 | - (void)revealSelection; 176 | @end 177 | 178 | 179 | @interface UIView(Private_text) 180 | // UIWebDocumentView 181 | - (void)_scrollRectToVisible:(CGRect)visible animated:(BOOL)animated; 182 | - (void)scrollSelectionToVisible:(BOOL)visible; 183 | 184 | // UITextInputPrivate 185 | - (CGRect)caretRect; 186 | - (void)_scrollRectToVisible:(CGRect)visible animated:(BOOL)animated; 187 | 188 | - (NSRange)selectedRange; 189 | - (NSRange)selectionRange; 190 | - (void)setSelectedRange:(NSRange)range; 191 | - (void)setSelectionRange:(NSRange)range; 192 | - (void)scrollSelectionToVisible:(BOOL)arg1; 193 | - (CGRect)rectForSelection:(NSRange)range; 194 | - (CGRect)textRectForBounds:(CGRect)rect; 195 | @end 196 | 197 | @interface WKWebView 198 | - (void)evaluateJavaScript:(id)arg1 completionHandler:(/*^block */id)arg2; 199 | - (NSString *)stringByEvaluatingJavaScriptFromString:(NSString *)script; 200 | - (id)loadHTMLString:(NSString *)string baseURL:(NSURL *)baseURL; 201 | @end 202 | 203 | // Safari webview 204 | @interface WKContentView : UIView { 205 | WKWebView *_webView; 206 | } 207 | - (void)moveByOffset:(NSInteger)offset; 208 | - (id)positionFromPosition:(id)arg1 inDirection:(NSInteger)arg2 offset:(NSInteger)arg3; 209 | - (id)positionFromPosition:(id)arg1 toBoundary:(NSInteger)arg2 inDirection:(NSInteger)arg3; 210 | - (id)textRangeFromPosition:(id)arg1 toPosition:(id)arg2; 211 | - (void)setSelectedTextRange:(UITextRange *)arg1; 212 | - (id)selectedText; 213 | - (void)selectWordBackward; 214 | - (UITextRange *)selectedTextRange; 215 | - (void)setSelectedTextRange:(UITextRange *)arg1; 216 | - (NSInteger)baseWritingDirectionForPosition:(id)arg1 inDirection:(NSInteger)arg2; 217 | - (id)tokenizer; 218 | - (UITextRange *)markedTextRange; 219 | - (id)textInRange:(id)arg1; 220 | - (void)setMarkedText:(id)arg1 selectedRange:(NSRange)arg2; 221 | - (NSRange)selectionRange; 222 | - (NSRange)_markedTextNSRange; 223 | - (UITextPosition *)beginningOfDocument; 224 | - (id)selectionRectsForRange:(id)arg1; 225 | - (NSInteger)comparePosition:(id)arg1 toPosition:(id)arg2; 226 | - (void)beginSelectionInDirection:(NSInteger)arg1 completionHandler:(id)arg2; 227 | - (void)selectTextWithGranularity:(NSInteger)arg1 atPoint:(CGPoint)arg2 completionHandler:(id)arg3; 228 | - (void)updateSelectionWithExtentPoint:(CGPoint)arg1 completionHandler:(/*^block */id)arg2; 229 | - (void)updateSelectionWithExtentPoint:(CGPoint)arg1 withBoundary:(NSInteger)arg2 completionHandler:(id)arg3; 230 | - (id)webView; 231 | - (id)positionFromPosition:(id)arg1 offset:(NSInteger)arg2; 232 | 233 | - (id)_moveToEndOfWord:(BOOL)arg1 withHistory:(id)arg2; 234 | - (id)_moveToEndOfLine:(BOOL)arg1 withHistory:(id)arg2; 235 | - (id)_moveRight:(BOOL)arg1 withHistory:(id)arg2; 236 | - (id)_moveToStartOfWord:(BOOL)arg1 withHistory:(id)arg2; 237 | - (id)_moveToStartOfLine:(BOOL)arg1 withHistory:(id)arg2; 238 | - (id)_moveLeft:(BOOL)arg1 withHistory:(id)arg2; 239 | - (id)_moveToEndOfParagraph:(BOOL)arg1 withHistory:(id)arg2; 240 | - (id)_moveToEndOfDocument:(BOOL)arg1 withHistory:(id)arg2; 241 | - (id)_moveDown:(BOOL)arg1 withHistory:(id)arg2; 242 | - (id)_moveToStartOfParagraph:(BOOL)arg1 withHistory:(id)arg2; 243 | - (id)_moveToStartOfDocument:(BOOL)arg1 withHistory:(id)arg2; 244 | - (id)_moveUp:(BOOL)arg1 withHistory:(id)arg2; 245 | 246 | @end 247 | 248 | 249 | 250 | 251 | @interface UIResponder (SwipeSelection) 252 | - (void)scrollSelectionToVisible:(BOOL)scroll; 253 | - (void)_define:(NSString *)text; 254 | @end 255 | 256 | 257 | 258 | @interface WKTextPosition : UITextPosition 259 | @property (assign,nonatomic) CGRect positionRect; 260 | + (id)textPositionWithRect:(CGRect)arg1; 261 | - (BOOL)isEqual:(id)arg1; 262 | - (id)description; 263 | - (CGRect)positionRect; 264 | - (void)setPositionRect:(CGRect)arg1; 265 | @end 266 | 267 | 268 | 269 | @interface WKTextRange : UITextRange 270 | @property (assign,nonatomic) CGRect startRect; 271 | @property (assign,nonatomic) CGRect endRect; 272 | @property (assign,nonatomic) BOOL isNone; 273 | @property (assign,nonatomic) BOOL isRange; 274 | @property (assign,nonatomic) BOOL isEditable; 275 | @property (assign,nonatomic) NSInteger selectedTextLength; 276 | @property (nonatomic,copy) NSArray *selectionRects; 277 | + (id)textRangeWithState:(BOOL)arg1 isRange:(BOOL)arg2 isEditable:(BOOL)arg3 startRect:(CGRect)arg4 endRect:(CGRect)arg5 selectionRects:(id)arg6 selectedTextLength:(NSUInteger)arg7; 278 | - (BOOL)isEqual:(id)arg1; 279 | - (id)description; 280 | - (BOOL)isEmpty; 281 | - (id)start; 282 | - (id)end; 283 | - (BOOL)isEditable; 284 | - (BOOL)_isRanged; 285 | - (BOOL)_isCaret; 286 | - (NSArray *)selectionRects; 287 | - (void)setStartRect:(CGRect)arg1; 288 | - (void)setEndRect:(CGRect)arg1; 289 | - (CGRect)startRect; 290 | - (CGRect)endRect; 291 | - (void)setIsEditable:(BOOL)arg1; 292 | - (void)setIsNone:(BOOL)arg1; 293 | - (void)setIsRange:(BOOL)arg1; 294 | - (void)setSelectedTextLength:(NSInteger)arg1; 295 | - (void)setSelectionRects:(NSArray *)arg1; 296 | - (BOOL)isRange; 297 | - (BOOL)isNone; 298 | - (NSInteger)selectedTextLength; 299 | @end 300 | 301 | 302 | 303 | @interface UIKeyboardInputMode : UITextInputMode 304 | @property (nonatomic,retain) NSString *normalizedIdentifier; 305 | @end 306 | 307 | @interface UIKeyboardInputModeController : NSObject 308 | @property (retain) UIKeyboardInputMode *currentInputMode; 309 | + (id)sharedInputModeController; 310 | - (UIKeyboardInputMode *)currentInputMode; 311 | @end 312 | -------------------------------------------------------------------------------- /SwipeSelectionPrefs/SSRoot.mm: -------------------------------------------------------------------------------- 1 | // 2 | // SSRootListController.mm 3 | // SwipeSelection 4 | // 5 | // Created by Juan Carlos Perez 01/16/2018 6 | // © CP Digital Darkroom All rights reserved. 7 | // 8 | #import "SSRoot.h" 9 | 10 | extern "C" CFNotificationCenterRef CFNotificationCenterGetDistributedCenter(void); 11 | 12 | 13 | UIImage *rescaleImage(UIImage *image, CGSize newSize) { 14 | UIGraphicsBeginImageContextWithOptions(newSize, NO, 0.0); 15 | [image drawInRect:CGRectMake(0, 0, newSize.width, newSize.height)]; 16 | UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext(); 17 | UIGraphicsEndImageContext(); 18 | return newImage; 19 | } 20 | 21 | UIImage *imageWithTint(UIImage *image, UIColor *tintColor) { 22 | UIImage *imageNew = [image imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate]; 23 | UIImageView *imageView = [[UIImageView alloc] initWithImage:imageNew]; 24 | imageView.tintColor = tintColor; 25 | 26 | UIGraphicsBeginImageContextWithOptions(imageView.bounds.size, NO, 0.0); 27 | [imageView.layer renderInContext:UIGraphicsGetCurrentContext()]; 28 | UIImage *tintedImage = UIGraphicsGetImageFromCurrentImageContext(); 29 | UIGraphicsEndImageContext(); 30 | 31 | return tintedImage; 32 | } 33 | 34 | NSArray *countArrayFromArray(NSInteger start, NSArray *countedArray) { 35 | NSArray *array = [NSArray array]; 36 | NSInteger to = [countedArray count] + start; 37 | for ( int i = start; i < to; i ++ ) 38 | array = [array arrayByAddingObject:[NSNumber numberWithInt:i]]; 39 | 40 | return array; 41 | } 42 | 43 | 44 | //@interface SSRoot : PSListController 45 | @interface SSRoot : HBRootListController 46 | @property (strong, nonatomic) NSMutableArray *dynamicHaptic; 47 | @property (strong, nonatomic) NSMutableArray *dynamicTaptic; 48 | @property (nonatomic, retain) UILabel *titleLabel; 49 | @property (nonatomic, retain) UIImageView *iconView; 50 | @end 51 | 52 | @implementation SSRoot 53 | 54 | - (instancetype)init { 55 | self = [super init]; 56 | if (self) { 57 | SSAppearanceSettings *appearanceSettings = [[SSAppearanceSettings alloc] init]; 58 | self.hb_appearanceSettings = appearanceSettings; 59 | 60 | UIBarButtonItem *respringButton = [[UIBarButtonItem alloc] initWithTitle:@"Respring" 61 | style:UIBarButtonItemStylePlain 62 | target:self 63 | action:@selector(respring)]; 64 | respringButton.tintColor = [UIColor whiteColor]; 65 | self.navigationItem.rightBarButtonItem = respringButton; 66 | 67 | [self createDynamicHaptic]; 68 | [self createDynamicTaptic]; 69 | 70 | } 71 | 72 | self.navigationItem.titleView = [UIView new]; 73 | self.titleLabel = [[UILabel alloc] initWithFrame:CGRectMake(0,0,10,10)]; 74 | self.titleLabel.font = [UIFont boldSystemFontOfSize:17]; 75 | self.titleLabel.translatesAutoresizingMaskIntoConstraints = NO; 76 | self.titleLabel.text = self.navigationItem.title; 77 | self.titleLabel.text = [NSString stringWithFormat:@"%@", @tweakName]; 78 | self.titleLabel.textColor = [UIColor whiteColor]; 79 | self.titleLabel.textAlignment = NSTextAlignmentCenter; 80 | [self.navigationItem.titleView addSubview:self.titleLabel]; 81 | 82 | self.iconView = [[UIImageView alloc] initWithFrame:CGRectMake(0,0,10,10)]; 83 | self.iconView.contentMode = UIViewContentModeScaleAspectFit; 84 | self.iconView.image = [UIImage imageWithContentsOfFile:[NSString stringWithFormat:@"%@/Icon@2x.png", @bundle]]; 85 | self.iconView.translatesAutoresizingMaskIntoConstraints = NO; 86 | self.iconView.alpha = 0.0; 87 | [self.navigationItem.titleView addSubview:self.iconView]; 88 | 89 | [NSLayoutConstraint activateConstraints:@[ 90 | [self.titleLabel.topAnchor constraintEqualToAnchor:self.navigationItem.titleView.topAnchor], 91 | [self.titleLabel.leadingAnchor constraintEqualToAnchor:self.navigationItem.titleView.leadingAnchor], 92 | [self.titleLabel.trailingAnchor constraintEqualToAnchor:self.navigationItem.titleView.trailingAnchor], 93 | [self.titleLabel.bottomAnchor constraintEqualToAnchor:self.navigationItem.titleView.bottomAnchor], 94 | [self.iconView.topAnchor constraintEqualToAnchor:self.navigationItem.titleView.topAnchor], 95 | [self.iconView.leadingAnchor constraintEqualToAnchor:self.navigationItem.titleView.leadingAnchor], 96 | [self.iconView.trailingAnchor constraintEqualToAnchor:self.navigationItem.titleView.trailingAnchor], 97 | [self.iconView.bottomAnchor constraintEqualToAnchor:self.navigationItem.titleView.bottomAnchor], 98 | ]]; 99 | return self; 100 | } 101 | 102 | - (void)viewWillAppear:(BOOL)animated { 103 | [super viewWillAppear:animated]; 104 | [self.titleLabel setText:self.navigationItem.title]; 105 | } 106 | 107 | - (void)scrollViewDidScroll:(UIScrollView *)scrollView { 108 | CGFloat offsetY = scrollView.contentOffset.y; 109 | 110 | if (offsetY > 60) { 111 | [UIView animateWithDuration:0.2 animations:^{ 112 | self.iconView.alpha = 1.0; 113 | self.titleLabel.alpha = 0.0; 114 | }]; 115 | } else { 116 | [UIView animateWithDuration:0.2 animations:^{ 117 | self.iconView.alpha = 0.0; 118 | self.titleLabel.alpha = 1.0; 119 | }]; 120 | } 121 | } 122 | 123 | - (void)viewDidLoad { 124 | [super viewDidLoad]; 125 | 126 | CGRect frame = CGRectMake(0,-50,self.table.bounds.size.width,130); 127 | UIImage *headerImage = rescaleImage([UIImage imageWithContentsOfFile:[NSString stringWithFormat:@"%@/Header.png", @bundle]], CGSizeMake(120, 120)); 128 | UIImageView *headerView = [[UIImageView alloc] initWithFrame:frame]; 129 | [headerView setImage:headerImage]; 130 | headerView.backgroundColor = [UIColor clearColor]; 131 | [headerView setContentMode:UIViewContentModeCenter]; 132 | [headerView setAutoresizingMask:UIViewAutoresizingFlexibleWidth]; 133 | 134 | self.table.tableHeaderView = headerView; 135 | } 136 | 137 | - (void)viewDidAppear:(BOOL)arg1 { 138 | [super viewDidLoad]; 139 | 140 | NSMutableDictionary *prefs = [[NSMutableDictionary alloc] initWithContentsOfFile:@prefFile]; 141 | if (![prefs objectForKey:@"showedWelcomeVC"]) { 142 | [self presentViewController:[Welcome new] animated:YES completion:nil]; 143 | } 144 | } 145 | 146 | - (id)specifiers { 147 | if (_specifiers == nil) { 148 | 149 | NSMutableArray *mutableSpecifiers = [NSMutableArray new]; 150 | PSSpecifier *specifier; 151 | 152 | specifier = groupSpecifier(@""); 153 | [mutableSpecifiers addObject:specifier]; 154 | 155 | specifier = groupSpecifier(@"Global Switch"); 156 | setFooterForSpec(@"Turning on/off requires a respring"); 157 | [mutableSpecifiers addObject:specifier]; 158 | 159 | specifier = subtitleSwitchCellWithName(@"Enabled"); 160 | [specifier setProperty:@packageID forKey:@"defaults"]; 161 | setKeyForSpec(@"enabledSwitch"); 162 | setDefaultForSpec(@YES); 163 | [mutableSpecifiers addObject:specifier]; 164 | 165 | specifier = groupSpecifier(@"Settings"); 166 | setFooterForSpec(@"• Only in Space Key: means SwipeSelection will only work when swiping on the space key.\n• Default Cursor Speed = 10 (Lower means faster)."); 167 | [mutableSpecifiers addObject:specifier]; 168 | 169 | // specifier = segmentCellWithName(@"Dead Zone: (%i)"); 170 | // [specifier setProperty:@packageID forKey:@"defaults"]; 171 | // [specifier setProperty:NSClassFromString(@"HBStepperTableCell") forKey:@"cellClass"]; 172 | // [specifier setProperty:@200 forKey:@"max"]; 173 | // [specifier setProperty:@1 forKey:@"min"]; 174 | // [specifier setProperty:@"Dead Zone: (%i)" forKey:@"label"]; 175 | // [specifier setProperty:@"Dead Zone: (1)" forKey:@"singularLabel"]; 176 | // setKeyForSpec(@"deadZone"); 177 | // setDefaultForSpec(@20); 178 | // [mutableSpecifiers addObject:specifier]; 179 | 180 | // specifier = subtitleSwitchCellWithName(@"Fade Keyboard"); 181 | // [specifier setProperty:@packageID forKey:@"defaults"]; 182 | // setKeyForSpec(@"enableKeyboardFade"); 183 | // setDefaultForSpec(@YES); 184 | // [mutableSpecifiers addObject:specifier]; 185 | 186 | specifier = subtitleSwitchCellWithName(@"Move the cursor up/down"); 187 | [specifier setProperty:@packageID forKey:@"defaults"]; 188 | setKeyForSpec(@"enabledCursorUpDown"); 189 | setDefaultForSpec(@YES); 190 | [mutableSpecifiers addObject:specifier]; 191 | 192 | specifier = subtitleSwitchCellWithName(@"Disable Trackpad"); 193 | [specifier setProperty:@packageID forKey:@"defaults"]; 194 | setKeyForSpec(@"disableTrackpad"); 195 | setDefaultForSpec(@NO); 196 | [mutableSpecifiers addObject:specifier]; 197 | 198 | specifier = subtitleSwitchCellWithName(@"Only in Space Key"); 199 | [specifier setProperty:@packageID forKey:@"defaults"]; 200 | setKeyForSpec(@"onlyInSpaceKey"); 201 | setDefaultForSpec(@NO); 202 | [mutableSpecifiers addObject:specifier]; 203 | 204 | specifier = subtitleSwitchCellWithName(@"Selection Menu After Selection"); 205 | [specifier setProperty:@packageID forKey:@"defaults"]; 206 | setKeyForSpec(@"showSelection"); 207 | setDefaultForSpec(@YES); 208 | [mutableSpecifiers addObject:specifier]; 209 | 210 | specifier = subtitleSwitchCellWithName(@"Delete Key Sound"); 211 | [specifier setProperty:@packageID forKey:@"defaults"]; 212 | setKeyForSpec(@"deleteKeySound"); 213 | setDefaultForSpec(@YES); 214 | [mutableSpecifiers addObject:specifier]; 215 | 216 | specifier = segmentCellWithName(@"Cursor Speed: (%i)"); 217 | [specifier setProperty:@packageID forKey:@"defaults"]; 218 | [specifier setProperty:NSClassFromString(@"HBStepperTableCell") forKey:@"cellClass"]; 219 | [specifier setProperty:@20 forKey:@"max"]; 220 | [specifier setProperty:@1 forKey:@"min"]; 221 | [specifier setProperty:@"Cursor Speed: (%i)" forKey:@"label"]; 222 | [specifier setProperty:@"Cursor Speed: (1)" forKey:@"singularLabel"]; 223 | setKeyForSpec(@"cursorSpeed"); 224 | setDefaultForSpec(@10); 225 | [mutableSpecifiers addObject:specifier]; 226 | 227 | specifier = groupSpecifier(@"Feedback Type"); 228 | [mutableSpecifiers addObject:specifier]; 229 | 230 | specifier = segmentCellWithName(@"Feedback Type"); 231 | [specifier setProperty:@packageID forKey:@"defaults"]; 232 | [specifier setValues:@[@(1), @(2)] titles:@[@"Taptic Engine", @"Haptic Engine"]]; 233 | setDefaultForSpec(@(1)); 234 | setKeyForSpec(@"feedbackTypeSegment"); 235 | [mutableSpecifiers addObject:specifier]; 236 | 237 | NSMutableDictionary *prefs = [[NSMutableDictionary alloc] initWithContentsOfFile:@prefFile]; 238 | int feedbackTypeSegment = ([prefs objectForKey:@"feedbackTypeSegment"] ? [[prefs objectForKey:@"feedbackTypeSegment"] intValue] : 1); 239 | 240 | if (feedbackTypeSegment == 1) { 241 | for (PSSpecifier *sp in _dynamicTaptic) { 242 | [mutableSpecifiers addObject:sp]; 243 | } 244 | } else if (feedbackTypeSegment == 2) { 245 | for (PSSpecifier *sp in _dynamicHaptic) { 246 | [mutableSpecifiers addObject:sp]; 247 | } 248 | } 249 | 250 | specifier = groupSpecifier(@"Compatibility"); 251 | setFooterForSpec(@"• This option adds SwipeExtenderX compatibility."); 252 | [mutableSpecifiers addObject:specifier]; 253 | 254 | specifier = subtitleSwitchCellWithName(@"SwipeExtenderX"); 255 | [specifier setProperty:@packageID forKey:@"defaults"]; 256 | setKeyForSpec(@"enabledSwipeExtenderX"); 257 | setDefaultForSpec(@NO); 258 | [mutableSpecifiers addObject:specifier]; 259 | 260 | specifier = groupSpecifier(@"Advanced Settings"); 261 | setFooterForSpec(@"• Default Value = 20px.\n• Default Value for SwipeExtenderX = 65px.\n\n• Cursor Moving: is the distance between the start touching point and the start moving cursor point.\n\n• This option is useful with compatibility with other swiping tweaks.\n\n• Enabling this option will override \"SwipeExtenderX Compatibility\" option."); 262 | [mutableSpecifiers addObject:specifier]; 263 | 264 | specifier = subtitleSwitchCellWithName(@"Modify Cursor Moving"); 265 | [specifier setProperty:@packageID forKey:@"defaults"]; 266 | setKeyForSpec(@"enableCursorMovingOffset"); 267 | setDefaultForSpec(@NO); 268 | [mutableSpecifiers addObject:specifier]; 269 | 270 | specifier = segmentCellWithName(@"Cursor Moving After (%i)px"); 271 | [specifier setProperty:@packageID forKey:@"defaults"]; 272 | [specifier setProperty:NSClassFromString(@"HBStepperTableCell") forKey:@"cellClass"]; 273 | [specifier setProperty:@100 forKey:@"max"]; 274 | [specifier setProperty:@1 forKey:@"min"]; 275 | [specifier setProperty:@"Cursor Moving After (%i)px" forKey:@"label"]; 276 | [specifier setProperty:@"Cursor Moving After (1)px" forKey:@"singularLabel"]; 277 | setKeyForSpec(@"cursorMovingOffset"); 278 | setDefaultForSpec(@20); 279 | [mutableSpecifiers addObject:specifier]; 280 | 281 | [mutableSpecifiers addObjectsFromArray:[self loadSpecifiersFromPlistName:@"Root" target:self]]; 282 | 283 | //_specifiers = [self loadSpecifiersFromPlistName:@"Root" target:self]; 284 | _specifiers = [mutableSpecifiers copy]; 285 | 286 | } 287 | 288 | return _specifiers; 289 | } 290 | 291 | - (void)presentSupportMailController:(PSSpecifier *)spec { 292 | MFMailComposeViewController *composeViewController = [[MFMailComposeViewController alloc] init]; 293 | [composeViewController setSubject:[NSString stringWithFormat:@"%@ Support", @tweakName]]; 294 | [composeViewController setToRecipients:[NSArray arrayWithObjects:@"MiRO ", nil]]; 295 | 296 | NSString *product = nil, *version = nil, *build = nil; 297 | product = (__bridge NSString *)MGCopyAnswer(kMGProductType, nil); 298 | version = (__bridge NSString *)MGCopyAnswer(kMGProductVersion, nil); 299 | build = (__bridge NSString *)MGCopyAnswer(kMGBuildVersion, nil); 300 | 301 | [composeViewController setMessageBody:[NSString stringWithFormat:@"\n\n\nCurrent Device: %@, iOS %@ (%@)", product, version, build] isHTML:NO]; 302 | 303 | NSTask *task = [[NSTask alloc] init]; 304 | [task setLaunchPath: @"/bin/sh"]; 305 | [task setArguments:@[@"-c", [NSString stringWithFormat:@"dpkg -l"]]]; 306 | 307 | NSPipe *pipe = [NSPipe pipe]; 308 | [task setStandardOutput:pipe]; 309 | [task launch]; 310 | 311 | NSData *data = [task.standardOutput fileHandleForReading].readDataToEndOfFile; 312 | [composeViewController addAttachmentData:data mimeType:@"text/plain" fileName:@"dpkgl.txt"]; 313 | 314 | [self.navigationController presentViewController:composeViewController animated:YES completion:nil]; 315 | composeViewController.mailComposeDelegate = self; 316 | } 317 | 318 | - (void)mailComposeController:(MFMailComposeViewController *)controller didFinishWithResult:(MFMailComposeResult)result error:(NSError *)error { 319 | [self dismissViewControllerAnimated: YES completion: nil]; 320 | } 321 | 322 | 323 | - (id) readPreferenceValue:(PSSpecifier *)specifier { 324 | NSDictionary *prefsPlist = [NSDictionary dictionaryWithContentsOfFile:[NSString stringWithFormat:@"/User/Library/Preferences/%@.plist", [specifier.properties objectForKey:@"defaults"]]]; 325 | if (![prefsPlist objectForKey:[specifier.properties objectForKey:@"key"]]) { 326 | return [specifier.properties objectForKey:@"default"]; 327 | } 328 | return [prefsPlist objectForKey:[specifier.properties objectForKey:@"key"]]; 329 | } 330 | 331 | - (void)setPreferenceValue:(id)value specifier:(PSSpecifier *)specifier { 332 | NSMutableDictionary *prefsPlist = [[NSMutableDictionary alloc] initWithContentsOfFile:[NSString stringWithFormat:@"/User/Library/Preferences/%@.plist", [specifier.properties objectForKey:@"defaults"]]]; 333 | [prefsPlist setObject:value forKey:[specifier.properties objectForKey:@"key"]]; 334 | [prefsPlist writeToFile:[NSString stringWithFormat:@"/User/Library/Preferences/%@.plist", [specifier.properties objectForKey:@"defaults"]] atomically:1]; 335 | if ([specifier.properties objectForKey:@"PostNotification"]) { 336 | CFNotificationCenterPostNotification(CFNotificationCenterGetDarwinNotifyCenter(), (CFStringRef)[specifier.properties objectForKey:@"PostNotification"], NULL, NULL, YES); 337 | } 338 | [super setPreferenceValue:value specifier:specifier]; 339 | 340 | NSDictionary *properties = specifier.properties; 341 | NSString *key = properties[@"key"]; 342 | 343 | if ([key isEqualToString:@"feedbackTypeSegment"]) { 344 | if ([value intValue] == 1) { 345 | [self shouldShowFeedbackSpecifiers:1]; 346 | } else if ([value intValue] == 2) { 347 | [self shouldShowFeedbackSpecifiers:2]; 348 | } 349 | } 350 | 351 | CFNotificationCenterPostNotification( 352 | CFNotificationCenterGetDistributedCenter(), 353 | CFSTR("com.miro.swipeselection.settings"), 354 | nil, nil, true); 355 | } 356 | 357 | - (void)respring { 358 | UIAlertController *respringAlert = [UIAlertController 359 | alertControllerWithTitle: [NSString stringWithFormat:@"%@", @tweakName] 360 | message:@"Do you really want to respring?" 361 | preferredStyle: UIAlertControllerStyleAlert]; 362 | UIAlertAction *confirmAction = [UIAlertAction actionWithTitle:@"Confirm" style: UIAlertActionStyleDestructive handler: 363 | ^(UIAlertAction * action) 364 | { 365 | [HBRespringController respringAndReturnTo:[NSURL URLWithString:[NSString stringWithFormat:@"prefs:root=%@", @tweakName]]]; 366 | }]; 367 | 368 | UIAlertAction *cancelAction = [UIAlertAction actionWithTitle:@"Cancel" style: UIAlertActionStyleCancel handler: nil]; 369 | [respringAlert addAction: confirmAction]; 370 | [respringAlert addAction: cancelAction]; 371 | [self presentViewController: respringAlert animated: YES completion: nil]; 372 | } 373 | 374 | - (void)createDynamicHaptic { 375 | PSSpecifier *specifier; 376 | _dynamicHaptic = [NSMutableArray new]; 377 | 378 | specifier = [PSSpecifier preferenceSpecifierNamed:@"Haptic Type" target:self set:@selector(setPreferenceValue:specifier:) get:@selector(readPreferenceValue:) detail:NSClassFromString(@"PSListItemsController") cell:PSLinkListCell edit:nil]; 379 | [specifier setProperty:@packageID forKey:@"defaults"]; 380 | setKeyForSpec(@"hapticStrength"); 381 | setDefaultForSpec(@(1)); 382 | [specifier setValues:@[@(0), @(1), @(2), @(3)] titles:@[@"None", @"Light",@"Medium", @"Strong"] shortTitles:@[@"None", @"Light",@"Medium", @"Strong"]]; 383 | [_dynamicHaptic addObject:specifier]; 384 | } 385 | 386 | - (void)createDynamicTaptic { 387 | PSSpecifier *specifier; 388 | _dynamicTaptic = [NSMutableArray new]; 389 | 390 | specifier = [PSSpecifier preferenceSpecifierNamed:@"Taptic Type" target:self set:@selector(setPreferenceValue:specifier:) get:@selector(readPreferenceValue:) detail:NSClassFromString(@"PSListItemsController") cell:PSLinkListCell edit:nil]; 391 | [specifier setProperty:@packageID forKey:@"defaults"]; 392 | setKeyForSpec(@"tapticStrength"); 393 | setDefaultForSpec(@(1)); 394 | [specifier setValues:@[@(0), @(1), @(2), @(3), @(4), @(5)] titles:@[@"None", @"Light", @"Medium", @"Heavy", @"Soft", @"Rigid"] shortTitles:@[@"None", @"Light", @"Medium", @"Heavy", @"Soft", @"Rigid"]]; 395 | [_dynamicTaptic addObject:specifier]; 396 | } 397 | 398 | - (void)shouldShowFeedbackSpecifiers:(int)show { 399 | if (show == 1) { 400 | [self insertContiguousSpecifiers:_dynamicTaptic afterSpecifierID:@"feedbackTypeSegment" animated:YES]; 401 | [self removeContiguousSpecifiers:_dynamicHaptic animated:YES]; 402 | } else if (show == 2) { 403 | [self insertContiguousSpecifiers:_dynamicHaptic afterSpecifierID:@"feedbackTypeSegment" animated:YES]; 404 | [self removeContiguousSpecifiers:_dynamicTaptic animated:YES]; 405 | } 406 | } 407 | 408 | @end 409 | -------------------------------------------------------------------------------- /Tweak.xm: -------------------------------------------------------------------------------- 1 | // **************************************************** // 2 | // **************************************************** // 3 | // ********** Design outline ********** // 4 | // **************************************************** // 5 | // **************************************************** // 6 | // 7 | // 1 finger moves the cursour 8 | // 2 fingers moves it one word at a time 9 | // 10 | // Should be able to move between 1 and 2 fingers without lifting your hand. 11 | // If a selection has been made and you move right the selection starts moving from the end. 12 | // - else it starts at the beginning. 13 | // 14 | // Holding shift selects text between the starting point and the destination. 15 | // - the starting point is the reverse of the non selection movement. 16 | // - - movement to the right starts at the start of existing selections. 17 | // 18 | // Movement upwards when in 2 finger mode should jump to the nearest word in the new line. 19 | // - But another movement up again (without sideways movement) will jump to the nearest word to the originals x location, 20 | // - - this ensures that the cursour doesn't jump about moving far away from it's start point. 21 | // 22 | 23 | 24 | #import "Tweak.h" 25 | extern "C" CFNotificationCenterRef CFNotificationCenterGetDistributedCenter(void); 26 | 27 | // Should finish deadZone so SS will run 28 | CGFloat deadZone = 20; 29 | 30 | BOOL enabledSwitch = YES; 31 | BOOL enabledCursorUpDown = YES; 32 | BOOL showSelection = YES; 33 | BOOL enabledSwipeExtenderX = NO; 34 | BOOL enableKeyboardFade = NO; 35 | BOOL onlyInSpaceKey = NO; 36 | BOOL disableTrackpad = NO; 37 | BOOL deleteKeySound = YES; 38 | int cursorSpeed = 10; 39 | 40 | BOOL slideCutExist = NO; 41 | 42 | static UITextRange *range; 43 | static NSString *textRange; 44 | static UIResponder *tempDelegate; 45 | static UIResponder *delegate; 46 | static WKContentView *webView; 47 | 48 | #pragma mark - Helper functions 49 | 50 | UITextPosition *KH_MovePositionDirection(id tokenizer, UITextPosition *startPosition, UITextDirection direction) { 51 | if (tokenizer && startPosition) { 52 | return [tokenizer positionFromPosition:startPosition inDirection:direction offset:1]; 53 | } 54 | return nil; 55 | } 56 | 57 | UITextPosition *KH_tokenizerMovePositionWithGranularitInDirection(id tokenizer, UITextPosition *startPosition, UITextGranularity granularity, UITextDirection direction) { 58 | 59 | if (tokenizer && startPosition) { 60 | return [tokenizer positionFromPosition:startPosition toBoundary:granularity inDirection:direction]; 61 | } 62 | 63 | return nil; 64 | } 65 | 66 | BOOL KH_positionsSame(id tokenizer, UITextPosition *position1, UITextPosition *position2) { 67 | return ([tokenizer comparePosition:position1 toPosition:position2] == NSOrderedSame); 68 | } 69 | 70 | static void ShiftCaretToOneCharacter(id delegate, UITextLayoutDirection direction) { 71 | UITextPosition *position = [delegate positionFromPosition:delegate.selectedTextRange.start inDirection:direction offset:1]; 72 | if (!position) 73 | return; 74 | UITextRange *range = [delegate textRangeFromPosition:position toPosition:position]; 75 | delegate.selectedTextRange = range; 76 | } 77 | 78 | 79 | #pragma mark - GestureRecognizer 80 | @interface SSPanGesture : UIPanGestureRecognizer 81 | @end 82 | 83 | @implementation SSPanGesture 84 | - (BOOL)canBePreventedByGestureRecognizer:(UIGestureRecognizer *)gesture { 85 | if (([gesture isKindOfClass:[UIPanGestureRecognizer class]] && ![gesture isKindOfClass:[SSPanGesture class]]) || [gesture isKindOfClass:[UISwipeGestureRecognizer class]]) { 86 | // if (enabledSwipeExtenderX) return YES; 87 | return YES; 88 | } 89 | return NO; 90 | } 91 | 92 | - (BOOL)canPreventGestureRecognizer:(UIGestureRecognizer *)gesture { 93 | return NO; 94 | } 95 | @end 96 | 97 | 98 | %group globalGroup 99 | 100 | #pragma mark - Hooks 101 | %hook UIKeyboardImpl 102 | 103 | - (id)initWithFrame:(CGRect)rect { 104 | id orig = %orig; 105 | 106 | if (orig) { 107 | SSPanGesture *panRightLeft = [[SSPanGesture alloc] initWithTarget:self action:@selector(SSLeftRightGesture:)]; 108 | panRightLeft.cancelsTouchesInView = NO; 109 | [self addGestureRecognizer:panRightLeft]; 110 | 111 | if (enabledCursorUpDown) { 112 | SSPanGesture *panUpDown = [[SSPanGesture alloc] initWithTarget:self action:@selector(SSUpDownGesture:)]; 113 | panUpDown.cancelsTouchesInView = NO; 114 | [self addGestureRecognizer:panUpDown]; 115 | } 116 | } 117 | 118 | return orig; 119 | } 120 | 121 | 122 | 123 | - (void)layoutSubviews { 124 | %orig; 125 | if (@available(iOS 14.0, *)) { 126 | BOOL found = NO; 127 | for (UIGestureRecognizer *gesture in self.gestureRecognizers) { 128 | if ([gesture isKindOfClass:[SSPanGesture class]]) { 129 | found = YES; 130 | } 131 | } 132 | 133 | if (!found) { 134 | SSPanGesture *panRightLeft = [[SSPanGesture alloc] initWithTarget:self action:@selector(SSLeftRightGesture:)]; 135 | panRightLeft.cancelsTouchesInView = NO; 136 | [self addGestureRecognizer:panRightLeft]; 137 | 138 | if (enabledCursorUpDown) { 139 | SSPanGesture *panUpDown = [[SSPanGesture alloc] initWithTarget:self action:@selector(SSUpDownGesture:)]; 140 | panUpDown.cancelsTouchesInView = NO; 141 | [self addGestureRecognizer:panUpDown]; 142 | } 143 | } 144 | } 145 | } 146 | 147 | 148 | 149 | %new 150 | - (void)SSUpDownGesture:(UIPanGestureRecognizer *)gesture { 151 | static CGPoint previousPosition; 152 | static CGFloat yOffset = 0; 153 | static CGPoint realPreviousPosition; 154 | 155 | static BOOL hasStarted = NO; 156 | static BOOL feedback = NO; 157 | static BOOL longPress = NO; 158 | static BOOL handWriting = NO; 159 | static BOOL haveCheckedHand = NO; 160 | static BOOL cancelled = NO; 161 | 162 | int touchesCount = [gesture numberOfTouches]; 163 | 164 | // Stop it from running in Emoji keyboard 165 | if ([[[NSClassFromString(@"UIKeyboardInputModeController") sharedInputModeController] currentInputMode].normalizedIdentifier isEqualToString:@"emoji"]) return; 166 | 167 | UIKeyboardImpl *keyboardImpl = self; 168 | if ([keyboardImpl isTrackpadMode] && !enableKeyboardFade) return; 169 | 170 | if ([keyboardImpl respondsToSelector:@selector(isLongPress)]) { 171 | if ([keyboardImpl isLongPress]) { 172 | longPress = [keyboardImpl isLongPress]; 173 | } 174 | } 175 | 176 | // Get current layout 177 | id currentLayout = nil; 178 | if ([keyboardImpl respondsToSelector:@selector(_layout)]) { 179 | currentLayout = [keyboardImpl _layout]; 180 | } 181 | 182 | // Hand writing recognition 183 | if ([currentLayout respondsToSelector:@selector(handwritingPlane)] && !haveCheckedHand) { 184 | handWriting = [currentLayout handwritingPlane]; 185 | } 186 | else if ([currentLayout respondsToSelector:@selector(subviews)] && !handWriting && !haveCheckedHand) { 187 | NSArray *subviews = [((UIView *)currentLayout) subviews]; 188 | for (UIView *subview in subviews) { 189 | 190 | if ([subview respondsToSelector:@selector(subviews)]) { 191 | NSArray *arrayToCheck = [subview subviews]; 192 | 193 | for (id view in arrayToCheck) { 194 | NSString *classString = [NSStringFromClass([view class]) lowercaseString]; 195 | NSString *substring = [@"Handwriting" lowercaseString]; 196 | 197 | if ([classString rangeOfString:substring].location != NSNotFound) { 198 | handWriting = YES; 199 | break; 200 | } 201 | } 202 | } 203 | } 204 | haveCheckedHand = YES; 205 | } 206 | haveCheckedHand = YES; 207 | 208 | // Get the text input 209 | id delegate = nil; 210 | if ([keyboardImpl respondsToSelector:@selector(privateInputDelegate)]) { 211 | delegate = (id)keyboardImpl.privateInputDelegate; 212 | } 213 | if (!delegate && [keyboardImpl respondsToSelector:@selector(inputDelegate)]) { 214 | delegate = (id)keyboardImpl.inputDelegate; 215 | } 216 | 217 | // Start Gesture stuff 218 | if (gesture.state == UIGestureRecognizerStateEnded || gesture.state == UIGestureRecognizerStateCancelled) { 219 | if (feedback) { 220 | [[SSHapticsManager sharedManager] triggerFeedback]; 221 | feedback = NO; 222 | } 223 | 224 | longPress = NO; 225 | hasStarted = NO; 226 | feedback = NO; 227 | handWriting = NO; 228 | haveCheckedHand = NO; 229 | cancelled = NO; 230 | 231 | touchesCount = 0; 232 | gesture.cancelsTouchesInView = NO; 233 | 234 | if ([currentLayout respondsToSelector:@selector(didEndIndirectSelectionGesture:)] && enableKeyboardFade) { 235 | [currentLayout didEndIndirectSelectionGesture:YES]; 236 | } 237 | } else if (([keyboardImpl isTrackpadMode] && !enableKeyboardFade) || longPress || handWriting || !delegate || cancelled) { 238 | if ([currentLayout respondsToSelector:@selector(didEndIndirectSelectionGesture:)] && enableKeyboardFade) { 239 | [currentLayout didEndIndirectSelectionGesture:YES]; 240 | } 241 | return; 242 | 243 | } else if (gesture.state == UIGestureRecognizerStateBegan) { 244 | if ([keyboardImpl isTrackpadMode] && !enableKeyboardFade) return; 245 | 246 | yOffset = 0; 247 | 248 | previousPosition = [gesture locationInView:self]; 249 | realPreviousPosition = previousPosition; 250 | 251 | } else if (gesture.state == UIGestureRecognizerStateChanged) { 252 | if ([keyboardImpl isTrackpadMode] && !enableKeyboardFade) return; 253 | 254 | CGPoint position = [gesture locationInView:self]; 255 | CGPoint delta = CGPointMake(position.x - previousPosition.x, position.y - previousPosition.y); 256 | 257 | // If hasn't started, and it's moved less than deadZone then we should kill it. 258 | if (hasStarted == NO && delta.y < deadZone && delta.y > (-deadZone)) return; 259 | 260 | if (!feedback) { 261 | [[SSHapticsManager sharedManager] triggerFeedback]; 262 | feedback = YES; 263 | } 264 | 265 | 266 | if ([currentLayout respondsToSelector:@selector(willBeginIndirectSelectionGesture:)] && enableKeyboardFade) [currentLayout willBeginIndirectSelectionGesture:YES]; 267 | 268 | // We are running so shut other things off/down 269 | gesture.cancelsTouchesInView = YES; 270 | hasStarted = YES; 271 | 272 | [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(SSLeftRightGesture:) object:nil]; 273 | 274 | // Make x positive for comparison 275 | CGFloat positiveY = ABS(delta.y); 276 | 277 | // Only do these new big 'jumps' if we've moved far enough 278 | CGFloat yMinimum = cursorSpeed; 279 | 280 | // Should I change X? 281 | if (positiveY > yMinimum) { 282 | previousPosition = position; 283 | } 284 | 285 | yOffset += (position.y - realPreviousPosition.y); 286 | 287 | if (ABS(yOffset) >= yMinimum) { 288 | BOOL positive = (yOffset > 0); 289 | int offset = (ABS(yOffset) / yMinimum); 290 | 291 | for (int i = 0; i < offset; i++) { 292 | if (!positive) { 293 | ShiftCaretToOneCharacter(delegate, UITextLayoutDirectionUp); 294 | } else { 295 | ShiftCaretToOneCharacter(delegate, UITextLayoutDirectionDown); 296 | } 297 | } 298 | yOffset += (positive ? -(offset * yMinimum) : (offset * yMinimum)); 299 | } 300 | 301 | realPreviousPosition = position; 302 | } 303 | } 304 | 305 | %new 306 | - (void)SSLeftRightGesture:(UIPanGestureRecognizer *)gesture { 307 | // Location info (may change) 308 | static UITextRange *startingTextRange = nil; 309 | static CGPoint previousPosition; 310 | 311 | // webView fix 312 | static CGFloat xOffset = 0; 313 | static CGPoint realPreviousPosition; 314 | 315 | // Basic info 316 | static BOOL selectionIsOn = NO; 317 | static BOOL hasStarted = NO; 318 | static BOOL feedback = NO; 319 | static BOOL longPress = NO; 320 | static BOOL isSpaceKey = NO; 321 | static BOOL handWriting = NO; 322 | static BOOL haveCheckedHand = NO; 323 | static BOOL isFirstShiftDown = NO; 324 | static BOOL isMoreKey = NO; 325 | static int touchesWhenShiting = 0; 326 | static BOOL cancelled = NO; 327 | 328 | int touchesCount = [gesture numberOfTouches]; 329 | 330 | if ([[[NSClassFromString(@"UIKeyboardInputModeController") sharedInputModeController] currentInputMode].normalizedIdentifier isEqualToString:@"emoji"]) return; 331 | 332 | UIKeyboardImpl *keyboardImpl = self; 333 | if ([keyboardImpl isTrackpadMode] && !enableKeyboardFade) return; 334 | 335 | if ([keyboardImpl respondsToSelector:@selector(isLongPress)]) { 336 | if ([keyboardImpl isLongPress]) { 337 | longPress = [keyboardImpl isLongPress]; 338 | } 339 | } 340 | 341 | // Get current layout 342 | id currentLayout = nil; 343 | if ([keyboardImpl respondsToSelector:@selector(_layout)]) { 344 | currentLayout = [keyboardImpl _layout]; 345 | } 346 | 347 | // Check more key, unless it's already use 348 | if ([currentLayout respondsToSelector:@selector(SS_disableSwipes)] && !isMoreKey) { 349 | isMoreKey = [currentLayout SS_disableSwipes]; 350 | } 351 | 352 | if ([currentLayout respondsToSelector:@selector(SS_isSpaceKey)] && !isSpaceKey) { 353 | isSpaceKey = [currentLayout SS_isSpaceKey]; 354 | } 355 | 356 | 357 | // Hand writing recognition 358 | if ([currentLayout respondsToSelector:@selector(handwritingPlane)] && !haveCheckedHand) { 359 | handWriting = [currentLayout handwritingPlane]; 360 | } 361 | else if ([currentLayout respondsToSelector:@selector(subviews)] && !handWriting && !haveCheckedHand) { 362 | NSArray *subviews = [((UIView *)currentLayout) subviews]; 363 | for (UIView *subview in subviews) { 364 | 365 | if ([subview respondsToSelector:@selector(subviews)]) { 366 | NSArray *arrayToCheck = [subview subviews]; 367 | 368 | for (id view in arrayToCheck) { 369 | NSString *classString = [NSStringFromClass([view class]) lowercaseString]; 370 | NSString *substring = [@"Handwriting" lowercaseString]; 371 | 372 | if ([classString rangeOfString:substring].location != NSNotFound) { 373 | handWriting = YES; 374 | break; 375 | } 376 | } 377 | } 378 | } 379 | haveCheckedHand = YES; 380 | } 381 | haveCheckedHand = YES; 382 | 383 | // Check for shift key being pressed 384 | if ([currentLayout respondsToSelector:@selector(SS_shouldSelect)] && !selectionIsOn) { 385 | selectionIsOn = [currentLayout SS_shouldSelect]; 386 | isFirstShiftDown = YES; 387 | touchesWhenShiting = touchesCount; 388 | } 389 | 390 | if (onlyInSpaceKey && !isSpaceKey && !selectionIsOn) return; 391 | 392 | // Get the text input 393 | id delegate = nil; 394 | if ([keyboardImpl respondsToSelector:@selector(privateInputDelegate)]) { 395 | delegate = (id)keyboardImpl.privateInputDelegate; 396 | } 397 | if (!delegate && [keyboardImpl respondsToSelector:@selector(inputDelegate)]) { 398 | delegate = (id)keyboardImpl.inputDelegate; 399 | } 400 | 401 | if ([NSStringFromClass([delegate class]) isEqualToString:@"WKContentView"]) webView = (WKContentView *)delegate; 402 | 403 | 404 | // Start Gesture stuff 405 | if (gesture.state == UIGestureRecognizerStateEnded || gesture.state == UIGestureRecognizerStateCancelled) { 406 | if (feedback) { 407 | [[SSHapticsManager sharedManager] triggerFeedback]; 408 | feedback = NO; 409 | } 410 | 411 | if (hasStarted) { 412 | if (webView && [webView respondsToSelector:@selector(markedTextRange)]) { 413 | UITextRange *range = [webView markedTextRange]; 414 | if (range && !range.empty && [keyboardImpl respondsToSelector:@selector(showSelectionCommands)] && showSelection) { 415 | [keyboardImpl showSelectionCommands]; 416 | } 417 | } else if ([delegate respondsToSelector:@selector(selectedTextRange)]) { 418 | UITextRange *range = [delegate selectedTextRange]; 419 | if (range && !range.empty && [keyboardImpl respondsToSelector:@selector(showSelectionCommands)] && showSelection) { 420 | [keyboardImpl showSelectionCommands]; 421 | } 422 | } 423 | 424 | // Tell auto correct/suggestions the cursor has moved 425 | if ([keyboardImpl respondsToSelector:@selector(updateForChangedSelection)]) { 426 | [keyboardImpl updateForChangedSelection]; 427 | } 428 | } 429 | 430 | selectionIsOn = NO; 431 | isMoreKey = NO; 432 | longPress = NO; 433 | isSpaceKey = NO; 434 | hasStarted = NO; 435 | feedback = NO; 436 | handWriting = NO; 437 | haveCheckedHand = NO; 438 | cancelled = NO; 439 | 440 | touchesCount = 0; 441 | touchesWhenShiting = 0; 442 | gesture.cancelsTouchesInView = NO; 443 | 444 | if ([currentLayout respondsToSelector:@selector(didEndIndirectSelectionGesture:)] && enableKeyboardFade) { 445 | [currentLayout didEndIndirectSelectionGesture:YES]; 446 | } 447 | } else if (([keyboardImpl isTrackpadMode] && !enableKeyboardFade) || longPress || handWriting || !delegate || isMoreKey || cancelled || (slideCutExist && isSpaceKey)) { 448 | if ([currentLayout respondsToSelector:@selector(didEndIndirectSelectionGesture:)] && enableKeyboardFade) { 449 | [currentLayout didEndIndirectSelectionGesture:YES]; 450 | } 451 | return; 452 | 453 | } else if (gesture.state == UIGestureRecognizerStateBegan) { 454 | if ([keyboardImpl isTrackpadMode] && !enableKeyboardFade) return; 455 | 456 | xOffset = 0; 457 | 458 | previousPosition = [gesture locationInView:self]; 459 | realPreviousPosition = previousPosition; 460 | 461 | if ([delegate respondsToSelector:@selector(selectedTextRange)]) { 462 | startingTextRange = nil; 463 | startingTextRange = [delegate selectedTextRange]; 464 | } 465 | 466 | } else if (gesture.state == UIGestureRecognizerStateChanged) { 467 | if ([keyboardImpl isTrackpadMode] && !enableKeyboardFade) return; 468 | 469 | UITextRange *currentRange = startingTextRange; 470 | if ([delegate respondsToSelector:@selector(selectedTextRange)]) { 471 | currentRange = nil; 472 | currentRange = [delegate selectedTextRange]; 473 | } 474 | 475 | CGPoint position = [gesture locationInView:self]; 476 | CGPoint delta = CGPointMake(position.x - previousPosition.x, position.y - previousPosition.y); 477 | 478 | 479 | // If hasn't started, and it's moved less than deadZone then we should kill it. 480 | if (hasStarted == NO && delta.x < deadZone && delta.x > (-deadZone)) return; 481 | 482 | if (!feedback) { 483 | [[SSHapticsManager sharedManager] triggerFeedback]; 484 | feedback = YES; 485 | } 486 | 487 | if ([currentLayout respondsToSelector:@selector(willBeginIndirectSelectionGesture:)] && enableKeyboardFade) [currentLayout willBeginIndirectSelectionGesture:YES]; 488 | 489 | // We are running so shut other things off/down 490 | gesture.cancelsTouchesInView = YES; 491 | hasStarted = YES; 492 | 493 | [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(SSUpDownGesture:) object:nil]; 494 | if ([self respondsToSelector:@selector(LSChangeInputGesture:)]) { 495 | [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(LSChangeInputGesture:) object:nil]; 496 | } 497 | 498 | // Make x positive for comparison 499 | CGFloat positiveX = ABS(delta.x); 500 | 501 | // Determine the direction it should be going in 502 | UITextDirection textDirection; 503 | if (delta.x < 0) { 504 | textDirection = UITextStorageDirectionBackward; 505 | } else { 506 | textDirection = UITextStorageDirectionForward; 507 | } 508 | 509 | // Only do these new big 'jumps' if we've moved far enough 510 | CGFloat xMinimum = cursorSpeed; 511 | CGFloat neededTouches = 2; 512 | if (selectionIsOn && (touchesWhenShiting >= 2)) { 513 | neededTouches = 3; 514 | } 515 | 516 | UITextGranularity granularity = UITextGranularityCharacter; 517 | // Handle different touches 518 | if (touchesCount >= neededTouches) { 519 | // make it skip words 520 | granularity = UITextGranularityWord; 521 | xMinimum = 20; 522 | } 523 | 524 | 525 | // Get the new range 526 | UITextPosition *positionStart = currentRange.start; 527 | UITextPosition *positionEnd = currentRange.end; 528 | 529 | // If this is the first run we are selecting then pick our pivot point 530 | static UITextPosition *pivotPoint = nil; 531 | if (isFirstShiftDown) { 532 | pivotPoint = nil; 533 | if (delta.x > 0 || delta.y < -20) { 534 | pivotPoint = positionStart; 535 | } else { 536 | pivotPoint = positionEnd; 537 | } 538 | } 539 | 540 | // The moving position is 541 | UITextPosition *_position = nil; 542 | 543 | if (selectionIsOn && pivotPoint) { 544 | // Find which position isn't our pivot and move that. 545 | BOOL startIsPivot = KH_positionsSame(delegate, pivotPoint, positionStart); 546 | if (startIsPivot) { 547 | _position = positionEnd; 548 | } else { 549 | _position = positionStart; 550 | } 551 | 552 | } else { 553 | _position = (delta.x > 0) ? positionEnd : positionStart; 554 | if (!pivotPoint) { 555 | pivotPoint = _position; 556 | } 557 | } 558 | 559 | // Is it right to left at the current selection point? 560 | 561 | if (webView && [webView baseWritingDirectionForPosition:_position inDirection:UITextStorageDirectionForward]) { 562 | if (textDirection == UITextStorageDirectionForward) { 563 | textDirection = UITextStorageDirectionBackward; 564 | } else { 565 | textDirection = UITextStorageDirectionForward; 566 | } 567 | 568 | } else if ([delegate baseWritingDirectionForPosition:_position inDirection:UITextStorageDirectionForward] == NSWritingDirectionRightToLeft) { 569 | if (textDirection == UITextStorageDirectionForward) { 570 | textDirection = UITextStorageDirectionBackward; 571 | } else { 572 | textDirection = UITextStorageDirectionForward; 573 | } 574 | } 575 | 576 | // Try and get the tokenizer 577 | id tokenizer = nil; 578 | 579 | if ([delegate respondsToSelector:@selector(positionFromPosition:toBoundary:inDirection:)]) { 580 | tokenizer = delegate; 581 | 582 | } else if ([delegate respondsToSelector:@selector(tokenizer)]) { 583 | tokenizer = (id )delegate.tokenizer; 584 | } 585 | 586 | if (tokenizer) { 587 | // Move X 588 | if (positiveX >= 1) { 589 | UITextPosition *_position_old = _position; 590 | 591 | if (granularity == UITextGranularityCharacter && [tokenizer respondsToSelector:@selector(positionFromPosition:inDirection:offset:)] && NO) { 592 | _position = KH_MovePositionDirection(tokenizer, _position, textDirection); 593 | } else { 594 | _position = KH_tokenizerMovePositionWithGranularitInDirection(tokenizer, _position, granularity, textDirection); 595 | } 596 | 597 | // If I tried to move it and got nothing back reset it to what I had. 598 | if (!_position) { _position = _position_old; } 599 | 600 | // If I tried to move it a word at a time and nothing happened 601 | if (granularity == UITextGranularityWord && (KH_positionsSame(delegate, currentRange.start, _position) && !KH_positionsSame(delegate, delegate.beginningOfDocument, _position))) { 602 | 603 | _position = KH_tokenizerMovePositionWithGranularitInDirection(tokenizer, _position, UITextGranularityCharacter, textDirection); 604 | xMinimum = 4; 605 | } 606 | 607 | // Another sanity check 608 | if (!_position || positiveX < xMinimum) { 609 | _position = _position_old; 610 | } 611 | } 612 | } 613 | 614 | if (!selectionIsOn && _position) { 615 | pivotPoint = nil; 616 | pivotPoint = _position; 617 | } 618 | 619 | // Get a new text range 620 | UITextRange *textRange = startingTextRange = nil; 621 | if ([delegate respondsToSelector:@selector(textRangeFromPosition:toPosition:)]) { 622 | textRange = [delegate textRangeFromPosition:pivotPoint toPosition:_position]; 623 | } 624 | 625 | CGPoint oldPrevious = previousPosition; 626 | // Should I change X? 627 | if (positiveX > xMinimum) { 628 | previousPosition = position; 629 | } 630 | isFirstShiftDown = NO; 631 | 632 | 633 | // Handle Safari's broken UITextInput support 634 | if (webView) { 635 | xOffset += (position.x - realPreviousPosition.x); 636 | 637 | if (ABS(xOffset) >= xMinimum) { 638 | BOOL positive = (xOffset > 0); 639 | int offset = (ABS(xOffset) / xMinimum); 640 | 641 | for (int i = 0; i < offset; i++) { 642 | if (selectionIsOn) { 643 | if (positive) { 644 | [webView _moveRight:YES withHistory:nil]; 645 | } else { 646 | [webView _moveLeft:YES withHistory:nil]; 647 | } 648 | } else { 649 | [webView moveByOffset:(positive ? 1 : -1)]; 650 | } 651 | } 652 | xOffset += (positive ? -(offset * xMinimum) : (offset * xMinimum)); 653 | } 654 | } 655 | 656 | // Normal text input 657 | if (textRange && (oldPrevious.x != previousPosition.x || oldPrevious.y != previousPosition.y)) { 658 | [delegate setSelectedTextRange:textRange]; 659 | } 660 | realPreviousPosition = position; 661 | } 662 | } 663 | %end 664 | 665 | 666 | static BOOL deleteKeyPressed = NO; 667 | static BOOL isLongPressed = NO; 668 | static BOOL isDeleteKey = NO; 669 | static BOOL isSpaceKey = NO; 670 | static BOOL isMoreKey = NO; 671 | static BOOL triggerDelete = NO; 672 | 673 | %hook UIKeyboardDockView 674 | - (id)_keyboardLongPressInteractionRegions { 675 | if (disableTrackpad) { 676 | NSMutableArray *regions = [NSMutableArray array]; 677 | return regions; 678 | } 679 | return %orig; 680 | } 681 | %end 682 | 683 | %hook UIKeyboardLayout 684 | - (id)_keyboardLongPressInteractionRegions { 685 | if (disableTrackpad) { 686 | NSMutableArray *regions = [NSMutableArray array]; 687 | return regions; 688 | } 689 | return %orig; 690 | } 691 | %end 692 | 693 | %hook UIKeyboardLayoutStar 694 | - (void)layoutSubviews { 695 | %orig; 696 | if (disableTrackpad) self.gestureRecognizers = [NSArray new]; 697 | } 698 | 699 | /*==============touchesBegan================*/ 700 | - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { 701 | UITouch *touch = [touches anyObject]; 702 | 703 | UIKBKey *keyObject = [self keyHitTest:[touch locationInView:touch.view]]; 704 | NSString *key = [[keyObject representedString] lowercaseString]; 705 | // NSLog(@"key=[%@] - keyObject=%@ - flickDirection = %d", key, keyObject, [(UIKBTree *)keyObject flickDirection]); 706 | 707 | // Delete key 708 | if ([key isEqualToString:@"delete"]) { 709 | isDeleteKey = YES; 710 | } else { 711 | isDeleteKey = NO; 712 | } 713 | 714 | if ([key isEqualToString:@" "]) { 715 | isSpaceKey = YES; 716 | } else { 717 | isSpaceKey = NO; 718 | } 719 | 720 | // More key 721 | if ([key isEqualToString:@"more"]) { 722 | isMoreKey = YES; 723 | } else { 724 | isMoreKey = NO; 725 | } 726 | 727 | %orig; 728 | } 729 | 730 | /*==============touchesMoved================*/ 731 | - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event { 732 | UITouch *touch = [touches anyObject]; 733 | 734 | UIKBKey *keyObject = [self keyHitTest:[touch locationInView:touch.view]]; 735 | NSString *key = [[keyObject representedString] lowercaseString]; 736 | 737 | // Delete key (or the arabic key which is where the shift key would be) 738 | if ([key isEqualToString:@"delete"] || 739 | [key isEqualToString:@"ء"] || 740 | [key isEqualToString:@"ㄈ"]) { 741 | deleteKeyPressed = YES; 742 | } 743 | 744 | if ([key isEqualToString:@" "]) { 745 | isSpaceKey = YES; 746 | } else { 747 | isSpaceKey = NO; 748 | } 749 | 750 | // More key 751 | if ([key isEqualToString:@"more"]) { 752 | isMoreKey = YES; 753 | } else { 754 | isMoreKey = NO; 755 | } 756 | 757 | // NSLog(@"miroo key \"%@\"", key); 758 | 759 | %orig; 760 | } 761 | 762 | - (void)touchesCancelled:(id)arg1 withEvent:(id)arg2 { 763 | %orig(arg1, arg2); 764 | 765 | deleteKeyPressed = NO; 766 | isSpaceKey = NO; 767 | isLongPressed = NO; 768 | isMoreKey = NO; 769 | } 770 | 771 | /*==============touchesEnded================*/ 772 | - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event { 773 | %orig; 774 | 775 | isDeleteKey = NO; 776 | 777 | UITouch *touch = [touches anyObject]; 778 | NSString *key = [[[self keyHitTest:[touch locationInView:touch.view]] representedString] lowercaseString]; 779 | 780 | 781 | // Delete key 782 | if (![self isShiftKeyBeingHeld] && [key isEqualToString:@"delete"] && !isLongPressed) { 783 | UIKeyboardImpl *kb = [UIKeyboardImpl activeInstance]; 784 | if ([kb respondsToSelector:@selector(handleDelete)]) { 785 | triggerDelete = YES; 786 | [kb handleDelete]; 787 | if (deleteKeySound) AudioServicesPlaySystemSound(1155); 788 | } 789 | } 790 | 791 | deleteKeyPressed = NO; 792 | isSpaceKey = NO; 793 | isLongPressed = NO; 794 | isMoreKey = NO; 795 | } 796 | 797 | %new 798 | - (BOOL)SS_shouldSelect { 799 | return ([self isShiftKeyBeingHeld] || deleteKeyPressed); 800 | } 801 | 802 | %new 803 | - (BOOL)SS_isSpaceKey { 804 | return isSpaceKey; 805 | } 806 | 807 | %new 808 | - (BOOL)SS_disableSwipes { 809 | return isMoreKey; 810 | } 811 | %end 812 | 813 | /*==============UIKeyboardImpl================*/ 814 | %hook UIKeyboardImpl 815 | 816 | // Doesn't work to get long press on delete key but does for other keys. 817 | - (BOOL)isLongPress { 818 | isLongPressed = %orig; 819 | 820 | if (isLongPressed) [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(SSLeftRightGesture:) object:nil]; 821 | if (isLongPressed) [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(SSUpDownGesture:) object:nil]; 822 | 823 | return isLongPressed; 824 | } 825 | 826 | - (BOOL)isTrackpadMode { 827 | BOOL isTrackpadMode = %orig; 828 | 829 | if (isTrackpadMode) [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(SSLeftRightGesture:) object:nil]; 830 | if (isTrackpadMode) [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(SSUpDownGesture:) object:nil]; 831 | 832 | return isTrackpadMode; 833 | } 834 | 835 | //- (void)handleDelete { 836 | // if (!isLongPressed && isDeleteKey) { 837 | // 838 | // } 839 | // else { 840 | // %orig; 841 | // } 842 | //} 843 | 844 | - (void)handleDeleteAsRepeat:(BOOL)repeat executionContext:(UIKeyboardTaskExecutionContext *)executionContext { 845 | isLongPressed = repeat; 846 | if ([[UIKeyboardImpl activeInstance] isInHardwareKeyboardMode] || (isLongPressed && isDeleteKey)) { 847 | %orig; 848 | 849 | } else if ([self isShiftKeyBeingHeld] || (triggerDelete && !isLongPressed)) { 850 | repeat = NO; 851 | %orig; 852 | 853 | } else { 854 | [[executionContext executionQueue] finishExecution]; 855 | } 856 | 857 | triggerDelete = NO; 858 | return; 859 | } 860 | %end 861 | 862 | 863 | 864 | //%hook WKWebView 865 | //%new 866 | //- (NSString *)stringByEvaluatingJavaScriptFromString:(NSString *)script { 867 | // __block NSString *resultString = nil; 868 | // __block BOOL finished = NO; 869 | // 870 | // [self evaluateJavaScript:script completionHandler:^(id result, NSError *error) { 871 | // if (error == nil) { 872 | // if (result != nil) { 873 | // resultString = [NSString stringWithFormat:@"%@", result]; 874 | // } 875 | // } else { 876 | // NSLog(@"miroo error : %@", error.localizedDescription); 877 | // } 878 | // finished = YES; 879 | // }]; 880 | // 881 | // while (!finished) { 882 | // [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]; 883 | // } 884 | // 885 | // return resultString; 886 | //} 887 | //%end 888 | %end // globalGroup 889 | 890 | 891 | 892 | 893 | static void loadPrefs() { 894 | NSMutableDictionary *prefs = [[NSMutableDictionary alloc] initWithContentsOfFile:@prefFile]; 895 | 896 | enabledSwitch = ([prefs objectForKey:@"enabledSwitch"] ? [[prefs objectForKey:@"enabledSwitch"] boolValue] : YES); 897 | enabledCursorUpDown = ([prefs objectForKey:@"enabledCursorUpDown"] ? [[prefs objectForKey:@"enabledCursorUpDown"] boolValue] : YES); 898 | deadZone = ([prefs objectForKey:@"deadZone"] ? [[prefs objectForKey:@"deadZone"] intValue] : 20); 899 | cursorSpeed = ([prefs objectForKey:@"cursorSpeed"] ? [[prefs objectForKey:@"cursorSpeed"] intValue] : 10); 900 | showSelection = ([prefs objectForKey:@"showSelection"] ? [[prefs objectForKey:@"showSelection"] boolValue] : YES); 901 | enabledSwipeExtenderX = ([prefs objectForKey:@"enabledSwipeExtenderX"] ? [[prefs objectForKey:@"enabledSwipeExtenderX"] boolValue] : NO); 902 | onlyInSpaceKey = ([prefs objectForKey:@"onlyInSpaceKey"] ? [[prefs objectForKey:@"onlyInSpaceKey"] boolValue] : NO); 903 | disableTrackpad = ([prefs objectForKey:@"disableTrackpad"] ? [[prefs objectForKey:@"disableTrackpad"] boolValue] : NO); 904 | deleteKeySound = ([prefs objectForKey:@"deleteKeySound"] ? [[prefs objectForKey:@"deleteKeySound"] boolValue] : YES); 905 | // enableKeyboardFade = ([prefs objectForKey:@"enableKeyboardFade"] ? [[prefs objectForKey:@"enableKeyboardFade"] boolValue] : YES); 906 | disableTrackpad = ([prefs objectForKey:@"disableTrackpad"] ? [[prefs objectForKey:@"disableTrackpad"] boolValue] : NO); 907 | BOOL enableCursorMovingOffset = ([prefs objectForKey:@"enableCursorMovingOffset"] ? [[prefs objectForKey:@"enableCursorMovingOffset"] boolValue] : NO); 908 | int cursorMovingOffset = ([prefs objectForKey:@"cursorMovingOffset"] ? [[prefs objectForKey:@"cursorMovingOffset"] intValue] : 20); 909 | 910 | if (enableCursorMovingOffset) { 911 | deadZone = cursorMovingOffset; 912 | } else if (enabledSwipeExtenderX) { 913 | deadZone = 65; 914 | } 915 | } 916 | 917 | static void loadPrefsNotification(CFNotificationCenterRef center, void *observer, CFStringRef name, const void *object, CFDictionaryRef userInfo) { 918 | loadPrefs(); 919 | } 920 | 921 | %ctor { 922 | @autoreleasepool { 923 | loadPrefs(); 924 | if (enabledSwitch) { 925 | NSArray *args = [[NSClassFromString(@"NSProcessInfo") processInfo] arguments]; 926 | 927 | if (args.count != 0) { 928 | NSString *executablePath = args[0]; 929 | 930 | if (executablePath) { 931 | NSString *processName = [executablePath lastPathComponent]; 932 | 933 | BOOL isSpringBoard = [processName isEqualToString:@"SpringBoard"]; 934 | BOOL isApplication = [executablePath rangeOfString:@"/Application"].location != NSNotFound; 935 | 936 | if (isSpringBoard || isApplication) { 937 | 938 | %init(globalGroup); 939 | 940 | if ([[NSFileManager defaultManager] fileExistsAtPath:@"/Library/MobileSubstrate/DynamicLibraries/SlideCut.dylib"]) { 941 | slideCutExist = YES; 942 | } 943 | 944 | CFNotificationCenterAddObserver(CFNotificationCenterGetDistributedCenter(), NULL, loadPrefsNotification, CFSTR("com.miro.swipeselection.settings"), NULL, CFNotificationSuspensionBehaviorDeliverImmediately); 945 | } 946 | } 947 | } 948 | } 949 | } 950 | } 951 | 952 | --------------------------------------------------------------------------------