├── .gitignore ├── LICENSE ├── README.md ├── cosyTabs.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ └── contents.xcworkspacedata └── xcshareddata │ ├── xcdebugger │ └── Breakpoints_v2.xcbkptlist │ └── xcschemes │ ├── cosyTabs Safari Debug (SIP disabled).xcscheme │ ├── cosyTabs.xcscheme │ └── xcschememanagement.plist └── cosyTabs ├── cosyTabs-Info.plist ├── cosyTabs-Prefix.pch ├── cosyTabs.h ├── cosyTabs.m └── en.lproj └── InfoPlist.strings /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by http://www.gitignore.io 2 | 3 | ### Objective-C ### 4 | # Xcode 5 | # 6 | build/ 7 | *.pbxuser 8 | !default.pbxuser 9 | *.mode1v3 10 | !default.mode1v3 11 | *.mode2v3 12 | !default.mode2v3 13 | *.perspectivev3 14 | !default.perspectivev3 15 | xcuserdata 16 | *.xccheckout 17 | *.moved-aside 18 | DerivedData 19 | *.hmap 20 | *.ipa 21 | *.xcuserstate 22 | 23 | # CocoaPods 24 | # 25 | # We recommend against adding the Pods directory to your .gitignore. However 26 | # you should judge for yourself, the pros and cons are mentioned at: 27 | # http://guides.cocoapods.org/using/using-cocoapods.html#should-i-ignore-the-pods-directory-in-source-control 28 | # 29 | # Pods/ 30 | 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (C) 2012 inket 2 | 3 | This program is free software: you can redistribute it and/or modify 4 | it under the terms of the GNU General Public License as published by 5 | the Free Software Foundation, either version 3 of the License, or 6 | (at your option) any later version. 7 | 8 | This program is distributed in the hope that it will be useful, 9 | but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | GNU General Public License for more details. 12 | 13 | See http://www.gnu.org/licenses/gpl-3.0.txt for details. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Safari 5 tabs on Safari 6+. Cosy. 2 | 3 | Tested on Safari 6, 7, 8, 9, 10. 4 | 5 | Safari 10 was tested under macOS Sierra only. 6 | 7 | If you're using an old version of Safari/macOS and you're having problems with the latest version, try the older ones. 8 | 9 | ### Preview 10 | 11 | ![After](http://i.imgur.com/sTX1W7j.png) 12 | 13 | ## How to Install 14 | 15 | ### macOS Sierra (10.12) 16 | 17 | Disable System Integrity Protection, and install the latest version of [mySIMBL](https://github.com/w0lfschild/app_updates/tree/master/mySIMBL). 18 | 19 | After that, [download cosyTabs](https://github.com/inket/cosyTabs/releases), extract it, and place `cosyTabs.bundle` in `/Library/Application Support/SIMBL/Plugins/` 20 | 21 | Run Safari! 22 | 23 | (Feel free to turn SIP back on after this installation, SIMBL will continue to work.) 24 | 25 | ### El Capitan (OS X 10.11) 26 | 27 | EasySIMBL does not work anymore because of the [System Integrity Protection](https://en.wikipedia.org/wiki/System_Integrity_Protection) introduced in El Capitan. 28 | 29 | It has been confirmed that the old SIMBL-0.9.9 works, but only after disabling SIP. 30 | 31 | Here's the guide for installing SIMBL on El Capitan: https://github.com/norio-nomura/EasySIMBL/issues/26#issuecomment-117028426 32 | 33 | After that, [download cosyTabs](https://github.com/inket/cosyTabs/releases), extract it, and place `cosyTabs.bundle` in `/Library/Application Support/SIMBL/Plugins/` 34 | 35 | ##### For advanced users 36 | 37 | Obviously, there is no need to disable SIP (and reboot 4+ times). If you can extract SIMBL from the .pkg, boot into Recovery or any other OS, then you can simply copy the SIMBL's files to the correct paths. 38 | 39 | ### OS X 10.10.4+ (Updated Yosemite) 40 | 41 | 1. Download and install [SIMBL-0.9.9](http://www.culater.net/software/SIMBL/SIMBL.php) 42 | 2. Download cosyTabs here: [Releases](https://github.com/inket/cosyTabs/releases) 43 | 3. Extract it, and place `cosyTabs.bundle` in `/Library/Application Support/SIMBL/Plugins/` 44 | 45 | ### OS X 10.7 → 10.10.3 46 | 47 | 1. Download and install [EasySIMBL](https://github.com/norio-nomura/EasySIMBL/#how-to-install) 48 | 2. Download cosyTabs here: [Releases](https://github.com/inket/cosyTabs/releases) 49 | 3. Extract & Install cosyTabs: 50 | - Open EasySIMBL and drag & drop *cosyTabs.bundle* into the list. 51 | 52 | ### Note about Safari Technology Preview 53 | 54 | I could not get SIMBL to inject into Safari Technology Preview (STP), so STP is unsupported at the moment. 55 | 56 | Please create an issue if you have a clue on how to fix this error: 57 | 58 | ``` 59 | Safari Technology Preview[3466:43691] Error loading /System/Library/ScriptingAdditions/SIMBL.osax/Contents/MacOS/SIMBL: dlopen(/System/Library/ScriptingAdditions/SIMBL.osax/Contents/MacOS/SIMBL, 262): no suitable image found. Did find: 60 | /System/Library/ScriptingAdditions/SIMBL.osax/Contents/MacOS/SIMBL: mmap() error 1 at address=0x10AF27000, size=0x00004000 segment=__TEXT in Segment::map() mapping /System/Library/ScriptingAdditions/SIMBL.osax/Contents/MacOS/SIMBL 61 | Safari Technology Preview: OpenScripting.framework - can't find entry point InjectEventHandler in scripting addition /System/Library/ScriptingAdditions/SIMBL.osax. 62 | ``` 63 | 64 | ### License 65 | This program is licensed under GNU GPL v3.0 (see LICENSE) 66 | -------------------------------------------------------------------------------- /cosyTabs.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | B26DE59415B7871900EDFD3F /* CoreFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B26DE59315B7871900EDFD3F /* CoreFoundation.framework */; }; 11 | B26DE59A15B7871900EDFD3F /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = B26DE59815B7871900EDFD3F /* InfoPlist.strings */; }; 12 | B26DE5A315B78DC500EDFD3F /* cosyTabs.m in Sources */ = {isa = PBXBuildFile; fileRef = B26DE5A215B78DC500EDFD3F /* cosyTabs.m */; }; 13 | /* End PBXBuildFile section */ 14 | 15 | /* Begin PBXFileReference section */ 16 | B26DE59015B7871900EDFD3F /* cosyTabs.bundle */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = cosyTabs.bundle; sourceTree = BUILT_PRODUCTS_DIR; }; 17 | B26DE59315B7871900EDFD3F /* CoreFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreFoundation.framework; path = System/Library/Frameworks/CoreFoundation.framework; sourceTree = SDKROOT; }; 18 | B26DE59715B7871900EDFD3F /* cosyTabs-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "cosyTabs-Info.plist"; sourceTree = ""; }; 19 | B26DE59915B7871900EDFD3F /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; 20 | B26DE59B15B7871900EDFD3F /* cosyTabs-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "cosyTabs-Prefix.pch"; sourceTree = ""; }; 21 | B26DE5A115B78DC500EDFD3F /* cosyTabs.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = cosyTabs.h; sourceTree = ""; }; 22 | B26DE5A215B78DC500EDFD3F /* cosyTabs.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = cosyTabs.m; sourceTree = ""; }; 23 | /* End PBXFileReference section */ 24 | 25 | /* Begin PBXFrameworksBuildPhase section */ 26 | B26DE58D15B7871900EDFD3F /* Frameworks */ = { 27 | isa = PBXFrameworksBuildPhase; 28 | buildActionMask = 2147483647; 29 | files = ( 30 | B26DE59415B7871900EDFD3F /* CoreFoundation.framework in Frameworks */, 31 | ); 32 | runOnlyForDeploymentPostprocessing = 0; 33 | }; 34 | /* End PBXFrameworksBuildPhase section */ 35 | 36 | /* Begin PBXGroup section */ 37 | B26DE58515B7871900EDFD3F = { 38 | isa = PBXGroup; 39 | children = ( 40 | B26DE59515B7871900EDFD3F /* cosyTabs */, 41 | B26DE59215B7871900EDFD3F /* Frameworks */, 42 | B26DE59115B7871900EDFD3F /* Products */, 43 | ); 44 | sourceTree = ""; 45 | }; 46 | B26DE59115B7871900EDFD3F /* Products */ = { 47 | isa = PBXGroup; 48 | children = ( 49 | B26DE59015B7871900EDFD3F /* cosyTabs.bundle */, 50 | ); 51 | name = Products; 52 | sourceTree = ""; 53 | }; 54 | B26DE59215B7871900EDFD3F /* Frameworks */ = { 55 | isa = PBXGroup; 56 | children = ( 57 | B26DE59315B7871900EDFD3F /* CoreFoundation.framework */, 58 | ); 59 | name = Frameworks; 60 | sourceTree = ""; 61 | }; 62 | B26DE59515B7871900EDFD3F /* cosyTabs */ = { 63 | isa = PBXGroup; 64 | children = ( 65 | B26DE59615B7871900EDFD3F /* Supporting Files */, 66 | B26DE5A115B78DC500EDFD3F /* cosyTabs.h */, 67 | B26DE5A215B78DC500EDFD3F /* cosyTabs.m */, 68 | ); 69 | path = cosyTabs; 70 | sourceTree = ""; 71 | }; 72 | B26DE59615B7871900EDFD3F /* Supporting Files */ = { 73 | isa = PBXGroup; 74 | children = ( 75 | B26DE59715B7871900EDFD3F /* cosyTabs-Info.plist */, 76 | B26DE59815B7871900EDFD3F /* InfoPlist.strings */, 77 | B26DE59B15B7871900EDFD3F /* cosyTabs-Prefix.pch */, 78 | ); 79 | name = "Supporting Files"; 80 | sourceTree = ""; 81 | }; 82 | /* End PBXGroup section */ 83 | 84 | /* Begin PBXNativeTarget section */ 85 | B26DE58F15B7871900EDFD3F /* cosyTabs */ = { 86 | isa = PBXNativeTarget; 87 | buildConfigurationList = B26DE59E15B7871900EDFD3F /* Build configuration list for PBXNativeTarget "cosyTabs" */; 88 | buildPhases = ( 89 | B26DE58C15B7871900EDFD3F /* Sources */, 90 | B26DE58D15B7871900EDFD3F /* Frameworks */, 91 | B26DE58E15B7871900EDFD3F /* Resources */, 92 | B2D1A81C196E6DD100000A1A /* ShellScript */, 93 | ); 94 | buildRules = ( 95 | ); 96 | dependencies = ( 97 | ); 98 | name = cosyTabs; 99 | productName = cosyTabs; 100 | productReference = B26DE59015B7871900EDFD3F /* cosyTabs.bundle */; 101 | productType = "com.apple.product-type.bundle"; 102 | }; 103 | /* End PBXNativeTarget section */ 104 | 105 | /* Begin PBXProject section */ 106 | B26DE58715B7871900EDFD3F /* Project object */ = { 107 | isa = PBXProject; 108 | attributes = { 109 | LastUpgradeCheck = 0700; 110 | ORGANIZATIONNAME = inket; 111 | }; 112 | buildConfigurationList = B26DE58A15B7871900EDFD3F /* Build configuration list for PBXProject "cosyTabs" */; 113 | compatibilityVersion = "Xcode 3.2"; 114 | developmentRegion = English; 115 | hasScannedForEncodings = 0; 116 | knownRegions = ( 117 | en, 118 | ); 119 | mainGroup = B26DE58515B7871900EDFD3F; 120 | productRefGroup = B26DE59115B7871900EDFD3F /* Products */; 121 | projectDirPath = ""; 122 | projectRoot = ""; 123 | targets = ( 124 | B26DE58F15B7871900EDFD3F /* cosyTabs */, 125 | ); 126 | }; 127 | /* End PBXProject section */ 128 | 129 | /* Begin PBXResourcesBuildPhase section */ 130 | B26DE58E15B7871900EDFD3F /* Resources */ = { 131 | isa = PBXResourcesBuildPhase; 132 | buildActionMask = 2147483647; 133 | files = ( 134 | B26DE59A15B7871900EDFD3F /* InfoPlist.strings in Resources */, 135 | ); 136 | runOnlyForDeploymentPostprocessing = 0; 137 | }; 138 | /* End PBXResourcesBuildPhase section */ 139 | 140 | /* Begin PBXShellScriptBuildPhase section */ 141 | B2D1A81C196E6DD100000A1A /* ShellScript */ = { 142 | isa = PBXShellScriptBuildPhase; 143 | buildActionMask = 2147483647; 144 | files = ( 145 | ); 146 | inputPaths = ( 147 | ); 148 | outputPaths = ( 149 | ); 150 | runOnlyForDeploymentPostprocessing = 0; 151 | shellPath = /bin/sh; 152 | shellScript = "SIMBL_DIR=\"/Library/Application Support/SIMBL/Plugins\"\n\nif [ ! -d \"$SIMBL_DIR\" ]; then\necho \"The directory '$SIMBL_DIR' does not exist.\"\necho \"Run \\`sudo mkdir -p '$SIMBL_DIR'; sudo chown $USER '$SIMBL_DIR'\\` to create it.\"\nexit 1\nfi\n\n# clean up any previous products/symbolic links in the SIMBL Plugins folder\nif [ -a \"$SIMBL_DIR/${FULL_PRODUCT_NAME}\" ]; then\nrm -Rf \"$SIMBL_DIR/${FULL_PRODUCT_NAME}\"\nfi\n\n# Depending on the build configuration, either copy or link to the most recent product\nif [ \"${CONFIGURATION}\" == \"Debug\" ]; then\n# if we're debugging, add a symbolic link to the plug-in\nln -sf \"${TARGET_BUILD_DIR}/${FULL_PRODUCT_NAME}\" \\\n\"$SIMBL_DIR/${FULL_PRODUCT_NAME}\"\nelif [ \"${CONFIGURATION}\" == \"Release\" ]; then\n# if we're compiling for release, just copy the plugin to the SIMBL Plugins folder\ncp -Rfv \"${TARGET_BUILD_DIR}/${FULL_PRODUCT_NAME}\" \\\n\"$SIMBL_DIR/${FULL_PRODUCT_NAME}\"\nfi"; 153 | }; 154 | /* End PBXShellScriptBuildPhase section */ 155 | 156 | /* Begin PBXSourcesBuildPhase section */ 157 | B26DE58C15B7871900EDFD3F /* Sources */ = { 158 | isa = PBXSourcesBuildPhase; 159 | buildActionMask = 2147483647; 160 | files = ( 161 | B26DE5A315B78DC500EDFD3F /* cosyTabs.m in Sources */, 162 | ); 163 | runOnlyForDeploymentPostprocessing = 0; 164 | }; 165 | /* End PBXSourcesBuildPhase section */ 166 | 167 | /* Begin PBXVariantGroup section */ 168 | B26DE59815B7871900EDFD3F /* InfoPlist.strings */ = { 169 | isa = PBXVariantGroup; 170 | children = ( 171 | B26DE59915B7871900EDFD3F /* en */, 172 | ); 173 | name = InfoPlist.strings; 174 | sourceTree = ""; 175 | }; 176 | /* End PBXVariantGroup section */ 177 | 178 | /* Begin XCBuildConfiguration section */ 179 | B26DE59C15B7871900EDFD3F /* Debug */ = { 180 | isa = XCBuildConfiguration; 181 | buildSettings = { 182 | ALWAYS_SEARCH_USER_PATHS = NO; 183 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 184 | CLANG_ENABLE_OBJC_ARC = YES; 185 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 186 | COPY_PHASE_STRIP = NO; 187 | ENABLE_TESTABILITY = YES; 188 | GCC_C_LANGUAGE_STANDARD = gnu99; 189 | GCC_DYNAMIC_NO_PIC = NO; 190 | GCC_ENABLE_OBJC_EXCEPTIONS = YES; 191 | GCC_OPTIMIZATION_LEVEL = 0; 192 | GCC_PREPROCESSOR_DEFINITIONS = ( 193 | "DEBUG=1", 194 | "$(inherited)", 195 | ); 196 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 197 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 198 | GCC_WARN_ABOUT_RETURN_TYPE = YES; 199 | GCC_WARN_UNINITIALIZED_AUTOS = YES; 200 | GCC_WARN_UNUSED_VARIABLE = YES; 201 | MACOSX_DEPLOYMENT_TARGET = 10.7; 202 | ONLY_ACTIVE_ARCH = YES; 203 | SDKROOT = macosx; 204 | }; 205 | name = Debug; 206 | }; 207 | B26DE59D15B7871900EDFD3F /* Release */ = { 208 | isa = XCBuildConfiguration; 209 | buildSettings = { 210 | ALWAYS_SEARCH_USER_PATHS = NO; 211 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 212 | CLANG_ENABLE_OBJC_ARC = YES; 213 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 214 | COPY_PHASE_STRIP = YES; 215 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 216 | GCC_C_LANGUAGE_STANDARD = gnu99; 217 | GCC_ENABLE_OBJC_EXCEPTIONS = YES; 218 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 219 | GCC_WARN_ABOUT_RETURN_TYPE = YES; 220 | GCC_WARN_UNINITIALIZED_AUTOS = YES; 221 | GCC_WARN_UNUSED_VARIABLE = YES; 222 | MACOSX_DEPLOYMENT_TARGET = 10.7; 223 | SDKROOT = macosx; 224 | }; 225 | name = Release; 226 | }; 227 | B26DE59F15B7871900EDFD3F /* Debug */ = { 228 | isa = XCBuildConfiguration; 229 | buildSettings = { 230 | COMBINE_HIDPI_IMAGES = YES; 231 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 232 | GCC_PREFIX_HEADER = "cosyTabs/cosyTabs-Prefix.pch"; 233 | INFOPLIST_FILE = "cosyTabs/cosyTabs-Info.plist"; 234 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Bundles"; 235 | PRODUCT_BUNDLE_IDENTIFIER = "me.inket.${PRODUCT_NAME:rfc1034identifier}"; 236 | PRODUCT_NAME = "$(TARGET_NAME)"; 237 | WRAPPER_EXTENSION = bundle; 238 | }; 239 | name = Debug; 240 | }; 241 | B26DE5A015B7871900EDFD3F /* Release */ = { 242 | isa = XCBuildConfiguration; 243 | buildSettings = { 244 | COMBINE_HIDPI_IMAGES = YES; 245 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 246 | GCC_PREFIX_HEADER = "cosyTabs/cosyTabs-Prefix.pch"; 247 | INFOPLIST_FILE = "cosyTabs/cosyTabs-Info.plist"; 248 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Bundles"; 249 | PRODUCT_BUNDLE_IDENTIFIER = "me.inket.${PRODUCT_NAME:rfc1034identifier}"; 250 | PRODUCT_NAME = "$(TARGET_NAME)"; 251 | WRAPPER_EXTENSION = bundle; 252 | }; 253 | name = Release; 254 | }; 255 | /* End XCBuildConfiguration section */ 256 | 257 | /* Begin XCConfigurationList section */ 258 | B26DE58A15B7871900EDFD3F /* Build configuration list for PBXProject "cosyTabs" */ = { 259 | isa = XCConfigurationList; 260 | buildConfigurations = ( 261 | B26DE59C15B7871900EDFD3F /* Debug */, 262 | B26DE59D15B7871900EDFD3F /* Release */, 263 | ); 264 | defaultConfigurationIsVisible = 0; 265 | defaultConfigurationName = Release; 266 | }; 267 | B26DE59E15B7871900EDFD3F /* Build configuration list for PBXNativeTarget "cosyTabs" */ = { 268 | isa = XCConfigurationList; 269 | buildConfigurations = ( 270 | B26DE59F15B7871900EDFD3F /* Debug */, 271 | B26DE5A015B7871900EDFD3F /* Release */, 272 | ); 273 | defaultConfigurationIsVisible = 0; 274 | defaultConfigurationName = Release; 275 | }; 276 | /* End XCConfigurationList section */ 277 | }; 278 | rootObject = B26DE58715B7871900EDFD3F /* Project object */; 279 | } 280 | -------------------------------------------------------------------------------- /cosyTabs.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /cosyTabs.xcodeproj/xcshareddata/xcdebugger/Breakpoints_v2.xcbkptlist: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | -------------------------------------------------------------------------------- /cosyTabs.xcodeproj/xcshareddata/xcschemes/cosyTabs Safari Debug (SIP disabled).xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 34 | 35 | 46 | 50 | 51 | 52 | 58 | 59 | 60 | 61 | 62 | 63 | 69 | 70 | 76 | 77 | 78 | 79 | 81 | 82 | 85 | 86 | 87 | -------------------------------------------------------------------------------- /cosyTabs.xcodeproj/xcshareddata/xcschemes/cosyTabs.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 34 | 35 | 45 | 46 | 52 | 53 | 54 | 55 | 56 | 57 | 63 | 64 | 66 | 67 | 70 | 71 | 72 | -------------------------------------------------------------------------------- /cosyTabs.xcodeproj/xcshareddata/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | cosyTabs Safari Debug (SIP disabled).xcscheme 8 | 9 | orderHint 10 | 1 11 | 12 | cosyTabs.xcscheme 13 | 14 | orderHint 15 | 0 16 | 17 | 18 | SuppressBuildableAutocreation 19 | 20 | B26DE58F15B7871900EDFD3F 21 | 22 | primary 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /cosyTabs/cosyTabs-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | English 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIconFile 10 | 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | ${PRODUCT_NAME} 17 | CFBundlePackageType 18 | BNDL 19 | CFBundleShortVersionString 20 | 1.4.4 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | 1.4.4 25 | NSPrincipalClass 26 | cosyTabs 27 | SIMBLTargetApplications 28 | 29 | 30 | BundleIdentifier 31 | com.apple.Safari 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /cosyTabs/cosyTabs-Prefix.pch: -------------------------------------------------------------------------------- 1 | // 2 | // Prefix header for all source files of the 'cosyTabs' target in the 'cosyTabs' project 3 | // 4 | 5 | #ifdef __OBJC__ 6 | #import 7 | #endif 8 | -------------------------------------------------------------------------------- /cosyTabs/cosyTabs.h: -------------------------------------------------------------------------------- 1 | // 2 | // cosyTabs.h 3 | // cosyTabs 4 | // 5 | // Created by inket on 19/07/2012. 6 | // Copyright (c) 2012 inket. Licensed under GNU GPL v3.0. See LICENSE for details. 7 | // 8 | 9 | #import 10 | #import 11 | 12 | @interface cosyTabs : NSObject 13 | 14 | @end -------------------------------------------------------------------------------- /cosyTabs/cosyTabs.m: -------------------------------------------------------------------------------- 1 | // 2 | // cosyTabs.m 3 | // cosyTabs 4 | // 5 | // Created by inket on 19/07/2012. 6 | // Copyright (c) 2012 inket. Licensed under GNU GPL v3.0. See LICENSE for details. 7 | // 8 | 9 | #import "cosyTabs.h" 10 | 11 | #define MAX_TAB_WIDTH 250 // Maximum tab width in Safari 5 is 250px, amirite? 12 | 13 | // Change these values to control the tab title margins 14 | #define TAB_TITLE_LEFT_MARGIN NSIntegerMax 15 | #define TAB_TITLE_RIGHT_MARGIN NSIntegerMax 16 | 17 | static cosyTabs* plugin = nil; 18 | 19 | @implementation NSObject (cosyTabs) 20 | 21 | #pragma mark - Controlling tab width 22 | 23 | #pragma mark Safari 9+ 24 | 25 | - (unsigned long long)new_visibleTabIndexAtPoint:(struct CGPoint)point 26 | stackingRegion:(unsigned long long *)arg2 27 | ignorePointsOutsideOfLayoutBounds:(BOOL)arg3 { 28 | unsigned long long originalResult = [self new_visibleTabIndexAtPoint:point 29 | stackingRegion:arg2 30 | ignorePointsOutsideOfLayoutBounds:arg3]; 31 | 32 | if (originalResult == 0) 33 | { 34 | BOOL pinnedTabsNonExistent = (NSInteger)[self performSelector:@selector(_numberOfPinnedTabsForLayout)] == 0; 35 | BOOL singleTab = (NSInteger)[self performSelector:@selector(_numberOfTabsForLayout)] == 1; 36 | 37 | if (pinnedTabsNonExistent && singleTab && point.x > MAX_TAB_WIDTH) 38 | { 39 | return LONG_LONG_MAX; // ULONG_LONG_MAX -> Crash! 40 | } 41 | } 42 | 43 | return originalResult; 44 | } 45 | 46 | - (unsigned long long)yosemite_visibleTabIndexAtPoint:(struct CGPoint)point 47 | stackingRegion:(unsigned long long *)arg2 48 | ignorePointsOutsideOfLayoutBounds:(BOOL)arg3 { 49 | unsigned long long originalResult = [self yosemite_visibleTabIndexAtPoint:point 50 | stackingRegion:arg2 51 | ignorePointsOutsideOfLayoutBounds:arg3]; 52 | 53 | BOOL numberOfTabs = (NSInteger)[self performSelector:@selector(_numberOfTabsForLayout)]; 54 | BOOL clickOnTabBarBackground = point.x > MAX_TAB_WIDTH * numberOfTabs; 55 | 56 | if ((originalResult == 0 || originalResult > numberOfTabs - 1) && clickOnTabBarBackground) 57 | { 58 | return LONG_LONG_MAX; // ULONG_LONG_MAX -> Crash! 59 | } 60 | 61 | return originalResult; 62 | } 63 | 64 | - (void)new_setButtonWidthForTitleLayout:(double)arg1 animated:(BOOL)arg2 { 65 | if (arg1 > MAX_TAB_WIDTH) arg1 = MAX_TAB_WIDTH; 66 | 67 | [self new_setButtonWidthForTitleLayout:arg1 animated:arg2]; 68 | } 69 | 70 | #pragma mark Safari 8+ 71 | 72 | - (void)new_trackMouseEventsForEvent:(NSEvent*)datEvent onTabAtIndex:(NSInteger)tabIndex { 73 | // Capture click events in the tab bar 74 | 75 | if ([datEvent clickCount] == 2) 76 | { 77 | if (tabIndex == 0) 78 | { 79 | NSView* v = (NSView*)self; 80 | 81 | for (NSView* view in [v subviews]) { 82 | if ([view isKindOfClass:[NSClassFromString(@"ScrollableTabBarViewButton") class]]) 83 | { 84 | NSView* previousView = [[v subviews] objectAtIndex:[[v subviews] indexOfObject:view]-1]; 85 | NSPoint windowLocation = [datEvent locationInWindow]; 86 | NSPoint viewLocation = [previousView convertPoint:windowLocation fromView:nil]; 87 | if (NSPointInRect(viewLocation, [previousView bounds])) 88 | { 89 | NSButton* newTabButton = (NSButton*)view; 90 | [[NSClassFromString(@"NSApplication") sharedApplication] sendAction:newTabButton.action to:newTabButton.target from:newTabButton]; 91 | return; 92 | } 93 | 94 | break; 95 | } 96 | } 97 | } 98 | else 99 | { 100 | @try { 101 | [self new_trackMouseEventsForEvent:datEvent onTabAtIndex:tabIndex]; 102 | } 103 | @catch (NSException *exception) { 104 | if ([[exception name] isEqualToString:NSRangeException]) 105 | { 106 | NSView* v = (NSView*)self; 107 | 108 | for (NSView* view in [v subviews]) { 109 | if ([view isKindOfClass:[NSClassFromString(@"ScrollableTabBarViewButton") class]]) 110 | { 111 | NSButton* newTabButton = (NSButton*)view; 112 | [[NSClassFromString(@"NSApplication") sharedApplication] sendAction:newTabButton.action to:newTabButton.target from:newTabButton]; 113 | break; 114 | } 115 | } 116 | } 117 | } 118 | } 119 | } 120 | 121 | @try { 122 | [self new_trackMouseEventsForEvent:datEvent onTabAtIndex:tabIndex]; 123 | } 124 | @catch (NSException *exception) { 125 | if (![[exception name] isEqualToString:NSRangeException]) 126 | { 127 | @throw exception; 128 | } 129 | } 130 | } 131 | 132 | - (CGFloat)new_buttonWidthForNumberOfButtons:(NSInteger)n inWidth:(CGFloat)w remainderWidth:(CGFloat)rw { 133 | NSInteger x = [self new_buttonWidthForNumberOfButtons:n inWidth:w remainderWidth:rw]; 134 | if (x > MAX_TAB_WIDTH) return MAX_TAB_WIDTH; 135 | return x; 136 | } 137 | 138 | - (BOOL)new_shouldLayOutButtonsToAlignWithWindowCenter { 139 | return NO; 140 | } 141 | 142 | #pragma mark Safari 7 <= 143 | 144 | - (double)new_availableWidthForButtonsWhenUnclipped { 145 | unsigned long long numberOfTabs = (unsigned long long)[self performSelector:@selector(numberOfTabs)]; 146 | 147 | double defaultAvailableWidth = [self new_availableWidthForButtonsWhenUnclipped]; 148 | double customAvailableWidth = (double)(MAX_TAB_WIDTH*numberOfTabs); 149 | 150 | if (defaultAvailableWidth <= customAvailableWidth) 151 | return defaultAvailableWidth; 152 | 153 | return customAvailableWidth; 154 | } 155 | 156 | - (void)new_tabViewDidChangeNumberOfTabViewItems:(id)arg1 { 157 | [self new_tabViewDidChangeNumberOfTabViewItems:arg1]; 158 | 159 | [self performSelector:@selector(refreshButtons)]; 160 | } 161 | 162 | #pragma mark - Controlling tab title margins 163 | 164 | #pragma mark Safari 10+ 165 | 166 | - (void)new_setTitleTextFieldCenterOffset:(double)arg1 animated:(BOOL)arg2 { 167 | [self new_setTitleTextFieldCenterOffset:0 animated:arg2]; 168 | } 169 | 170 | #pragma mark Safari 7+ 171 | 172 | - (void)new__updateTitleTextFieldFrame { 173 | [self new__updateTitleTextFieldFrame]; 174 | 175 | NSTextField* titleTextField = [self valueForKey:@"titleTextField"]; 176 | 177 | if ([[titleTextField attributedStringValue] size].width+15 > MAX_TAB_WIDTH) 178 | { 179 | // Title is going to be truncated, let's just make it as wide as possible while using the custom margins 180 | 181 | NSRect currentFrame = [titleTextField frame]; 182 | 183 | // Selected tabs are drawn wider than regular ones; Take that into account 184 | CGFloat addedRightMargin = [(NSButton*)self state] == NSOnState ? 4 : 0; 185 | CGFloat addedLeftMargin = [(NSButton*)self state] == NSOnState ? 6 : 0; 186 | 187 | // Get the needed margin values 188 | CGFloat leftMargin = TAB_TITLE_LEFT_MARGIN != NSIntegerMax ? TAB_TITLE_LEFT_MARGIN + addedLeftMargin : currentFrame.origin.x; 189 | CGFloat originalRightMargin = [[titleTextField superview] frame].size.width - (currentFrame.size.width - currentFrame.origin.x); 190 | CGFloat rightMargin = TAB_TITLE_RIGHT_MARGIN != NSIntegerMax ? TAB_TITLE_RIGHT_MARGIN + addedRightMargin : originalRightMargin; 191 | 192 | // Resize the textfield 193 | CGFloat newWidth = [[titleTextField superview] frame].size.width - leftMargin - rightMargin; 194 | [titleTextField setFrame:NSMakeRect(leftMargin, currentFrame.origin.y, newWidth, currentFrame.size.height)]; 195 | } 196 | 197 | 198 | // Move the Close button to the top of the hierarchy so it can be clicked 199 | NSButton* closeButton = [self valueForKey:@"closeButton"]; 200 | NSButtonCell* tabCell = [(NSButton*)self cell]; 201 | [(NSButton*)self addSubview:closeButton positioned:NSWindowAbove relativeTo:(NSView*)tabCell]; 202 | } 203 | 204 | - (void)new_setState:(id)arg1 { 205 | [self new_setState:arg1]; 206 | 207 | if ([self respondsToSelector:@selector(_updateTitleTextFieldFrame)]) 208 | [self performSelector:@selector(_updateTitleTextFieldFrame) withObject:nil]; 209 | } 210 | 211 | - (void)new_setTitle:(id)arg1 { 212 | [self new_setTitle:arg1]; 213 | 214 | if ([self respondsToSelector:@selector(_updateTitleTextFieldFrame)]) 215 | [self performSelector:@selector(_updateTitleTextFieldFrame) withObject:nil]; 216 | } 217 | 218 | @end 219 | 220 | 221 | @implementation cosyTabs 222 | 223 | #pragma mark SIMBL methods and loading 224 | 225 | + (cosyTabs*)sharedInstance { 226 | if (plugin == nil) 227 | plugin = [[cosyTabs alloc] init]; 228 | 229 | return plugin; 230 | } 231 | 232 | + (void)load { 233 | [[cosyTabs sharedInstance] loadPlugin]; 234 | NSLog(@"cosyTabs loaded."); 235 | } 236 | 237 | - (void)loadPlugin { 238 | BOOL elCapitanOrLater = [cosyTabs isElCapitanOrLater]; 239 | BOOL safari10OrLater = [cosyTabs isSafari10OrLater]; 240 | BOOL safari9OrLater = [cosyTabs isSafari9OrLater]; 241 | BOOL safari8 = [cosyTabs isSafari8]; 242 | 243 | if (safari8 || safari9OrLater) 244 | { 245 | Class class = NSClassFromString(safari9OrLater ? @"TabBarView" : @"ScrollableTabBarView"); 246 | 247 | Method new = class_getInstanceMethod(class, @selector(new_buttonWidthForNumberOfButtons:inWidth:remainderWidth:)); 248 | Method old = class_getInstanceMethod(class, @selector(_buttonWidthForNumberOfButtons:inWidth:remainderWidth:)); 249 | method_exchangeImplementations(new, old); 250 | 251 | new = class_getInstanceMethod(class, @selector(new_shouldLayOutButtonsToAlignWithWindowCenter)); 252 | old = class_getInstanceMethod(class, @selector(_shouldLayOutButtonsToAlignWithWindowCenter)); 253 | method_exchangeImplementations(new, old); 254 | 255 | if (safari9OrLater) 256 | { 257 | if (elCapitanOrLater) 258 | { 259 | new = class_getInstanceMethod(class, @selector(new_visibleTabIndexAtPoint:stackingRegion:ignorePointsOutsideOfLayoutBounds:)); 260 | old = class_getInstanceMethod(class, @selector(_visibleTabIndexAtPoint:stackingRegion:ignorePointsOutsideOfLayoutBounds:)); 261 | method_exchangeImplementations(new, old); 262 | } 263 | else 264 | { 265 | new = class_getInstanceMethod(class, @selector(yosemite_visibleTabIndexAtPoint:stackingRegion:ignorePointsOutsideOfLayoutBounds:)); 266 | old = class_getInstanceMethod(class, @selector(_visibleTabIndexAtPoint:stackingRegion:ignorePointsOutsideOfLayoutBounds:)); 267 | method_exchangeImplementations(new, old); 268 | } 269 | 270 | class = NSClassFromString(@"TabButton"); 271 | new = class_getInstanceMethod(class, @selector(new_setButtonWidthForTitleLayout:animated:)); 272 | old = class_getInstanceMethod(class, @selector(setButtonWidthForTitleLayout:animated:)); 273 | method_exchangeImplementations(new, old); 274 | 275 | if (safari10OrLater) 276 | { 277 | new = class_getInstanceMethod(class, @selector(new_setTitleTextFieldCenterOffset:animated:)); 278 | old = class_getInstanceMethod(class, @selector(setTitleTextFieldCenterOffset:animated:)); 279 | method_exchangeImplementations(new, old); 280 | } 281 | } 282 | else 283 | { 284 | new = class_getInstanceMethod(class, @selector(new_trackMouseEventsForEvent:onTabAtIndex:)); 285 | old = class_getInstanceMethod(class, @selector(_trackMouseEventsForEvent:onTabAtIndex:)); 286 | method_exchangeImplementations(new, old); 287 | } 288 | } 289 | else // Safari 7 290 | { 291 | Class class = NSClassFromString(@"TabBarView"); 292 | 293 | Method new = class_getInstanceMethod(class, @selector(new_availableWidthForButtonsWhenUnclipped)); 294 | Method old = class_getInstanceMethod(class, @selector(_availableWidthForButtonsWhenUnclipped)); 295 | method_exchangeImplementations(new, old); 296 | 297 | new = class_getInstanceMethod(class, @selector(new_tabViewDidChangeNumberOfTabViewItems:)); 298 | old = class_getInstanceMethod(class, @selector(tabViewDidChangeNumberOfTabViewItems:)); 299 | method_exchangeImplementations(new, old); 300 | 301 | 302 | if (TAB_TITLE_LEFT_MARGIN != NSIntegerMax || TAB_TITLE_RIGHT_MARGIN != NSIntegerMax) 303 | { 304 | class = NSClassFromString(@"AttachedTabButton"); 305 | 306 | new = class_getInstanceMethod(class, @selector(new__updateTitleTextFieldFrame)); 307 | old = class_getInstanceMethod(class, @selector(_updateTitleTextFieldFrame)); 308 | method_exchangeImplementations(new, old); 309 | 310 | new = class_getInstanceMethod(class, @selector(new_setState:)); 311 | old = class_getInstanceMethod(class, @selector(setState:)); 312 | method_exchangeImplementations(new, old); 313 | 314 | new = class_getInstanceMethod(class, @selector(new_setTitle:)); 315 | old = class_getInstanceMethod(class, @selector(setTitle:)); 316 | method_exchangeImplementations(new, old); 317 | } 318 | } 319 | 320 | // Resize already-open tabs, surrounded by a try-catch as a precaution. Thanks to @gbroochian for the suggestion. 321 | @try { 322 | for (NSWindow* window in [[NSClassFromString(@"NSApplication") sharedApplication] windows]) 323 | { 324 | if ([window isKindOfClass:NSClassFromString(@"BrowserWindow")]) 325 | { 326 | if (safari9OrLater) 327 | { 328 | id tabBarView = [[window windowController] performSelector:@selector(tabBarView)]; 329 | [tabBarView performSelector:@selector(layout)]; 330 | } 331 | else 332 | { 333 | NSArray *orderedTabViewItems = [window performSelector:@selector(orderedTabViewItems)]; 334 | id firstTabBarViewItem = [orderedTabViewItems firstObject]; 335 | 336 | // Safari 8 337 | if ([firstTabBarViewItem respondsToSelector:@selector(scrollableTabBarView)]) 338 | { 339 | id scrollableTabBarView = [firstTabBarViewItem performSelector:@selector(scrollableTabBarView)]; 340 | [scrollableTabBarView performSelector:@selector(tabViewDidChangeNumberOfTabViewItems:) withObject:nil]; 341 | } 342 | // Safari 7<= 343 | else if ([firstTabBarViewItem respondsToSelector:@selector(tabBarView)]) 344 | { 345 | id tabBarView = [firstTabBarViewItem performSelector:@selector(tabBarView)]; 346 | [tabBarView performSelector:@selector(refreshButtons)]; 347 | } 348 | } 349 | 350 | } 351 | } 352 | } 353 | @catch (NSException* exception) { 354 | NSLog(@"Caught cosyTabs exception: %@", exception); 355 | } 356 | } 357 | 358 | + (BOOL)isSafari10OrLater { 359 | return [self safariMajorVersion] >= 10; 360 | } 361 | 362 | + (BOOL)isSafari9OrLater { 363 | return [self safariMajorVersion] >= 9; 364 | } 365 | 366 | + (BOOL)isSafari8 { 367 | return NSClassFromString(@"ScrollableTabBarView") != nil; 368 | } 369 | 370 | + (NSInteger)safariMajorVersion { 371 | NSBundle* bundle = NSBundle.mainBundle; 372 | if ([bundle respondsToSelector:@selector(shortVersion)]) 373 | { 374 | NSString* version = [bundle performSelector:@selector(shortVersion)]; 375 | return [version componentsSeparatedByString:@"."].firstObject.integerValue; 376 | } 377 | 378 | return 0; 379 | } 380 | 381 | + (NSString*)osVersion { 382 | static NSString* versionString = nil; 383 | if (!versionString) 384 | { 385 | NSDictionary * sv = [NSDictionary dictionaryWithContentsOfFile:@"/System/Library/CoreServices/SystemVersion.plist"]; 386 | versionString = [sv objectForKey:@"ProductVersion"]; 387 | } 388 | 389 | return versionString; 390 | } 391 | 392 | + (BOOL)isYosemite { 393 | return [[cosyTabs osVersion] hasPrefix:@"10.10"]; 394 | } 395 | 396 | + (BOOL)isElCapitanOrLater { 397 | return ![cosyTabs isYosemite] && [[cosyTabs osVersion] hasPrefix:@"10.1"]; 398 | } 399 | 400 | @end 401 | -------------------------------------------------------------------------------- /cosyTabs/en.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | /* Localized versions of Info.plist keys */ 2 | 3 | --------------------------------------------------------------------------------