mirror of
https://github.com/Sunnyyoung/WeChatTweak-macOS.git
synced 2025-07-08 00:26:06 +08:00
Add dock menu item
This commit is contained in:
parent
e1cb4bf7d2
commit
5dd8ca7a40
13
JRSwizzle.h
Normal file
13
JRSwizzle.h
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
// JRSwizzle.h semver:1.0
|
||||||
|
// Copyright (c) 2007-2011 Jonathan 'Wolf' Rentzsch: http://rentzsch.com
|
||||||
|
// Some rights reserved: http://opensource.org/licenses/MIT
|
||||||
|
// https://github.com/rentzsch/jrswizzle
|
||||||
|
|
||||||
|
#import <Foundation/Foundation.h>
|
||||||
|
|
||||||
|
@interface NSObject (JRSwizzle)
|
||||||
|
|
||||||
|
+ (BOOL)jr_swizzleMethod:(SEL)origSel_ withMethod:(SEL)altSel_ error:(NSError**)error_;
|
||||||
|
+ (BOOL)jr_swizzleClassMethod:(SEL)origSel_ withClassMethod:(SEL)altSel_ error:(NSError**)error_;
|
||||||
|
|
||||||
|
@end
|
134
JRSwizzle.m
Normal file
134
JRSwizzle.m
Normal file
|
@ -0,0 +1,134 @@
|
||||||
|
// JRSwizzle.m semver:1.0
|
||||||
|
// Copyright (c) 2007-2011 Jonathan 'Wolf' Rentzsch: http://rentzsch.com
|
||||||
|
// Some rights reserved: http://opensource.org/licenses/MIT
|
||||||
|
// https://github.com/rentzsch/jrswizzle
|
||||||
|
|
||||||
|
#import "JRSwizzle.h"
|
||||||
|
|
||||||
|
#if TARGET_OS_IPHONE
|
||||||
|
#import <objc/runtime.h>
|
||||||
|
#import <objc/message.h>
|
||||||
|
#else
|
||||||
|
#import <objc/objc-class.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define SetNSErrorFor(FUNC, ERROR_VAR, FORMAT,...) \
|
||||||
|
if (ERROR_VAR) { \
|
||||||
|
NSString *errStr = [NSString stringWithFormat:@"%s: " FORMAT,FUNC,##__VA_ARGS__]; \
|
||||||
|
*ERROR_VAR = [NSError errorWithDomain:@"NSCocoaErrorDomain" \
|
||||||
|
code:-1 \
|
||||||
|
userInfo:[NSDictionary dictionaryWithObject:errStr forKey:NSLocalizedDescriptionKey]]; \
|
||||||
|
}
|
||||||
|
#define SetNSError(ERROR_VAR, FORMAT,...) SetNSErrorFor(__func__, ERROR_VAR, FORMAT, ##__VA_ARGS__)
|
||||||
|
|
||||||
|
#if OBJC_API_VERSION >= 2
|
||||||
|
#define GetClass(obj) object_getClass(obj)
|
||||||
|
#else
|
||||||
|
#define GetClass(obj) (obj ? obj->isa : Nil)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
@implementation NSObject (JRSwizzle)
|
||||||
|
|
||||||
|
+ (BOOL)jr_swizzleMethod:(SEL)origSel_ withMethod:(SEL)altSel_ error:(NSError**)error_ {
|
||||||
|
#if OBJC_API_VERSION >= 2
|
||||||
|
Method origMethod = class_getInstanceMethod(self, origSel_);
|
||||||
|
if (!origMethod) {
|
||||||
|
#if TARGET_OS_IPHONE
|
||||||
|
SetNSError(error_, @"original method %@ not found for class %@", NSStringFromSelector(origSel_), [self class]);
|
||||||
|
#else
|
||||||
|
SetNSError(error_, @"original method %@ not found for class %@", NSStringFromSelector(origSel_), [self className]);
|
||||||
|
#endif
|
||||||
|
return NO;
|
||||||
|
}
|
||||||
|
|
||||||
|
Method altMethod = class_getInstanceMethod(self, altSel_);
|
||||||
|
if (!altMethod) {
|
||||||
|
#if TARGET_OS_IPHONE
|
||||||
|
SetNSError(error_, @"alternate method %@ not found for class %@", NSStringFromSelector(altSel_), [self class]);
|
||||||
|
#else
|
||||||
|
SetNSError(error_, @"alternate method %@ not found for class %@", NSStringFromSelector(altSel_), [self className]);
|
||||||
|
#endif
|
||||||
|
return NO;
|
||||||
|
}
|
||||||
|
|
||||||
|
class_addMethod(self,
|
||||||
|
origSel_,
|
||||||
|
class_getMethodImplementation(self, origSel_),
|
||||||
|
method_getTypeEncoding(origMethod));
|
||||||
|
class_addMethod(self,
|
||||||
|
altSel_,
|
||||||
|
class_getMethodImplementation(self, altSel_),
|
||||||
|
method_getTypeEncoding(altMethod));
|
||||||
|
|
||||||
|
method_exchangeImplementations(class_getInstanceMethod(self, origSel_), class_getInstanceMethod(self, altSel_));
|
||||||
|
return YES;
|
||||||
|
#else
|
||||||
|
// Scan for non-inherited methods.
|
||||||
|
Method directOriginalMethod = NULL, directAlternateMethod = NULL;
|
||||||
|
|
||||||
|
void *iterator = NULL;
|
||||||
|
struct objc_method_list *mlist = class_nextMethodList(self, &iterator);
|
||||||
|
while (mlist) {
|
||||||
|
int method_index = 0;
|
||||||
|
for (; method_index < mlist->method_count; method_index++) {
|
||||||
|
if (mlist->method_list[method_index].method_name == origSel_) {
|
||||||
|
assert(!directOriginalMethod);
|
||||||
|
directOriginalMethod = &mlist->method_list[method_index];
|
||||||
|
}
|
||||||
|
if (mlist->method_list[method_index].method_name == altSel_) {
|
||||||
|
assert(!directAlternateMethod);
|
||||||
|
directAlternateMethod = &mlist->method_list[method_index];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mlist = class_nextMethodList(self, &iterator);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If either method is inherited, copy it up to the target class to make it non-inherited.
|
||||||
|
if (!directOriginalMethod || !directAlternateMethod) {
|
||||||
|
Method inheritedOriginalMethod = NULL, inheritedAlternateMethod = NULL;
|
||||||
|
if (!directOriginalMethod) {
|
||||||
|
inheritedOriginalMethod = class_getInstanceMethod(self, origSel_);
|
||||||
|
if (!inheritedOriginalMethod) {
|
||||||
|
SetNSError(error_, @"original method %@ not found for class %@", NSStringFromSelector(origSel_), [self className]);
|
||||||
|
return NO;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!directAlternateMethod) {
|
||||||
|
inheritedAlternateMethod = class_getInstanceMethod(self, altSel_);
|
||||||
|
if (!inheritedAlternateMethod) {
|
||||||
|
SetNSError(error_, @"alternate method %@ not found for class %@", NSStringFromSelector(altSel_), [self className]);
|
||||||
|
return NO;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int hoisted_method_count = !directOriginalMethod && !directAlternateMethod ? 2 : 1;
|
||||||
|
struct objc_method_list *hoisted_method_list = malloc(sizeof(struct objc_method_list) + (sizeof(struct objc_method)*(hoisted_method_count-1)));
|
||||||
|
hoisted_method_list->obsolete = NULL; // soothe valgrind - apparently ObjC runtime accesses this value and it shows as uninitialized in valgrind
|
||||||
|
hoisted_method_list->method_count = hoisted_method_count;
|
||||||
|
Method hoisted_method = hoisted_method_list->method_list;
|
||||||
|
|
||||||
|
if (!directOriginalMethod) {
|
||||||
|
bcopy(inheritedOriginalMethod, hoisted_method, sizeof(struct objc_method));
|
||||||
|
directOriginalMethod = hoisted_method++;
|
||||||
|
}
|
||||||
|
if (!directAlternateMethod) {
|
||||||
|
bcopy(inheritedAlternateMethod, hoisted_method, sizeof(struct objc_method));
|
||||||
|
directAlternateMethod = hoisted_method;
|
||||||
|
}
|
||||||
|
class_addMethods(self, hoisted_method_list);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Swizzle.
|
||||||
|
IMP temp = directOriginalMethod->method_imp;
|
||||||
|
directOriginalMethod->method_imp = directAlternateMethod->method_imp;
|
||||||
|
directAlternateMethod->method_imp = temp;
|
||||||
|
|
||||||
|
return YES;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
+ (BOOL)jr_swizzleClassMethod:(SEL)origSel_ withClassMethod:(SEL)altSel_ error:(NSError**)error_ {
|
||||||
|
return [GetClass((id)self) jr_swizzleMethod:origSel_ withMethod:altSel_ error:error_];
|
||||||
|
}
|
||||||
|
|
||||||
|
@end
|
|
@ -8,12 +8,14 @@
|
||||||
|
|
||||||
## 截图
|
## 截图
|
||||||
|
|
||||||

|

|
||||||
|

|
||||||
|
|
||||||
## 功能
|
## 功能
|
||||||
|
|
||||||
- 阻止消息撤回
|
- 阻止消息撤回
|
||||||
- 客户端无限多开
|
- 客户端无限多开
|
||||||
|
- 右键 Dock Icon 登录新的微信账号
|
||||||
- 命令行执行:`open -n /Applications/WeChat.app`
|
- 命令行执行:`open -n /Applications/WeChat.app`
|
||||||
|
|
||||||
## 使用
|
## 使用
|
||||||
|
@ -33,6 +35,7 @@
|
||||||
|
|
||||||
## 依赖
|
## 依赖
|
||||||
|
|
||||||
|
- [JRSwizzle](https://github.com/rentzsch/jrswizzle)
|
||||||
- [insert_dylib](https://github.com/Tyilo/insert_dylib)
|
- [insert_dylib](https://github.com/Tyilo/insert_dylib)
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
|
@ -8,17 +8,19 @@ A dynamic library tweak for WeChat macOS.
|
||||||
|
|
||||||
## Screenshot
|
## Screenshot
|
||||||
|
|
||||||

|

|
||||||
|

|
||||||
|
|
||||||
## Feature
|
## Feature
|
||||||
|
|
||||||
- Prevent message revoked
|
- Prevent message revoked
|
||||||
- Multiple WeChat Instance
|
- Multiple WeChat Instance
|
||||||
|
- Right click on the Dock icon to login another WeChat account
|
||||||
- Run command: `open -n /Applications/WeChat.app`
|
- Run command: `open -n /Applications/WeChat.app`
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
- `sudo make install` Inject the dylib to `WeChat` by [insert_dylib](https://github.com/Tyilo/insert_dylib)
|
- `sudo make install` Inject the dylib to `WeChat`
|
||||||
- `sudo make uninstall` Uninstall the injection
|
- `sudo make uninstall` Uninstall the injection
|
||||||
|
|
||||||
## Development
|
## Development
|
||||||
|
@ -33,6 +35,7 @@ Run `xcode-select --install` to install Command Line Tools.
|
||||||
|
|
||||||
## Dependency
|
## Dependency
|
||||||
|
|
||||||
|
- [JRSwizzle](https://github.com/rentzsch/jrswizzle)
|
||||||
- [insert_dylib](https://github.com/Tyilo/insert_dylib)
|
- [insert_dylib](https://github.com/Tyilo/insert_dylib)
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
Before Width: | Height: | Size: 48 KiB After Width: | Height: | Size: 48 KiB |
BIN
Screenshot/0x02.png
Normal file
BIN
Screenshot/0x02.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 174 KiB |
Binary file not shown.
|
@ -2,34 +2,51 @@
|
||||||
#import <objc/runtime.h>
|
#import <objc/runtime.h>
|
||||||
#import <objc/message.h>
|
#import <objc/message.h>
|
||||||
#import <Cocoa/Cocoa.h>
|
#import <Cocoa/Cocoa.h>
|
||||||
|
#import "JRSwizzle.h"
|
||||||
|
|
||||||
// Tweak for no revoke message.
|
@implementation NSObject (WeChatTweak)
|
||||||
__attribute__((constructor(101))) static void noRevokeTweak(void) {
|
|
||||||
Class class = NSClassFromString(@"MessageService");
|
#pragma mark - Constructor
|
||||||
SEL selector = NSSelectorFromString(@"onRevokeMsg:");
|
|
||||||
Method method = class_getInstanceMethod(class, selector);
|
static void __attribute__((constructor)) tweak(void) {
|
||||||
IMP imp = imp_implementationWithBlock(^(id self, NSString *message) {
|
[objc_getClass("MessageService") jr_swizzleMethod:NSSelectorFromString(@"onRevokeMsg:") withMethod:@selector(tweak_OnRevokeMsg:) error:nil];
|
||||||
|
[objc_getClass("CUtility") jr_swizzleClassMethod:NSSelectorFromString(@"HasWechatInstance") withClassMethod:@selector(tweak_HasWechatInstance) error:nil];
|
||||||
|
class_addMethod(objc_getClass("AppDelegate"), @selector(applicationDockMenu:), method_getImplementation(class_getInstanceMethod(objc_getClass("AppDelegate"), @selector(applicationDockMenu:))), "@:@");
|
||||||
|
}
|
||||||
|
|
||||||
|
#pragma mark - No Revoke Message
|
||||||
|
|
||||||
|
- (void)tweak_OnRevokeMsg:(NSString *)message {
|
||||||
NSRange begin = [message rangeOfString:@"<replacemsg><![CDATA["];
|
NSRange begin = [message rangeOfString:@"<replacemsg><![CDATA["];
|
||||||
NSRange end = [message rangeOfString:@"]]></replacemsg>"];
|
NSRange end = [message rangeOfString:@"]]></replacemsg>"];
|
||||||
NSRange subRange = NSMakeRange(begin.location + begin.length,end.location - begin.location - begin.length);
|
NSRange subRange = NSMakeRange(begin.location + begin.length,end.location - begin.location - begin.length);
|
||||||
|
|
||||||
NSString *informativeText = [message substringWithRange:subRange];
|
NSString *informativeText = [message substringWithRange:subRange];
|
||||||
dispatch_async(dispatch_get_main_queue(), ^{
|
dispatch_async(dispatch_get_main_queue(), ^{
|
||||||
NSUserNotification *userNotification = [[NSUserNotification alloc] init];
|
NSUserNotification *userNotification = [[NSUserNotification alloc] init];
|
||||||
userNotification.informativeText = informativeText;
|
userNotification.informativeText = informativeText;
|
||||||
[[NSUserNotificationCenter defaultUserNotificationCenter] deliverNotification:userNotification];
|
[[NSUserNotificationCenter defaultUserNotificationCenter] deliverNotification:userNotification];
|
||||||
});
|
});
|
||||||
});
|
|
||||||
class_replaceMethod(class, selector, imp, method_getTypeEncoding(method));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tweak for multiple instance.
|
#pragma mark - Mutiple Instance
|
||||||
__attribute__((constructor(102))) static void multipleInstanceTweak(void) {
|
|
||||||
Class class = object_getClass(NSClassFromString(@"CUtility"));
|
+ (BOOL)tweak_HasWechatInstance {
|
||||||
SEL selector = NSSelectorFromString(@"HasWechatInstance");
|
return NO;
|
||||||
Method method = class_getInstanceMethod(class, selector);
|
|
||||||
IMP imp = imp_implementationWithBlock(^(id self) {
|
|
||||||
return 0;
|
|
||||||
});
|
|
||||||
class_replaceMethod(class, selector, imp, method_getTypeEncoding(method));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
- (NSMenu *)applicationDockMenu:(NSApplication *)sender {
|
||||||
|
NSMenu *menu = [[objc_getClass("NSMenu") alloc] init];
|
||||||
|
NSMenuItem *menuItem = [[objc_getClass("NSMenuItem") alloc] initWithTitle:@"登录新的微信账号" action:@selector(openNewWeChatInstace:) keyEquivalent:@""];
|
||||||
|
[menu insertItem:menuItem atIndex:0];
|
||||||
|
return menu;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)openNewWeChatInstace:(id)sender {
|
||||||
|
NSString *applicationPath = [[objc_getClass("NSBundle") mainBundle] bundlePath];
|
||||||
|
NSTask *task = [[objc_getClass("NSTask") alloc] init];
|
||||||
|
task.launchPath = @"/usr/bin/open";
|
||||||
|
task.arguments = @[@"-n", applicationPath];
|
||||||
|
[task launch];
|
||||||
|
}
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
Loading…
Reference in New Issue
Block a user