├── 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 |
--------------------------------------------------------------------------------