Add dock menu item

This commit is contained in:
Sunnyyoung 2017-04-02 01:30:20 +08:00
parent e1cb4bf7d2
commit 5dd8ca7a40
8 changed files with 198 additions and 28 deletions

13
JRSwizzle.h Normal file
View 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
View 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

View File

@ -8,12 +8,14 @@
## 截图 ## 截图
![](Screenshot/WeChatTweak-macOS.png) ![](Screenshot/0x01.png)
![](Screenshot/0x02.png)
## 功能 ## 功能
- 阻止消息撤回 - 阻止消息撤回
- 客户端无限多开 - 客户端无限多开
- 右键 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

View File

@ -8,17 +8,19 @@ A dynamic library tweak for WeChat macOS.
## Screenshot ## Screenshot
![](Screenshot/WeChatTweak-macOS.png) ![](Screenshot/0x01.png)
![](Screenshot/0x02.png)
## 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

View File

Before

Width:  |  Height:  |  Size: 48 KiB

After

Width:  |  Height:  |  Size: 48 KiB

BIN
Screenshot/0x02.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 174 KiB

Binary file not shown.

View File

@ -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