├── .DS_Store ├── .background └── background.png ├── .gitignore ├── BirdDrop-Prefix.pch ├── BirdDrop.dmg ├── BirdDrop.xcodeproj └── project.pbxproj ├── License.txt ├── Popup ├── .DS_Store ├── ApplicationDelegate.h ├── ApplicationDelegate.m ├── BackgroundView.h ├── BackgroundView.m ├── BirdDrop-Info.plist ├── BirdDrop-Prefix.pch ├── BirdDrop.iconset │ ├── icon_128x128.png │ ├── icon_128x128@2x.png │ ├── icon_16x16.png │ ├── icon_16x16@2x.png │ ├── icon_256x256.png │ ├── icon_256x256@2x.png │ ├── icon_32x32.png │ ├── icon_32x32@2x.png │ ├── icon_512x512.png │ └── icon_512x512@2x.png ├── DDHotKeyCenter.h ├── DDHotKeyCenter.m ├── Images │ ├── .svn │ │ ├── entries │ │ ├── format │ │ ├── pristine │ │ │ ├── 71 │ │ │ │ └── 7151f8996a9f98c1fe1bb8ff386ff701e65270aa.svn-base │ │ │ ├── dc │ │ │ │ └── dcac81283e25249d031644b17aee7dd5bbde5988.svn-base │ │ │ ├── ed │ │ │ │ └── edd87be5abe269c0c2f525d9b4eee32d595d7d8d.svn-base │ │ │ └── f8 │ │ │ │ └── f81462ffc3af6afb4f1d3deb3bf18e6ea7759825.svn-base │ │ └── wc.db │ ├── SRRemoveShortcut.tif │ ├── SRRemoveShortcutPressed.tif │ ├── SRRemoveShortcutRollover.tif │ └── SRSnapback.tiff ├── MenubarController.h ├── MenubarController.m ├── Panel.h ├── Panel.m ├── Panel.xib ├── PanelController.h ├── PanelController.m ├── PreferencesViewController.h ├── PreferencesViewController.m ├── PreferencesViewController.xib ├── Status.png ├── Status@2x.png ├── StatusBlue.png ├── StatusBlue@2x.png ├── StatusHighlighted.png ├── StatusHighlighted@2x.png ├── StatusItemView.h ├── StatusItemView.m ├── bird.aif ├── en.lproj │ └── MainMenu.xib ├── keys.js └── main.m ├── README.md ├── mountain-lion-notification.png ├── pkg-dmg ├── screen1.png └── version.txt /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/soheil-zz/BirdDrop-OSX/b20de07ee6f197ceb7a91f6d7ca296ad8abad529/.DS_Store -------------------------------------------------------------------------------- /.background/background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/soheil-zz/BirdDrop-OSX/b20de07ee6f197ceb7a91f6d7ca296ad8abad529/.background/background.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | build/* 3 | *.pbxuser 4 | !default.pbxuser 5 | *.mode1v3 6 | !default.mode1v3 7 | *.mode2v3 8 | !default.mode2v3 9 | *.perspectivev3 10 | !default.perspectivev3 11 | !default.xcworkspace 12 | xcuserdata 13 | profile 14 | *.moved-aside 15 | #deploy script 16 | deploy 17 | rakefile*.rb 18 | upload*.sh 19 | *.rb 20 | pkg/ 21 | bin 22 | 23 | # default project workspace 24 | project.xcworkspace 25 | 26 | #*.dmg 27 | -------------------------------------------------------------------------------- /BirdDrop-Prefix.pch: -------------------------------------------------------------------------------- 1 | #ifdef __OBJC__ 2 | #import 3 | #endif 4 | #define VERSION 10019 5 | -------------------------------------------------------------------------------- /BirdDrop.dmg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/soheil-zz/BirdDrop-OSX/b20de07ee6f197ceb7a91f6d7ca296ad8abad529/BirdDrop.dmg -------------------------------------------------------------------------------- /BirdDrop.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXAggregateTarget section */ 10 | 5B7C616B1724FF0100E6FAD8 /* Dist */ = { 11 | isa = PBXAggregateTarget; 12 | buildConfigurationList = 5B7C616C1724FF0100E6FAD8 /* Build configuration list for PBXAggregateTarget "Dist" */; 13 | buildPhases = ( 14 | 5B7C616F1724FF0A00E6FAD8 /* Package & scp DMG */, 15 | 5BCBD81C1727DBB700EFFE50 /* Install & open locally */, 16 | ); 17 | dependencies = ( 18 | 5B65692517279E4E001EF646 /* PBXTargetDependency */, 19 | ); 20 | name = Dist; 21 | productName = Dist; 22 | }; 23 | 5BD3006C1727C6780015866C /* Bump Version */ = { 24 | isa = PBXAggregateTarget; 25 | buildConfigurationList = 5BD3006D1727C6780015866C /* Build configuration list for PBXAggregateTarget "Bump Version" */; 26 | buildPhases = ( 27 | 5BD300721727C6910015866C /* Bump Version */, 28 | ); 29 | dependencies = ( 30 | ); 31 | name = "Bump Version"; 32 | productName = "Bump Version"; 33 | }; 34 | /* End PBXAggregateTarget section */ 35 | 36 | /* Begin PBXBuildFile section */ 37 | 5B12391517324437004DC5ED /* PreferencesViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 5B12391317324437004DC5ED /* PreferencesViewController.m */; }; 38 | 5B12391617324437004DC5ED /* PreferencesViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 5B12391417324437004DC5ED /* PreferencesViewController.xib */; }; 39 | 5B12391C17324540004DC5ED /* SRRemoveShortcut.tif in Resources */ = {isa = PBXBuildFile; fileRef = 5B12391817324540004DC5ED /* SRRemoveShortcut.tif */; }; 40 | 5B12391D17324540004DC5ED /* SRRemoveShortcutPressed.tif in Resources */ = {isa = PBXBuildFile; fileRef = 5B12391917324540004DC5ED /* SRRemoveShortcutPressed.tif */; }; 41 | 5B12391E17324540004DC5ED /* SRRemoveShortcutRollover.tif in Resources */ = {isa = PBXBuildFile; fileRef = 5B12391A17324540004DC5ED /* SRRemoveShortcutRollover.tif */; }; 42 | 5B12391F17324540004DC5ED /* SRSnapback.tiff in Resources */ = {isa = PBXBuildFile; fileRef = 5B12391B17324540004DC5ED /* SRSnapback.tiff */; }; 43 | 5B3E85E81792587B0006E7D1 /* bird.aif in Resources */ = {isa = PBXBuildFile; fileRef = 5B3E85E71792587B0006E7D1 /* bird.aif */; }; 44 | 5B766C051733A57A00B671F4 /* StatusBlue.png in Resources */ = {isa = PBXBuildFile; fileRef = 5B766C031733A57A00B671F4 /* StatusBlue.png */; }; 45 | 5B766C061733A57A00B671F4 /* StatusBlue@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 5B766C041733A57A00B671F4 /* StatusBlue@2x.png */; }; 46 | 5B7C61611724E26E00E6FAD8 /* WebKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5B7C61601724E26E00E6FAD8 /* WebKit.framework */; }; 47 | 5B7C61631724F03400E6FAD8 /* Carbon.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5B7C61621724F03400E6FAD8 /* Carbon.framework */; }; 48 | 5B7C61661724F06500E6FAD8 /* DDHotKeyCenter.m in Sources */ = {isa = PBXBuildFile; fileRef = 5B7C61651724F06500E6FAD8 /* DDHotKeyCenter.m */; }; 49 | 5B7C616A1724FD0000E6FAD8 /* BirdDrop.iconset in Resources */ = {isa = PBXBuildFile; fileRef = 5B7C61691724FD0000E6FAD8 /* BirdDrop.iconset */; }; 50 | 5BC128B2172A545A007F7883 /* README.md in Resources */ = {isa = PBXBuildFile; fileRef = 5BC128B1172A545A007F7883 /* README.md */; }; 51 | 5BE2B9D017337A65003996F6 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DD4F7C0D13C30F9F00825C6E /* Foundation.framework */; }; 52 | DD4F7C0913C30F9F00825C6E /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DD4F7C0813C30F9F00825C6E /* Cocoa.framework */; }; 53 | DD4F7C1513C30F9F00825C6E /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = DD4F7C1413C30F9F00825C6E /* main.m */; }; 54 | DD4F7C1C13C30F9F00825C6E /* ApplicationDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = DD4F7C1B13C30F9F00825C6E /* ApplicationDelegate.m */; }; 55 | DD4F7C1F13C30F9F00825C6E /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = DD4F7C1D13C30F9F00825C6E /* MainMenu.xib */; }; 56 | DD4F7C3813C317A500825C6E /* MenubarController.m in Sources */ = {isa = PBXBuildFile; fileRef = DD4F7C3713C317A500825C6E /* MenubarController.m */; }; 57 | DD4F7C3C13C3180800825C6E /* StatusItemView.m in Sources */ = {isa = PBXBuildFile; fileRef = DD4F7C3B13C3180800825C6E /* StatusItemView.m */; }; 58 | DD4F7C3F13C318D600825C6E /* PanelController.m in Sources */ = {isa = PBXBuildFile; fileRef = DD4F7C3E13C318D600825C6E /* PanelController.m */; }; 59 | DD4F7C4113C31B4A00825C6E /* Panel.xib in Resources */ = {isa = PBXBuildFile; fileRef = DD4F7C4013C31B4A00825C6E /* Panel.xib */; }; 60 | DD4F7C4413C31BD200825C6E /* Panel.m in Sources */ = {isa = PBXBuildFile; fileRef = DD4F7C4313C31BD200825C6E /* Panel.m */; }; 61 | DD4F7C4713C3208500825C6E /* BackgroundView.m in Sources */ = {isa = PBXBuildFile; fileRef = DD4F7C4613C3208500825C6E /* BackgroundView.m */; }; 62 | DD4F7C4B13C322FA00825C6E /* Status.png in Resources */ = {isa = PBXBuildFile; fileRef = DD4F7C4913C322FA00825C6E /* Status.png */; }; 63 | DD4F7C4C13C322FA00825C6E /* StatusHighlighted.png in Resources */ = {isa = PBXBuildFile; fileRef = DD4F7C4A13C322FA00825C6E /* StatusHighlighted.png */; }; 64 | DDF4EFAC16C80A510044D141 /* Status@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DDF4EFAB16C80A510044D141 /* Status@2x.png */; }; 65 | DDF4EFAE16C80AD30044D141 /* StatusHighlighted@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DDF4EFAD16C80AD30044D141 /* StatusHighlighted@2x.png */; }; 66 | /* End PBXBuildFile section */ 67 | 68 | /* Begin PBXContainerItemProxy section */ 69 | 5B65692417279E4E001EF646 /* PBXContainerItemProxy */ = { 70 | isa = PBXContainerItemProxy; 71 | containerPortal = DD4F7BFB13C30F9F00825C6E /* Project object */; 72 | proxyType = 1; 73 | remoteGlobalIDString = DD4F7C0313C30F9F00825C6E; 74 | remoteInfo = BirdDrop; 75 | }; 76 | 5BAFE0E9172C67B500B01426 /* PBXContainerItemProxy */ = { 77 | isa = PBXContainerItemProxy; 78 | containerPortal = DD4F7BFB13C30F9F00825C6E /* Project object */; 79 | proxyType = 1; 80 | remoteGlobalIDString = 5BD3006C1727C6780015866C; 81 | remoteInfo = "Bump Version"; 82 | }; 83 | /* End PBXContainerItemProxy section */ 84 | 85 | /* Begin PBXFileReference section */ 86 | 5B12391217324437004DC5ED /* PreferencesViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PreferencesViewController.h; sourceTree = ""; }; 87 | 5B12391317324437004DC5ED /* PreferencesViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PreferencesViewController.m; sourceTree = ""; }; 88 | 5B12391417324437004DC5ED /* PreferencesViewController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = PreferencesViewController.xib; sourceTree = ""; }; 89 | 5B12391817324540004DC5ED /* SRRemoveShortcut.tif */ = {isa = PBXFileReference; lastKnownFileType = image.tiff; path = SRRemoveShortcut.tif; sourceTree = ""; }; 90 | 5B12391917324540004DC5ED /* SRRemoveShortcutPressed.tif */ = {isa = PBXFileReference; lastKnownFileType = image.tiff; path = SRRemoveShortcutPressed.tif; sourceTree = ""; }; 91 | 5B12391A17324540004DC5ED /* SRRemoveShortcutRollover.tif */ = {isa = PBXFileReference; lastKnownFileType = image.tiff; path = SRRemoveShortcutRollover.tif; sourceTree = ""; }; 92 | 5B12391B17324540004DC5ED /* SRSnapback.tiff */ = {isa = PBXFileReference; lastKnownFileType = image.tiff; path = SRSnapback.tiff; sourceTree = ""; }; 93 | 5B3E85E71792587B0006E7D1 /* bird.aif */ = {isa = PBXFileReference; lastKnownFileType = file; path = bird.aif; sourceTree = ""; }; 94 | 5B766C031733A57A00B671F4 /* StatusBlue.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = StatusBlue.png; path = Popup/StatusBlue.png; sourceTree = ""; }; 95 | 5B766C041733A57A00B671F4 /* StatusBlue@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "StatusBlue@2x.png"; path = "Popup/StatusBlue@2x.png"; sourceTree = ""; }; 96 | 5B7C61601724E26E00E6FAD8 /* WebKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = WebKit.framework; path = System/Library/Frameworks/WebKit.framework; sourceTree = SDKROOT; }; 97 | 5B7C61621724F03400E6FAD8 /* Carbon.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Carbon.framework; path = System/Library/Frameworks/Carbon.framework; sourceTree = SDKROOT; }; 98 | 5B7C61641724F06400E6FAD8 /* DDHotKeyCenter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DDHotKeyCenter.h; sourceTree = ""; }; 99 | 5B7C61651724F06500E6FAD8 /* DDHotKeyCenter.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DDHotKeyCenter.m; sourceTree = ""; }; 100 | 5B7C61691724FD0000E6FAD8 /* BirdDrop.iconset */ = {isa = PBXFileReference; lastKnownFileType = folder.iconset; name = BirdDrop.iconset; path = Popup/BirdDrop.iconset; sourceTree = ""; }; 101 | 5BC128B1172A545A007F7883 /* README.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = README.md; sourceTree = ""; }; 102 | DD4F7C0413C30F9F00825C6E /* BirdDrop.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = BirdDrop.app; sourceTree = BUILT_PRODUCTS_DIR; }; 103 | DD4F7C0813C30F9F00825C6E /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = System/Library/Frameworks/Cocoa.framework; sourceTree = SDKROOT; }; 104 | DD4F7C0B13C30F9F00825C6E /* AppKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AppKit.framework; path = System/Library/Frameworks/AppKit.framework; sourceTree = SDKROOT; }; 105 | DD4F7C0C13C30F9F00825C6E /* CoreData.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreData.framework; path = System/Library/Frameworks/CoreData.framework; sourceTree = SDKROOT; }; 106 | DD4F7C0D13C30F9F00825C6E /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; 107 | DD4F7C1013C30F9F00825C6E /* BirdDrop-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = "BirdDrop-Info.plist"; path = "Popup/BirdDrop-Info.plist"; sourceTree = ""; }; 108 | DD4F7C1413C30F9F00825C6E /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 109 | DD4F7C1613C30F9F00825C6E /* BirdDrop-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "BirdDrop-Prefix.pch"; sourceTree = ""; }; 110 | DD4F7C1A13C30F9F00825C6E /* ApplicationDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ApplicationDelegate.h; sourceTree = ""; }; 111 | DD4F7C1B13C30F9F00825C6E /* ApplicationDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ApplicationDelegate.m; sourceTree = ""; }; 112 | DD4F7C1E13C30F9F00825C6E /* en */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = en; path = en.lproj/MainMenu.xib; sourceTree = ""; }; 113 | DD4F7C3613C317A500825C6E /* MenubarController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MenubarController.h; sourceTree = ""; }; 114 | DD4F7C3713C317A500825C6E /* MenubarController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MenubarController.m; sourceTree = ""; }; 115 | DD4F7C3A13C3180800825C6E /* StatusItemView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = StatusItemView.h; sourceTree = ""; }; 116 | DD4F7C3B13C3180800825C6E /* StatusItemView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = StatusItemView.m; sourceTree = ""; }; 117 | DD4F7C3D13C318D600825C6E /* PanelController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PanelController.h; sourceTree = ""; }; 118 | DD4F7C3E13C318D600825C6E /* PanelController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PanelController.m; sourceTree = ""; }; 119 | DD4F7C4013C31B4A00825C6E /* Panel.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; name = Panel.xib; path = Popup/Panel.xib; sourceTree = ""; }; 120 | DD4F7C4213C31BD200825C6E /* Panel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Panel.h; sourceTree = ""; }; 121 | DD4F7C4313C31BD200825C6E /* Panel.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Panel.m; sourceTree = ""; }; 122 | DD4F7C4513C3208500825C6E /* BackgroundView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BackgroundView.h; sourceTree = ""; }; 123 | DD4F7C4613C3208500825C6E /* BackgroundView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BackgroundView.m; sourceTree = ""; }; 124 | DD4F7C4913C322FA00825C6E /* Status.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = Status.png; path = Popup/Status.png; sourceTree = ""; }; 125 | DD4F7C4A13C322FA00825C6E /* StatusHighlighted.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = StatusHighlighted.png; path = Popup/StatusHighlighted.png; sourceTree = ""; }; 126 | DDF4EFAB16C80A510044D141 /* Status@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "Status@2x.png"; path = "Popup/Status@2x.png"; sourceTree = ""; }; 127 | DDF4EFAD16C80AD30044D141 /* StatusHighlighted@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "StatusHighlighted@2x.png"; path = "Popup/StatusHighlighted@2x.png"; sourceTree = ""; }; 128 | /* End PBXFileReference section */ 129 | 130 | /* Begin PBXFrameworksBuildPhase section */ 131 | DD4F7C0113C30F9F00825C6E /* Frameworks */ = { 132 | isa = PBXFrameworksBuildPhase; 133 | buildActionMask = 2147483647; 134 | files = ( 135 | 5BE2B9D017337A65003996F6 /* Foundation.framework in Frameworks */, 136 | 5B7C61631724F03400E6FAD8 /* Carbon.framework in Frameworks */, 137 | 5B7C61611724E26E00E6FAD8 /* WebKit.framework in Frameworks */, 138 | DD4F7C0913C30F9F00825C6E /* Cocoa.framework in Frameworks */, 139 | ); 140 | runOnlyForDeploymentPostprocessing = 0; 141 | }; 142 | /* End PBXFrameworksBuildPhase section */ 143 | 144 | /* Begin PBXGroup section */ 145 | 5B12391717324540004DC5ED /* Images */ = { 146 | isa = PBXGroup; 147 | children = ( 148 | 5B12391817324540004DC5ED /* SRRemoveShortcut.tif */, 149 | 5B12391917324540004DC5ED /* SRRemoveShortcutPressed.tif */, 150 | 5B12391A17324540004DC5ED /* SRRemoveShortcutRollover.tif */, 151 | 5B12391B17324540004DC5ED /* SRSnapback.tiff */, 152 | ); 153 | path = Images; 154 | sourceTree = ""; 155 | }; 156 | DD4F7BF913C30F9F00825C6E = { 157 | isa = PBXGroup; 158 | children = ( 159 | 5BC128B1172A545A007F7883 /* README.md */, 160 | 5B7C61691724FD0000E6FAD8 /* BirdDrop.iconset */, 161 | 5B7C61621724F03400E6FAD8 /* Carbon.framework */, 162 | 5B7C61601724E26E00E6FAD8 /* WebKit.framework */, 163 | DD4F7C0E13C30F9F00825C6E /* Sources */, 164 | DD4F7C2813C3123E00825C6E /* User Interface */, 165 | DD4F7C0713C30F9F00825C6E /* Frameworks */, 166 | DD4F7C0513C30F9F00825C6E /* Products */, 167 | DD4F7C1013C30F9F00825C6E /* BirdDrop-Info.plist */, 168 | ); 169 | sourceTree = ""; 170 | }; 171 | DD4F7C0513C30F9F00825C6E /* Products */ = { 172 | isa = PBXGroup; 173 | children = ( 174 | DD4F7C0413C30F9F00825C6E /* BirdDrop.app */, 175 | ); 176 | name = Products; 177 | sourceTree = ""; 178 | }; 179 | DD4F7C0713C30F9F00825C6E /* Frameworks */ = { 180 | isa = PBXGroup; 181 | children = ( 182 | DD4F7C0B13C30F9F00825C6E /* AppKit.framework */, 183 | DD4F7C0C13C30F9F00825C6E /* CoreData.framework */, 184 | DD4F7C0D13C30F9F00825C6E /* Foundation.framework */, 185 | DD4F7C0813C30F9F00825C6E /* Cocoa.framework */, 186 | ); 187 | name = Frameworks; 188 | sourceTree = ""; 189 | }; 190 | DD4F7C0E13C30F9F00825C6E /* Sources */ = { 191 | isa = PBXGroup; 192 | children = ( 193 | 5B3E85E71792587B0006E7D1 /* bird.aif */, 194 | 5B12391717324540004DC5ED /* Images */, 195 | 5B7C61641724F06400E6FAD8 /* DDHotKeyCenter.h */, 196 | 5B7C61651724F06500E6FAD8 /* DDHotKeyCenter.m */, 197 | DD4F7C2613C311DF00825C6E /* Controllers */, 198 | DD4F7C3913C317EC00825C6E /* Views */, 199 | DD4F7C0F13C30F9F00825C6E /* Supporting Files */, 200 | ); 201 | name = Sources; 202 | path = Popup; 203 | sourceTree = ""; 204 | }; 205 | DD4F7C0F13C30F9F00825C6E /* Supporting Files */ = { 206 | isa = PBXGroup; 207 | children = ( 208 | DD4F7C1613C30F9F00825C6E /* BirdDrop-Prefix.pch */, 209 | DD4F7C1413C30F9F00825C6E /* main.m */, 210 | ); 211 | name = "Supporting Files"; 212 | sourceTree = ""; 213 | }; 214 | DD4F7C2613C311DF00825C6E /* Controllers */ = { 215 | isa = PBXGroup; 216 | children = ( 217 | DD4F7C1A13C30F9F00825C6E /* ApplicationDelegate.h */, 218 | DD4F7C1B13C30F9F00825C6E /* ApplicationDelegate.m */, 219 | DD4F7C3613C317A500825C6E /* MenubarController.h */, 220 | DD4F7C3713C317A500825C6E /* MenubarController.m */, 221 | DD4F7C3D13C318D600825C6E /* PanelController.h */, 222 | DD4F7C3E13C318D600825C6E /* PanelController.m */, 223 | 5B12391217324437004DC5ED /* PreferencesViewController.h */, 224 | 5B12391317324437004DC5ED /* PreferencesViewController.m */, 225 | 5B12391417324437004DC5ED /* PreferencesViewController.xib */, 226 | ); 227 | name = Controllers; 228 | sourceTree = ""; 229 | }; 230 | DD4F7C2813C3123E00825C6E /* User Interface */ = { 231 | isa = PBXGroup; 232 | children = ( 233 | DD4F7C4813C322D800825C6E /* Graphics */, 234 | DD4F7C2913C3124300825C6E /* XIBs */, 235 | ); 236 | name = "User Interface"; 237 | sourceTree = ""; 238 | }; 239 | DD4F7C2913C3124300825C6E /* XIBs */ = { 240 | isa = PBXGroup; 241 | children = ( 242 | DD4F7C1D13C30F9F00825C6E /* MainMenu.xib */, 243 | DD4F7C4013C31B4A00825C6E /* Panel.xib */, 244 | ); 245 | name = XIBs; 246 | sourceTree = ""; 247 | }; 248 | DD4F7C3913C317EC00825C6E /* Views */ = { 249 | isa = PBXGroup; 250 | children = ( 251 | DD4F7C4213C31BD200825C6E /* Panel.h */, 252 | DD4F7C4313C31BD200825C6E /* Panel.m */, 253 | DD4F7C3A13C3180800825C6E /* StatusItemView.h */, 254 | DD4F7C3B13C3180800825C6E /* StatusItemView.m */, 255 | DD4F7C4513C3208500825C6E /* BackgroundView.h */, 256 | DD4F7C4613C3208500825C6E /* BackgroundView.m */, 257 | ); 258 | name = Views; 259 | sourceTree = ""; 260 | }; 261 | DD4F7C4813C322D800825C6E /* Graphics */ = { 262 | isa = PBXGroup; 263 | children = ( 264 | DD4F7C4913C322FA00825C6E /* Status.png */, 265 | DDF4EFAB16C80A510044D141 /* Status@2x.png */, 266 | DD4F7C4A13C322FA00825C6E /* StatusHighlighted.png */, 267 | DDF4EFAD16C80AD30044D141 /* StatusHighlighted@2x.png */, 268 | 5B766C031733A57A00B671F4 /* StatusBlue.png */, 269 | 5B766C041733A57A00B671F4 /* StatusBlue@2x.png */, 270 | ); 271 | name = Graphics; 272 | sourceTree = ""; 273 | }; 274 | /* End PBXGroup section */ 275 | 276 | /* Begin PBXNativeTarget section */ 277 | DD4F7C0313C30F9F00825C6E /* BirdDrop */ = { 278 | isa = PBXNativeTarget; 279 | buildConfigurationList = DD4F7C2213C30F9F00825C6E /* Build configuration list for PBXNativeTarget "BirdDrop" */; 280 | buildPhases = ( 281 | DD4F7C0013C30F9F00825C6E /* Sources */, 282 | DD4F7C0113C30F9F00825C6E /* Frameworks */, 283 | DD4F7C0213C30F9F00825C6E /* Resources */, 284 | 5BC128B0172A423D007F7883 /* Kill Existing Procs */, 285 | ); 286 | buildRules = ( 287 | ); 288 | dependencies = ( 289 | 5BAFE0EA172C67B500B01426 /* PBXTargetDependency */, 290 | ); 291 | name = BirdDrop; 292 | productName = Popup; 293 | productReference = DD4F7C0413C30F9F00825C6E /* BirdDrop.app */; 294 | productType = "com.apple.product-type.application"; 295 | }; 296 | /* End PBXNativeTarget section */ 297 | 298 | /* Begin PBXProject section */ 299 | DD4F7BFB13C30F9F00825C6E /* Project object */ = { 300 | isa = PBXProject; 301 | attributes = { 302 | LastUpgradeCheck = 0720; 303 | }; 304 | buildConfigurationList = DD4F7BFE13C30F9F00825C6E /* Build configuration list for PBXProject "BirdDrop" */; 305 | compatibilityVersion = "Xcode 3.2"; 306 | developmentRegion = English; 307 | hasScannedForEncodings = 0; 308 | knownRegions = ( 309 | en, 310 | ); 311 | mainGroup = DD4F7BF913C30F9F00825C6E; 312 | productRefGroup = DD4F7C0513C30F9F00825C6E /* Products */; 313 | projectDirPath = ""; 314 | projectRoot = ""; 315 | targets = ( 316 | DD4F7C0313C30F9F00825C6E /* BirdDrop */, 317 | 5B7C616B1724FF0100E6FAD8 /* Dist */, 318 | 5BD3006C1727C6780015866C /* Bump Version */, 319 | ); 320 | }; 321 | /* End PBXProject section */ 322 | 323 | /* Begin PBXResourcesBuildPhase section */ 324 | DD4F7C0213C30F9F00825C6E /* Resources */ = { 325 | isa = PBXResourcesBuildPhase; 326 | buildActionMask = 2147483647; 327 | files = ( 328 | DD4F7C1F13C30F9F00825C6E /* MainMenu.xib in Resources */, 329 | DD4F7C4113C31B4A00825C6E /* Panel.xib in Resources */, 330 | DD4F7C4B13C322FA00825C6E /* Status.png in Resources */, 331 | DD4F7C4C13C322FA00825C6E /* StatusHighlighted.png in Resources */, 332 | DDF4EFAC16C80A510044D141 /* Status@2x.png in Resources */, 333 | DDF4EFAE16C80AD30044D141 /* StatusHighlighted@2x.png in Resources */, 334 | 5B7C616A1724FD0000E6FAD8 /* BirdDrop.iconset in Resources */, 335 | 5BC128B2172A545A007F7883 /* README.md in Resources */, 336 | 5B12391617324437004DC5ED /* PreferencesViewController.xib in Resources */, 337 | 5B12391C17324540004DC5ED /* SRRemoveShortcut.tif in Resources */, 338 | 5B12391D17324540004DC5ED /* SRRemoveShortcutPressed.tif in Resources */, 339 | 5B12391E17324540004DC5ED /* SRRemoveShortcutRollover.tif in Resources */, 340 | 5B12391F17324540004DC5ED /* SRSnapback.tiff in Resources */, 341 | 5B766C051733A57A00B671F4 /* StatusBlue.png in Resources */, 342 | 5B766C061733A57A00B671F4 /* StatusBlue@2x.png in Resources */, 343 | 5B3E85E81792587B0006E7D1 /* bird.aif in Resources */, 344 | ); 345 | runOnlyForDeploymentPostprocessing = 0; 346 | }; 347 | /* End PBXResourcesBuildPhase section */ 348 | 349 | /* Begin PBXShellScriptBuildPhase section */ 350 | 5B7C616F1724FF0A00E6FAD8 /* Package & scp DMG */ = { 351 | isa = PBXShellScriptBuildPhase; 352 | buildActionMask = 2147483647; 353 | files = ( 354 | ); 355 | inputPaths = ( 356 | ); 357 | name = "Package & scp DMG"; 358 | outputPaths = ( 359 | ); 360 | runOnlyForDeploymentPostprocessing = 0; 361 | shellPath = /bin/sh; 362 | shellScript = "DMG=$PROJECT_NAME.dmg\nrm -rf $DMG\n\n./pkg-dmg \\\n--verbosity 2 \\\n--volname $PROJECT_NAME \\\n--source $TARGET_BUILD_DIR/$PROJECT_NAME.app \\\n--sourcefile \\\n--target $DMG \\\n--copy .DS_Store:.DS_Store \\\n--mkdir .background \\\n--copy .background/background.png:.background/background.png \\\n--attribute V:.background \\\n--symlink /Applications:Applications\n\nscp $DMG root@marrynoname.xxx:/drop\necho \"SCP'ed DMG\"\n"; 363 | }; 364 | 5BC128B0172A423D007F7883 /* Kill Existing Procs */ = { 365 | isa = PBXShellScriptBuildPhase; 366 | buildActionMask = 2147483647; 367 | files = ( 368 | ); 369 | inputPaths = ( 370 | ); 371 | name = "Kill Existing Procs"; 372 | outputPaths = ( 373 | ); 374 | runOnlyForDeploymentPostprocessing = 0; 375 | shellPath = /bin/sh; 376 | shellScript = "#ps aux|grep $PROJECT_NAME|grep -v grep|awk '{print $2}'|xargs kill\necho \"Killed existing procs\"\n"; 377 | }; 378 | 5BCBD81C1727DBB700EFFE50 /* Install & open locally */ = { 379 | isa = PBXShellScriptBuildPhase; 380 | buildActionMask = 2147483647; 381 | files = ( 382 | ); 383 | inputPaths = ( 384 | ); 385 | name = "Install & open locally"; 386 | outputPaths = ( 387 | ); 388 | runOnlyForDeploymentPostprocessing = 0; 389 | shellPath = /bin/sh; 390 | shellScript = "rm -rf /Applications/$PROJECT_NAME.app\ncp -r $TARGET_BUILD_DIR/$PROJECT_NAME.app /Applications\nopen /Applications/$PROJECT_NAME.app"; 391 | }; 392 | 5BD300721727C6910015866C /* Bump Version */ = { 393 | isa = PBXShellScriptBuildPhase; 394 | buildActionMask = 2147483647; 395 | files = ( 396 | ); 397 | inputPaths = ( 398 | ); 399 | name = "Bump Version"; 400 | outputPaths = ( 401 | ); 402 | runOnlyForDeploymentPostprocessing = 0; 403 | shellPath = /bin/sh; 404 | shellScript = "ver=$(($(cat version.txt) + 1))\necho $ver > version.txt\n\nsed s/\"VERSION.*\"/\"VERSION $ver\"/ Popup/BirdDrop-Prefix.pch > /tmp/a\nmv /tmp/a Popup/BirdDrop-Prefix.pch\n"; 405 | }; 406 | /* End PBXShellScriptBuildPhase section */ 407 | 408 | /* Begin PBXSourcesBuildPhase section */ 409 | DD4F7C0013C30F9F00825C6E /* Sources */ = { 410 | isa = PBXSourcesBuildPhase; 411 | buildActionMask = 2147483647; 412 | files = ( 413 | DD4F7C1513C30F9F00825C6E /* main.m in Sources */, 414 | DD4F7C1C13C30F9F00825C6E /* ApplicationDelegate.m in Sources */, 415 | DD4F7C3813C317A500825C6E /* MenubarController.m in Sources */, 416 | DD4F7C3C13C3180800825C6E /* StatusItemView.m in Sources */, 417 | DD4F7C3F13C318D600825C6E /* PanelController.m in Sources */, 418 | DD4F7C4413C31BD200825C6E /* Panel.m in Sources */, 419 | DD4F7C4713C3208500825C6E /* BackgroundView.m in Sources */, 420 | 5B7C61661724F06500E6FAD8 /* DDHotKeyCenter.m in Sources */, 421 | 5B12391517324437004DC5ED /* PreferencesViewController.m in Sources */, 422 | ); 423 | runOnlyForDeploymentPostprocessing = 0; 424 | }; 425 | /* End PBXSourcesBuildPhase section */ 426 | 427 | /* Begin PBXTargetDependency section */ 428 | 5B65692517279E4E001EF646 /* PBXTargetDependency */ = { 429 | isa = PBXTargetDependency; 430 | target = DD4F7C0313C30F9F00825C6E /* BirdDrop */; 431 | targetProxy = 5B65692417279E4E001EF646 /* PBXContainerItemProxy */; 432 | }; 433 | 5BAFE0EA172C67B500B01426 /* PBXTargetDependency */ = { 434 | isa = PBXTargetDependency; 435 | target = 5BD3006C1727C6780015866C /* Bump Version */; 436 | targetProxy = 5BAFE0E9172C67B500B01426 /* PBXContainerItemProxy */; 437 | }; 438 | /* End PBXTargetDependency section */ 439 | 440 | /* Begin PBXVariantGroup section */ 441 | DD4F7C1D13C30F9F00825C6E /* MainMenu.xib */ = { 442 | isa = PBXVariantGroup; 443 | children = ( 444 | DD4F7C1E13C30F9F00825C6E /* en */, 445 | ); 446 | name = MainMenu.xib; 447 | path = Popup; 448 | sourceTree = ""; 449 | }; 450 | /* End PBXVariantGroup section */ 451 | 452 | /* Begin XCBuildConfiguration section */ 453 | 5B7C616D1724FF0100E6FAD8 /* Debug */ = { 454 | isa = XCBuildConfiguration; 455 | buildSettings = { 456 | PRODUCT_NAME = "$(TARGET_NAME)"; 457 | }; 458 | name = Debug; 459 | }; 460 | 5B7C616E1724FF0100E6FAD8 /* Release */ = { 461 | isa = XCBuildConfiguration; 462 | buildSettings = { 463 | PRODUCT_NAME = "$(TARGET_NAME)"; 464 | }; 465 | name = Release; 466 | }; 467 | 5BD3006E1727C6780015866C /* Debug */ = { 468 | isa = XCBuildConfiguration; 469 | buildSettings = { 470 | PRODUCT_NAME = "$(TARGET_NAME)"; 471 | }; 472 | name = Debug; 473 | }; 474 | 5BD3006F1727C6780015866C /* Release */ = { 475 | isa = XCBuildConfiguration; 476 | buildSettings = { 477 | PRODUCT_NAME = "$(TARGET_NAME)"; 478 | }; 479 | name = Release; 480 | }; 481 | DD4F7C2013C30F9F00825C6E /* Debug */ = { 482 | isa = XCBuildConfiguration; 483 | buildSettings = { 484 | ALWAYS_SEARCH_USER_PATHS = NO; 485 | CLANG_WARN_BOOL_CONVERSION = YES; 486 | CLANG_WARN_CONSTANT_CONVERSION = YES; 487 | CLANG_WARN_EMPTY_BODY = YES; 488 | CLANG_WARN_ENUM_CONVERSION = YES; 489 | CLANG_WARN_INT_CONVERSION = YES; 490 | CLANG_WARN_UNREACHABLE_CODE = YES; 491 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 492 | COPY_PHASE_STRIP = NO; 493 | ENABLE_STRICT_OBJC_MSGSEND = YES; 494 | ENABLE_TESTABILITY = YES; 495 | GCC_C_LANGUAGE_STANDARD = gnu99; 496 | GCC_DYNAMIC_NO_PIC = NO; 497 | GCC_ENABLE_OBJC_EXCEPTIONS = YES; 498 | GCC_NO_COMMON_BLOCKS = YES; 499 | GCC_OPTIMIZATION_LEVEL = 0; 500 | GCC_PREPROCESSOR_DEFINITIONS = ( 501 | "DEBUG=1", 502 | "$(inherited)", 503 | ); 504 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 505 | GCC_VERSION = com.apple.compilers.llvm.clang.1_0; 506 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 507 | GCC_WARN_ABOUT_MISSING_PROTOTYPES = YES; 508 | GCC_WARN_ABOUT_RETURN_TYPE = YES; 509 | GCC_WARN_UNDECLARED_SELECTOR = YES; 510 | GCC_WARN_UNINITIALIZED_AUTOS = YES; 511 | GCC_WARN_UNUSED_FUNCTION = YES; 512 | GCC_WARN_UNUSED_VARIABLE = YES; 513 | MACOSX_DEPLOYMENT_TARGET = 10.6; 514 | ONLY_ACTIVE_ARCH = YES; 515 | SDKROOT = macosx; 516 | }; 517 | name = Debug; 518 | }; 519 | DD4F7C2113C30F9F00825C6E /* Release */ = { 520 | isa = XCBuildConfiguration; 521 | buildSettings = { 522 | ALWAYS_SEARCH_USER_PATHS = NO; 523 | CLANG_WARN_BOOL_CONVERSION = YES; 524 | CLANG_WARN_CONSTANT_CONVERSION = YES; 525 | CLANG_WARN_EMPTY_BODY = YES; 526 | CLANG_WARN_ENUM_CONVERSION = YES; 527 | CLANG_WARN_INT_CONVERSION = YES; 528 | CLANG_WARN_UNREACHABLE_CODE = YES; 529 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 530 | COPY_PHASE_STRIP = YES; 531 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 532 | ENABLE_STRICT_OBJC_MSGSEND = YES; 533 | GCC_C_LANGUAGE_STANDARD = gnu99; 534 | GCC_ENABLE_OBJC_EXCEPTIONS = YES; 535 | GCC_NO_COMMON_BLOCKS = YES; 536 | GCC_VERSION = com.apple.compilers.llvm.clang.1_0; 537 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 538 | GCC_WARN_ABOUT_MISSING_PROTOTYPES = YES; 539 | GCC_WARN_ABOUT_RETURN_TYPE = YES; 540 | GCC_WARN_UNDECLARED_SELECTOR = YES; 541 | GCC_WARN_UNINITIALIZED_AUTOS = YES; 542 | GCC_WARN_UNUSED_FUNCTION = YES; 543 | GCC_WARN_UNUSED_VARIABLE = YES; 544 | MACOSX_DEPLOYMENT_TARGET = 10.6; 545 | SDKROOT = macosx; 546 | }; 547 | name = Release; 548 | }; 549 | DD4F7C2313C30F9F00825C6E /* Debug */ = { 550 | isa = XCBuildConfiguration; 551 | buildSettings = { 552 | CLANG_ENABLE_OBJC_ARC = YES; 553 | COMBINE_HIDPI_IMAGES = YES; 554 | FRAMEWORK_SEARCH_PATHS = ( 555 | "$(inherited)", 556 | "\"$(SRCROOT)/../../Library/Developer/Xcode/DerivedData/ShortcutRecorder-gejgdznrmlizthftgcqcpytrsxnf/Build/Products/Debug\"", 557 | "\"$(SRCROOT)\"", 558 | ); 559 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 560 | GCC_PREFIX_HEADER = "Popup/BirdDrop-Prefix.pch"; 561 | INFOPLIST_FILE = "Popup/BirdDrop-Info.plist"; 562 | PRODUCT_BUNDLE_IDENTIFIER = com.shpakovski.mac.popup; 563 | PRODUCT_NAME = BirdDrop; 564 | WRAPPER_EXTENSION = app; 565 | }; 566 | name = Debug; 567 | }; 568 | DD4F7C2413C30F9F00825C6E /* Release */ = { 569 | isa = XCBuildConfiguration; 570 | buildSettings = { 571 | CLANG_ENABLE_OBJC_ARC = YES; 572 | COMBINE_HIDPI_IMAGES = YES; 573 | FRAMEWORK_SEARCH_PATHS = ( 574 | "$(inherited)", 575 | "\"$(SRCROOT)/../../Library/Developer/Xcode/DerivedData/ShortcutRecorder-gejgdznrmlizthftgcqcpytrsxnf/Build/Products/Debug\"", 576 | "\"$(SRCROOT)\"", 577 | ); 578 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 579 | GCC_PREFIX_HEADER = "Popup/BirdDrop-Prefix.pch"; 580 | INFOPLIST_FILE = "Popup/BirdDrop-Info.plist"; 581 | PRODUCT_BUNDLE_IDENTIFIER = com.shpakovski.mac.popup; 582 | PRODUCT_NAME = BirdDrop; 583 | WRAPPER_EXTENSION = app; 584 | }; 585 | name = Release; 586 | }; 587 | /* End XCBuildConfiguration section */ 588 | 589 | /* Begin XCConfigurationList section */ 590 | 5B7C616C1724FF0100E6FAD8 /* Build configuration list for PBXAggregateTarget "Dist" */ = { 591 | isa = XCConfigurationList; 592 | buildConfigurations = ( 593 | 5B7C616D1724FF0100E6FAD8 /* Debug */, 594 | 5B7C616E1724FF0100E6FAD8 /* Release */, 595 | ); 596 | defaultConfigurationIsVisible = 0; 597 | defaultConfigurationName = Release; 598 | }; 599 | 5BD3006D1727C6780015866C /* Build configuration list for PBXAggregateTarget "Bump Version" */ = { 600 | isa = XCConfigurationList; 601 | buildConfigurations = ( 602 | 5BD3006E1727C6780015866C /* Debug */, 603 | 5BD3006F1727C6780015866C /* Release */, 604 | ); 605 | defaultConfigurationIsVisible = 0; 606 | defaultConfigurationName = Release; 607 | }; 608 | DD4F7BFE13C30F9F00825C6E /* Build configuration list for PBXProject "BirdDrop" */ = { 609 | isa = XCConfigurationList; 610 | buildConfigurations = ( 611 | DD4F7C2013C30F9F00825C6E /* Debug */, 612 | DD4F7C2113C30F9F00825C6E /* Release */, 613 | ); 614 | defaultConfigurationIsVisible = 0; 615 | defaultConfigurationName = Release; 616 | }; 617 | DD4F7C2213C30F9F00825C6E /* Build configuration list for PBXNativeTarget "BirdDrop" */ = { 618 | isa = XCConfigurationList; 619 | buildConfigurations = ( 620 | DD4F7C2313C30F9F00825C6E /* Debug */, 621 | DD4F7C2413C30F9F00825C6E /* Release */, 622 | ); 623 | defaultConfigurationIsVisible = 0; 624 | defaultConfigurationName = Release; 625 | }; 626 | /* End XCConfigurationList section */ 627 | }; 628 | rootObject = DD4F7BFB13C30F9F00825C6E /* Project object */; 629 | } 630 | -------------------------------------------------------------------------------- /License.txt: -------------------------------------------------------------------------------- 1 | BirdDrop is licensed under the BSD license. 2 | -------------------------------------------------------------------------------- /Popup/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/soheil-zz/BirdDrop-OSX/b20de07ee6f197ceb7a91f6d7ca296ad8abad529/Popup/.DS_Store -------------------------------------------------------------------------------- /Popup/ApplicationDelegate.h: -------------------------------------------------------------------------------- 1 | #import "MenubarController.h" 2 | #import "PanelController.h" 3 | 4 | @interface ApplicationDelegate : NSObject 5 | { 6 | /* 7 | * Summary: 8 | * Virtual keycodes 9 | * 10 | * Discussion: 11 | * These constants are the virtual keycodes defined originally in 12 | * Inside Mac Volume V, pg. V-191. They identify physical keys on a 13 | * keyboard. Those constants with "ANSI" in the name are labeled 14 | * according to the key position on an ANSI-standard US keyboard. 15 | * For example, kVK_ANSI_A indicates the virtual keycode for the key 16 | * with the letter 'A' in the US keyboard layout. Other keyboard 17 | * layouts may have the 'A' key label on a different physical key; 18 | * in this case, pressing 'A' will generate a different virtual 19 | * keycode. 20 | */ 21 | enum { 22 | kVK_ANSI_A = 0x00, 23 | kVK_ANSI_S = 0x01, 24 | kVK_ANSI_D = 0x02, 25 | kVK_ANSI_F = 0x03, 26 | kVK_ANSI_H = 0x04, 27 | kVK_ANSI_G = 0x05, 28 | kVK_ANSI_Z = 0x06, 29 | kVK_ANSI_X = 0x07, 30 | kVK_ANSI_C = 0x08, 31 | kVK_ANSI_V = 0x09, 32 | kVK_ANSI_B = 0x0B, 33 | kVK_ANSI_Q = 0x0C, 34 | kVK_ANSI_W = 0x0D, 35 | kVK_ANSI_E = 0x0E, 36 | kVK_ANSI_R = 0x0F, 37 | kVK_ANSI_Y = 0x10, 38 | kVK_ANSI_T = 0x11, 39 | kVK_ANSI_1 = 0x12, 40 | kVK_ANSI_2 = 0x13, 41 | kVK_ANSI_3 = 0x14, 42 | kVK_ANSI_4 = 0x15, 43 | kVK_ANSI_6 = 0x16, 44 | kVK_ANSI_5 = 0x17, 45 | kVK_ANSI_Equal = 0x18, 46 | kVK_ANSI_9 = 0x19, 47 | kVK_ANSI_7 = 0x1A, 48 | kVK_ANSI_Minus = 0x1B, 49 | kVK_ANSI_8 = 0x1C, 50 | kVK_ANSI_0 = 0x1D, 51 | kVK_ANSI_RightBracket = 0x1E, 52 | kVK_ANSI_O = 0x1F, 53 | kVK_ANSI_U = 0x20, 54 | kVK_ANSI_LeftBracket = 0x21, 55 | kVK_ANSI_I = 0x22, 56 | kVK_ANSI_P = 0x23, 57 | kVK_ANSI_L = 0x25, 58 | kVK_ANSI_J = 0x26, 59 | kVK_ANSI_Quote = 0x27, 60 | kVK_ANSI_K = 0x28, 61 | kVK_ANSI_Semicolon = 0x29, 62 | kVK_ANSI_Backslash = 0x2A, 63 | kVK_ANSI_Comma = 0x2B, 64 | kVK_ANSI_Slash = 0x2C, 65 | kVK_ANSI_N = 0x2D, 66 | kVK_ANSI_M = 0x2E, 67 | kVK_ANSI_Period = 0x2F, 68 | kVK_ANSI_Grave = 0x32, 69 | kVK_ANSI_KeypadDecimal = 0x41, 70 | kVK_ANSI_KeypadMultiply = 0x43, 71 | kVK_ANSI_KeypadPlus = 0x45, 72 | kVK_ANSI_KeypadClear = 0x47, 73 | kVK_ANSI_KeypadDivide = 0x4B, 74 | kVK_ANSI_KeypadEnter = 0x4C, 75 | kVK_ANSI_KeypadMinus = 0x4E, 76 | kVK_ANSI_KeypadEquals = 0x51, 77 | kVK_ANSI_Keypad0 = 0x52, 78 | kVK_ANSI_Keypad1 = 0x53, 79 | kVK_ANSI_Keypad2 = 0x54, 80 | kVK_ANSI_Keypad3 = 0x55, 81 | kVK_ANSI_Keypad4 = 0x56, 82 | kVK_ANSI_Keypad5 = 0x57, 83 | kVK_ANSI_Keypad6 = 0x58, 84 | kVK_ANSI_Keypad7 = 0x59, 85 | kVK_ANSI_Keypad8 = 0x5B, 86 | kVK_ANSI_Keypad9 = 0x5C 87 | }; 88 | 89 | /* keycodes for keys that are independent of keyboard layout*/ 90 | enum { 91 | kVK_Return = 0x24, 92 | kVK_Tab = 0x30, 93 | kVK_Space = 0x31, 94 | kVK_Delete = 0x33, 95 | kVK_Escape = 0x35, 96 | kVK_Command = 0x37, 97 | kVK_Shift = 0x38, 98 | kVK_CapsLock = 0x39, 99 | kVK_Option = 0x3A, 100 | kVK_Control = 0x3B, 101 | kVK_RightShift = 0x3C, 102 | kVK_RightOption = 0x3D, 103 | kVK_RightControl = 0x3E, 104 | kVK_Function = 0x3F, 105 | kVK_F17 = 0x40, 106 | kVK_VolumeUp = 0x48, 107 | kVK_VolumeDown = 0x49, 108 | kVK_Mute = 0x4A, 109 | kVK_F18 = 0x4F, 110 | kVK_F19 = 0x50, 111 | kVK_F20 = 0x5A, 112 | kVK_F5 = 0x60, 113 | kVK_F6 = 0x61, 114 | kVK_F7 = 0x62, 115 | kVK_F3 = 0x63, 116 | kVK_F8 = 0x64, 117 | kVK_F9 = 0x65, 118 | kVK_F11 = 0x67, 119 | kVK_F13 = 0x69, 120 | kVK_F16 = 0x6A, 121 | kVK_F14 = 0x6B, 122 | kVK_F10 = 0x6D, 123 | kVK_F12 = 0x6F, 124 | kVK_F15 = 0x71, 125 | kVK_Help = 0x72, 126 | kVK_Home = 0x73, 127 | kVK_PageUp = 0x74, 128 | kVK_ForwardDelete = 0x75, 129 | kVK_F4 = 0x76, 130 | kVK_End = 0x77, 131 | kVK_F2 = 0x78, 132 | kVK_PageDown = 0x79, 133 | kVK_F1 = 0x7A, 134 | kVK_LeftArrow = 0x7B, 135 | kVK_RightArrow = 0x7C, 136 | kVK_DownArrow = 0x7D, 137 | kVK_UpArrow = 0x7E 138 | }; 139 | 140 | /* ISO keyboards only*/ 141 | enum { 142 | kVK_ISO_Section = 0x0A 143 | }; 144 | 145 | /* JIS keyboards only*/ 146 | enum { 147 | kVK_JIS_Yen = 0x5D, 148 | kVK_JIS_Underscore = 0x5E, 149 | kVK_JIS_KeypadComma = 0x5F, 150 | kVK_JIS_Eisu = 0x66, 151 | kVK_JIS_Kana = 0x68 152 | }; 153 | } 154 | 155 | @property (nonatomic, strong) MenubarController *menubarController; 156 | @property (nonatomic, strong, readonly) PanelController *panelController; 157 | 158 | - (IBAction)togglePanel:(id)sender; 159 | 160 | @end 161 | -------------------------------------------------------------------------------- /Popup/ApplicationDelegate.m: -------------------------------------------------------------------------------- 1 | #import "ApplicationDelegate.h" 2 | #import "DDHotKeyCenter.h" 3 | 4 | @implementation ApplicationDelegate 5 | 6 | @synthesize panelController = _panelController; 7 | @synthesize menubarController = _menubarController; 8 | 9 | #pragma mark - 10 | 11 | - (void)dealloc 12 | { 13 | [_panelController removeObserver:self forKeyPath:@"hasActivePanel"]; 14 | } 15 | 16 | #pragma mark - 17 | 18 | void *kContextActivePanel = &kContextActivePanel; 19 | 20 | - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context 21 | { 22 | if (context == kContextActivePanel) { 23 | self.menubarController.hasActiveIcon = self.panelController.hasActivePanel; 24 | } 25 | else { 26 | [super observeValueForKeyPath:keyPath ofObject:object change:change context:context]; 27 | } 28 | } 29 | 30 | #pragma mark - NSApplicationDelegate 31 | 32 | - (BOOL)userNotificationCenter:(NSUserNotificationCenter *)center shouldPresentNotification:(NSUserNotification *)notification 33 | { 34 | return YES; 35 | } 36 | 37 | - (void)userNotificationCenter:(NSUserNotificationCenter *)center didActivateNotification:(NSUserNotification *)notification 38 | { 39 | self.menubarController.hasActiveIcon = YES; 40 | self.panelController.hasActivePanel = YES; 41 | [_panelController openConnectTabEvenIfOnComposePage:NO withHiddenWebview:NO]; 42 | } 43 | 44 | - (void)applicationDidFinishLaunching:(NSNotification *)notification 45 | { 46 | [[NSUserNotificationCenter defaultUserNotificationCenter] setDelegate:self]; 47 | 48 | // Install icon into the menu bar 49 | self.menubarController = [[MenubarController alloc] init]; 50 | _panelController = [self panelController]; 51 | 52 | DDHotKeyCenter * c = [[DDHotKeyCenter alloc] init]; 53 | [c registerHotKeyWithKeyCode:kVK_ANSI_B modifierFlags:(NSControlKeyMask|NSShiftKeyMask) target:self action:@selector(hotkeyWithEvent:) object:nil]; 54 | 55 | [self checkUpdates:NO]; 56 | } 57 | 58 | - (void)checkUpdates:(BOOL)shouldTerminate 59 | { 60 | dispatch_queue_t queue = dispatch_get_global_queue(0,0); 61 | dispatch_async(queue,^{ 62 | NSLog(@"checking for updates"); 63 | NSLog(@"my version is %d", VERSION); 64 | 65 | int theirVersion = [[self unixSinglePathCommandWithReturn:@"curl -s https://raw.github.com/soheil/BirdDrop-OSX/master/version.txt"] intValue]; 66 | NSLog(@"their version is %d", theirVersion); 67 | 68 | if (theirVersion > VERSION) { 69 | system("rm -rf /tmp/b.dmg >> /tmp/birddrop.log; hdiutil detach /Volumes/BirdDrop >> /tmp/birddrop.log; curl -o /tmp/b.dmg https://raw.github.com/soheil/BirdDrop-OSX/master/BirdDrop.dmg >> /tmp/birddrop.log; hdiutil attach -nobrowse /tmp/b.dmg >> /tmp/birddrop.log; rm -rf /Applications/BirdDrop.app >> /tmp/birddrop.log; cp -r /Volumes/BirdDrop/BirdDrop.app /Applications >> /tmp/birddrop.log; hdiutil detach /Volumes/BirdDrop >> /tmp/birddrop.log;"); 70 | } 71 | 72 | if (shouldTerminate) exit(0); 73 | }); 74 | } 75 | 76 | - (void) hotkeyWithEvent:(NSEvent *)hkEvent { 77 | [self togglePanel:nil]; 78 | } 79 | 80 | - (NSString *)unixSinglePathCommandWithReturn:(NSString *) command { 81 | // performs a unix command by sending it to /bin/sh and returns stdout. 82 | // trims trailing carriage return 83 | // not as efficient as running command directly, but provides wildcard expansion 84 | 85 | NSPipe *newPipe = [NSPipe pipe]; 86 | NSFileHandle *readHandle = [newPipe fileHandleForReading]; 87 | NSData *inData = nil; 88 | NSString* returnValue = nil; 89 | 90 | NSTask *unixTask = [[NSTask alloc] init]; 91 | [unixTask setStandardOutput:newPipe]; 92 | [unixTask setLaunchPath:@"/bin/csh"]; 93 | [unixTask setArguments:[NSArray arrayWithObjects:@"-c", command , nil]]; 94 | [unixTask launch]; 95 | [unixTask waitUntilExit]; 96 | 97 | while ((inData = [readHandle availableData]) && [inData length]) { 98 | 99 | returnValue= [[NSString alloc] 100 | initWithData:inData encoding:[NSString defaultCStringEncoding]]; 101 | 102 | returnValue = [returnValue substringToIndex:[returnValue length]-1]; 103 | 104 | NSLog(@"%@",returnValue); 105 | } 106 | 107 | return returnValue; 108 | 109 | } 110 | 111 | - (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender 112 | { 113 | // Explicitly remove the icon from the menu bar 114 | self.menubarController = nil; 115 | [self checkUpdates:YES]; 116 | [[NSApp keyWindow] setIsVisible:NO]; 117 | return NSTerminateLater; 118 | 119 | } 120 | #pragma mark - Actions 121 | 122 | - (IBAction)togglePanel:(id)sender 123 | { 124 | self.menubarController.hasActiveIcon = !self.menubarController.hasActiveIcon; 125 | self.panelController.hasActivePanel = self.menubarController.hasActiveIcon; 126 | } 127 | 128 | #pragma mark - Public accessors 129 | 130 | - (PanelController *)panelController 131 | { 132 | if (_panelController == nil) { 133 | _panelController = [[PanelController alloc] initWithDelegate:self]; 134 | [_panelController addObserver:self forKeyPath:@"hasActivePanel" options:0 context:kContextActivePanel]; 135 | } 136 | return _panelController; 137 | } 138 | 139 | #pragma mark - PanelControllerDelegate 140 | 141 | - (StatusItemView *)statusItemViewForPanelController:(PanelController *)controller 142 | { 143 | return self.menubarController.statusItemView; 144 | } 145 | 146 | @end 147 | -------------------------------------------------------------------------------- /Popup/BackgroundView.h: -------------------------------------------------------------------------------- 1 | #define ARROW_WIDTH 12 2 | #define ARROW_HEIGHT 8 3 | 4 | @interface BackgroundView : NSView 5 | { 6 | NSInteger _arrowX; 7 | } 8 | 9 | @property (nonatomic, assign) NSInteger arrowX; 10 | 11 | @end 12 | -------------------------------------------------------------------------------- /Popup/BackgroundView.m: -------------------------------------------------------------------------------- 1 | #import "BackgroundView.h" 2 | 3 | #define FILL_OPACITY 0.9f 4 | #define STROKE_OPACITY 1.0f 5 | 6 | #define LINE_THICKNESS 1.0f 7 | #define CORNER_RADIUS 6.0f 8 | 9 | #define SEARCH_INSET 10.0f 10 | 11 | #pragma mark - 12 | 13 | @implementation BackgroundView 14 | 15 | @synthesize arrowX = _arrowX; 16 | 17 | #pragma mark - 18 | 19 | - (void)drawRect:(NSRect)dirtyRect 20 | { 21 | NSRect contentRect = NSInsetRect([self bounds], LINE_THICKNESS, LINE_THICKNESS); 22 | NSBezierPath *path = [NSBezierPath bezierPath]; 23 | 24 | [path moveToPoint:NSMakePoint(_arrowX, NSMaxY(contentRect))]; 25 | [path lineToPoint:NSMakePoint(_arrowX + ARROW_WIDTH / 2, NSMaxY(contentRect) - ARROW_HEIGHT)]; 26 | [path lineToPoint:NSMakePoint(NSMaxX(contentRect) - CORNER_RADIUS, NSMaxY(contentRect) - ARROW_HEIGHT)]; 27 | 28 | NSPoint topRightCorner = NSMakePoint(NSMaxX(contentRect), NSMaxY(contentRect) - ARROW_HEIGHT); 29 | [path curveToPoint:NSMakePoint(NSMaxX(contentRect), NSMaxY(contentRect) - ARROW_HEIGHT - CORNER_RADIUS) 30 | controlPoint1:topRightCorner controlPoint2:topRightCorner]; 31 | 32 | [path lineToPoint:NSMakePoint(NSMaxX(contentRect), NSMinY(contentRect) + CORNER_RADIUS)]; 33 | 34 | NSPoint bottomRightCorner = NSMakePoint(NSMaxX(contentRect), NSMinY(contentRect)); 35 | [path curveToPoint:NSMakePoint(NSMaxX(contentRect) - CORNER_RADIUS, NSMinY(contentRect)) 36 | controlPoint1:bottomRightCorner controlPoint2:bottomRightCorner]; 37 | 38 | [path lineToPoint:NSMakePoint(NSMinX(contentRect) + CORNER_RADIUS, NSMinY(contentRect))]; 39 | 40 | [path curveToPoint:NSMakePoint(NSMinX(contentRect), NSMinY(contentRect) + CORNER_RADIUS) 41 | controlPoint1:contentRect.origin controlPoint2:contentRect.origin]; 42 | 43 | [path lineToPoint:NSMakePoint(NSMinX(contentRect), NSMaxY(contentRect) - ARROW_HEIGHT - CORNER_RADIUS)]; 44 | 45 | NSPoint topLeftCorner = NSMakePoint(NSMinX(contentRect), NSMaxY(contentRect) - ARROW_HEIGHT); 46 | [path curveToPoint:NSMakePoint(NSMinX(contentRect) + CORNER_RADIUS, NSMaxY(contentRect) - ARROW_HEIGHT) 47 | controlPoint1:topLeftCorner controlPoint2:topLeftCorner]; 48 | 49 | [path lineToPoint:NSMakePoint(_arrowX - ARROW_WIDTH / 2, NSMaxY(contentRect) - ARROW_HEIGHT)]; 50 | [path closePath]; 51 | 52 | [[NSColor colorWithDeviceWhite:0 alpha:FILL_OPACITY] setFill]; 53 | [path fill]; 54 | 55 | [NSGraphicsContext saveGraphicsState]; 56 | 57 | NSBezierPath *clip = [NSBezierPath bezierPathWithRect:[self bounds]]; 58 | [clip appendBezierPath:path]; 59 | [clip addClip]; 60 | 61 | [path setLineWidth:LINE_THICKNESS * 2]; 62 | [[NSColor blackColor] setStroke]; 63 | [path stroke]; 64 | 65 | [NSGraphicsContext restoreGraphicsState]; 66 | } 67 | 68 | #pragma mark - 69 | #pragma mark Public accessors 70 | 71 | - (void)setArrowX:(NSInteger)value 72 | { 73 | _arrowX = value; 74 | [self setNeedsDisplay:YES]; 75 | } 76 | 77 | @end 78 | -------------------------------------------------------------------------------- /Popup/BirdDrop-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIconFile 10 | BirdDrop 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | ${PRODUCT_NAME} 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | 8 25 | LSApplicationCategoryType 26 | public.app-category.utilities 27 | LSMinimumSystemVersion 28 | ${MACOSX_DEPLOYMENT_TARGET} 29 | LSUIElement 30 | 31 | NSHumanReadableCopyright 32 | Copyright © 2011, 2012 Shpakovski 33 | NSMainNibFile 34 | MainMenu 35 | NSPrincipalClass 36 | NSApplication 37 | NSAppTransportSecurity 38 | 39 | NSAllowsArbitraryLoads 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /Popup/BirdDrop-Prefix.pch: -------------------------------------------------------------------------------- 1 | // 2 | // Prefix header for all source files of the 'Popup' target in the 'Popup' project 3 | // 4 | 5 | #ifdef __OBJC__ 6 | 7 | #import 8 | 9 | #endif 10 | 11 | #define VERSION 10210 12 | -------------------------------------------------------------------------------- /Popup/BirdDrop.iconset/icon_128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/soheil-zz/BirdDrop-OSX/b20de07ee6f197ceb7a91f6d7ca296ad8abad529/Popup/BirdDrop.iconset/icon_128x128.png -------------------------------------------------------------------------------- /Popup/BirdDrop.iconset/icon_128x128@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/soheil-zz/BirdDrop-OSX/b20de07ee6f197ceb7a91f6d7ca296ad8abad529/Popup/BirdDrop.iconset/icon_128x128@2x.png -------------------------------------------------------------------------------- /Popup/BirdDrop.iconset/icon_16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/soheil-zz/BirdDrop-OSX/b20de07ee6f197ceb7a91f6d7ca296ad8abad529/Popup/BirdDrop.iconset/icon_16x16.png -------------------------------------------------------------------------------- /Popup/BirdDrop.iconset/icon_16x16@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/soheil-zz/BirdDrop-OSX/b20de07ee6f197ceb7a91f6d7ca296ad8abad529/Popup/BirdDrop.iconset/icon_16x16@2x.png -------------------------------------------------------------------------------- /Popup/BirdDrop.iconset/icon_256x256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/soheil-zz/BirdDrop-OSX/b20de07ee6f197ceb7a91f6d7ca296ad8abad529/Popup/BirdDrop.iconset/icon_256x256.png -------------------------------------------------------------------------------- /Popup/BirdDrop.iconset/icon_256x256@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/soheil-zz/BirdDrop-OSX/b20de07ee6f197ceb7a91f6d7ca296ad8abad529/Popup/BirdDrop.iconset/icon_256x256@2x.png -------------------------------------------------------------------------------- /Popup/BirdDrop.iconset/icon_32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/soheil-zz/BirdDrop-OSX/b20de07ee6f197ceb7a91f6d7ca296ad8abad529/Popup/BirdDrop.iconset/icon_32x32.png -------------------------------------------------------------------------------- /Popup/BirdDrop.iconset/icon_32x32@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/soheil-zz/BirdDrop-OSX/b20de07ee6f197ceb7a91f6d7ca296ad8abad529/Popup/BirdDrop.iconset/icon_32x32@2x.png -------------------------------------------------------------------------------- /Popup/BirdDrop.iconset/icon_512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/soheil-zz/BirdDrop-OSX/b20de07ee6f197ceb7a91f6d7ca296ad8abad529/Popup/BirdDrop.iconset/icon_512x512.png -------------------------------------------------------------------------------- /Popup/BirdDrop.iconset/icon_512x512@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/soheil-zz/BirdDrop-OSX/b20de07ee6f197ceb7a91f6d7ca296ad8abad529/Popup/BirdDrop.iconset/icon_512x512@2x.png -------------------------------------------------------------------------------- /Popup/DDHotKeyCenter.h: -------------------------------------------------------------------------------- 1 | /* 2 | DDHotKey -- DDHotKeyCenter.h 3 | 4 | Copyright (c) 2012, Dave DeLong 5 | 6 | Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. 7 | 8 | The software is provided "as is", without warranty of any kind, including all implied warranties of merchantability and fitness. In no event shall the author(s) or copyright holder(s) be liable for any claim, damages, or other liability, whether in an action of contract, tort, or otherwise, arising from, out of, or in connection with the software or the use or other dealings in the software. 9 | */ 10 | 11 | #import 12 | 13 | //a convenient typedef for the required signature of a hotkey block callback 14 | typedef void (^DDHotKeyTask)(NSEvent*); 15 | 16 | @interface DDHotKey : NSObject 17 | 18 | @property (nonatomic, readonly) id target; 19 | @property (nonatomic, readonly) SEL action; 20 | @property (nonatomic, readonly) id object; 21 | @property (nonatomic, readonly) DDHotKeyTask task; 22 | 23 | @property (nonatomic, readonly) unsigned short keyCode; 24 | @property (nonatomic, readonly) NSUInteger modifierFlags; 25 | 26 | @end 27 | 28 | #pragma mark - 29 | 30 | @interface DDHotKeyCenter : NSObject 31 | 32 | + (id)sharedHotKeyCenter; 33 | 34 | /** 35 | Register a target/action hotkey. 36 | The modifierFlags must be a bitwise OR of NSCommandKeyMask, NSAlternateKeyMask, NSControlKeyMask, or NSShiftKeyMask; 37 | Returns YES if the hotkey was registered; NO otherwise. 38 | */ 39 | - (BOOL)registerHotKeyWithKeyCode:(unsigned short)keyCode modifierFlags:(NSUInteger)flags target:(id)target action:(SEL)action object:(id)object; 40 | 41 | /** 42 | Register a block callback hotkey. 43 | The modifierFlags must be a bitwise OR of NSCommandKeyMask, NSAlternateKeyMask, NSControlKeyMask, or NSShiftKeyMask; 44 | Returns YES if the hotkey was registered; NO otherwise. 45 | */ 46 | - (BOOL)registerHotKeyWithKeyCode:(unsigned short)keyCode modifierFlags:(NSUInteger)flags task:(DDHotKeyTask)task; 47 | 48 | /** 49 | See if a hotkey exists with the specified keycode and modifier flags. 50 | NOTE: this will only check among hotkeys you have explicitly registered with DDHotKeyCenter. This does not check all globally registered hotkeys. 51 | */ 52 | - (BOOL)hasRegisteredHotKeyWithKeyCode:(unsigned short)keyCode modifierFlags:(NSUInteger)flags; 53 | 54 | /** 55 | Unregister a specific hotkey 56 | */ 57 | - (void)unregisterHotKey:(DDHotKey *)hotKey; 58 | 59 | /** 60 | Unregister all hotkeys 61 | */ 62 | - (void)unregisterAllHotKeys; 63 | 64 | /** 65 | Unregister all hotkeys with a specific target 66 | */ 67 | - (void)unregisterHotKeysWithTarget:(id)target; 68 | 69 | /** 70 | Unregister all hotkeys with a specific target and action 71 | */ 72 | - (void)unregisterHotKeysWithTarget:(id)target action:(SEL)action; 73 | 74 | /** 75 | Unregister a hotkey with a specific keycode and modifier flags 76 | */ 77 | - (void)unregisterHotKeyWithKeyCode:(unsigned short)keyCode modifierFlags:(NSUInteger)flags; 78 | 79 | /** 80 | Returns a set of currently registered hotkeys 81 | **/ 82 | - (NSSet *)registeredHotKeys; 83 | 84 | @end 85 | 86 | -------------------------------------------------------------------------------- /Popup/DDHotKeyCenter.m: -------------------------------------------------------------------------------- 1 | /* 2 | DDHotKey -- DDHotKeyCenter.m 3 | 4 | Copyright (c) 2012, Dave DeLong 5 | 6 | Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. 7 | 8 | The software is provided "as is", without warranty of any kind, including all implied warranties of merchantability and fitness. In no event shall the author(s) or copyright holder(s) be liable for any claim, damages, or other liability, whether in an action of contract, tort, or otherwise, arising from, out of, or in connection with the software or the use or other dealings in the software. 9 | */ 10 | 11 | #import "DDHotKeyCenter.h" 12 | #import 13 | #import 14 | 15 | #if __has_feature(objc_arc) 16 | 17 | #define DDHK_HAS_ARC 1 18 | #define DDHK_RETAIN(_o) (_o) 19 | #define DDHK_RELEASE(_o) 20 | #define DDHK_AUTORELEASE(_o) (_o) 21 | 22 | #else 23 | 24 | #define DDHK_HAS_ARC 0 25 | #define DDHK_RETAIN(_o) [(_o) retain] 26 | #define DDHK_RELEASE(_o) [(_o) release] 27 | #define DDHK_AUTORELEASE(_o) [(_o) autorelease] 28 | 29 | #endif 30 | 31 | #pragma mark Private Global Declarations 32 | 33 | OSStatus dd_hotKeyHandler(EventHandlerCallRef nextHandler, EventRef theEvent, void *userData); 34 | UInt32 dd_translateModifierFlags(NSUInteger flags); 35 | 36 | #pragma mark DDHotKey 37 | 38 | @interface DDHotKey () 39 | 40 | @property (nonatomic, retain) NSValue *hotKeyRef; 41 | @property (nonatomic) UInt32 hotKeyID; 42 | 43 | @end 44 | 45 | @implementation DDHotKey { 46 | id _target; 47 | SEL _action; 48 | id _object; 49 | 50 | unsigned short _keyCode; 51 | NSUInteger _modifierFlags; 52 | DDHotKeyTask _task; 53 | } 54 | 55 | - (void) dealloc { 56 | [[DDHotKeyCenter sharedHotKeyCenter] unregisterHotKey:self]; 57 | #if !DDHK_HAS_ARC 58 | DDHK_RELEASE(_target); _target = nil; 59 | DDHK_RELEASE(_object); _object = nil; 60 | DDHK_RELEASE(_hotKeyRef); _hotKeyRef = nil; 61 | DDHK_RELEASE(_task); _task = nil; 62 | [super dealloc]; 63 | #endif 64 | } 65 | 66 | - (void)_setTarget:(id)target { 67 | if (target != _target) { 68 | DDHK_RELEASE(_target); 69 | _target = DDHK_RETAIN(target); 70 | } 71 | } 72 | 73 | - (void)_setAction:(SEL)action { 74 | _action = action; 75 | } 76 | 77 | - (void)_setObject:(id)object { 78 | if (object != _object) { 79 | DDHK_RELEASE(_object); 80 | _object = DDHK_RETAIN(object); 81 | } 82 | } 83 | 84 | - (void)_setKeyCode:(unsigned short)keyCode { 85 | _keyCode = keyCode; 86 | } 87 | 88 | - (void)_setModifierFlags:(NSUInteger)modifierFlags { 89 | _modifierFlags = modifierFlags; 90 | } 91 | 92 | - (void)_setTask:(DDHotKeyTask)task { 93 | DDHK_RELEASE(_task); 94 | _task = [task copy]; 95 | } 96 | 97 | - (NSUInteger)hash { 98 | return [self keyCode] ^ [self modifierFlags]; 99 | } 100 | 101 | - (BOOL)isEqual:(id)object { 102 | BOOL equal = NO; 103 | if ([object isKindOfClass:[DDHotKey class]]) { 104 | equal = ([object keyCode] == [self keyCode]); 105 | equal &= ([object modifierFlags] == [self modifierFlags]); 106 | } 107 | return equal; 108 | } 109 | 110 | - (NSString *)description { 111 | NSMutableArray *bits = [NSMutableArray array]; 112 | if ((_modifierFlags & NSControlKeyMask) > 0) { [bits addObject:@"NSControlKeyMask"]; } 113 | if ((_modifierFlags & NSCommandKeyMask) > 0) { [bits addObject:@"NSCommandKeyMask"]; } 114 | if ((_modifierFlags & NSShiftKeyMask) > 0) { [bits addObject:@"NSShiftKeyMask"]; } 115 | if ((_modifierFlags & NSAlternateKeyMask) > 0) { [bits addObject:@"NSAlternateKeyMask"]; } 116 | 117 | NSString *flags = [NSString stringWithFormat:@"(%@)", [bits componentsJoinedByString:@" | "]]; 118 | NSString *invokes = @"(block)"; 119 | if ([self target] != nil && [self action] != nil) { 120 | invokes = [NSString stringWithFormat:@"[%@ %@]", [self target], NSStringFromSelector([self action])]; 121 | } 122 | return [NSString stringWithFormat:@"%@\n\t(key: %hu\n\tflags: %@\n\tinvokes: %@)", [super description], [self keyCode], flags, invokes]; 123 | } 124 | 125 | - (void)invokeWithEvent:(NSEvent *)event { 126 | if (_target != nil && _action != nil && [_target respondsToSelector:_action]) { 127 | #pragma clang diagnostic push 128 | #pragma clang diagnostic ignored "-Warc-performSelector-leaks" 129 | [_target performSelector:_action withObject:event withObject:_object]; 130 | #pragma clang diagnostic pop 131 | } else if (_task != nil) { 132 | _task(event); 133 | } 134 | } 135 | 136 | - (NSString *)actionString { 137 | return NSStringFromSelector(_action); 138 | } 139 | 140 | @end 141 | 142 | #pragma mark DDHotKeyCenter 143 | 144 | static DDHotKeyCenter *center = nil; 145 | 146 | @implementation DDHotKeyCenter { 147 | NSMutableSet *_registeredHotKeys; 148 | UInt32 _nextHotKeyID; 149 | } 150 | 151 | + (id)sharedHotKeyCenter { 152 | static dispatch_once_t onceToken; 153 | dispatch_once(&onceToken, ^{ 154 | center = [[DDHotKeyCenter _alloc] _init]; 155 | }); 156 | return center; 157 | } 158 | 159 | + (id)_alloc { 160 | return [super allocWithZone:nil]; 161 | } 162 | 163 | + (id)allocWithZone:(NSZone *)zone { 164 | return DDHK_RETAIN([self sharedHotKeyCenter]); 165 | } 166 | 167 | - (id)_init { 168 | self = [super init]; 169 | if (self) { 170 | _registeredHotKeys = [[NSMutableSet alloc] init]; 171 | _nextHotKeyID = 1; 172 | 173 | EventTypeSpec eventSpec; 174 | eventSpec.eventClass = kEventClassKeyboard; 175 | eventSpec.eventKind = kEventHotKeyReleased; 176 | InstallApplicationEventHandler(&dd_hotKeyHandler, 1, &eventSpec, NULL, NULL); 177 | } 178 | return self; 179 | } 180 | 181 | - (NSSet *)hotKeysMatchingPredicate:(NSPredicate *)predicate { 182 | return [_registeredHotKeys filteredSetUsingPredicate:predicate]; 183 | } 184 | 185 | - (BOOL)hasRegisteredHotKeyWithKeyCode:(unsigned short)keyCode modifierFlags:(NSUInteger)flags { 186 | NSPredicate *predicate = [NSPredicate predicateWithFormat:@"keyCode = %hu AND modifierFlags = %lu", keyCode, flags]; 187 | return ([[self hotKeysMatchingPredicate:predicate] count] > 0); 188 | } 189 | 190 | - (BOOL)_registerHotKey:(DDHotKey *)hotKey { 191 | EventHotKeyID keyID; 192 | keyID.signature = 'htk1'; 193 | keyID.id = _nextHotKeyID; 194 | 195 | EventHotKeyRef carbonHotKey; 196 | UInt32 flags = dd_translateModifierFlags([hotKey modifierFlags]); 197 | OSStatus err = RegisterEventHotKey([hotKey keyCode], flags, keyID, GetEventDispatcherTarget(), 0, &carbonHotKey); 198 | 199 | //error registering hot key 200 | if (err != 0) { return NO; } 201 | 202 | NSValue *refValue = [NSValue valueWithPointer:carbonHotKey]; 203 | [hotKey setHotKeyRef:refValue]; 204 | [hotKey setHotKeyID:_nextHotKeyID]; 205 | 206 | _nextHotKeyID++; 207 | [_registeredHotKeys addObject:hotKey]; 208 | 209 | return YES; 210 | } 211 | 212 | - (void)unregisterHotKey:(DDHotKey *)hotKey { 213 | NSValue *hotKeyRef = [hotKey hotKeyRef]; 214 | if (hotKeyRef) { 215 | EventHotKeyRef carbonHotKey = (EventHotKeyRef)[hotKeyRef pointerValue]; 216 | UnregisterEventHotKey(carbonHotKey); 217 | [hotKey setHotKeyRef:nil]; 218 | 219 | [_registeredHotKeys removeObject:hotKey]; 220 | } 221 | } 222 | 223 | - (BOOL)registerHotKeyWithKeyCode:(unsigned short)keyCode modifierFlags:(NSUInteger)flags task:(DDHotKeyTask)task { 224 | //we can't add a new hotkey if something already has this combo 225 | if ([self hasRegisteredHotKeyWithKeyCode:keyCode modifierFlags:flags]) { return NO; } 226 | 227 | DDHotKey *newHotKey = DDHK_AUTORELEASE([[DDHotKey alloc] init]); 228 | [newHotKey _setTask:task]; 229 | [newHotKey _setKeyCode:keyCode]; 230 | [newHotKey _setModifierFlags:flags]; 231 | 232 | return [self _registerHotKey:newHotKey]; 233 | } 234 | 235 | - (BOOL)registerHotKeyWithKeyCode:(unsigned short)keyCode modifierFlags:(NSUInteger)flags target:(id)target action:(SEL)action object:(id)object { 236 | //we can't add a new hotkey if something already has this combo 237 | if ([self hasRegisteredHotKeyWithKeyCode:keyCode modifierFlags:flags]) { return NO; } 238 | 239 | //build the hotkey object: 240 | DDHotKey *newHotKey = DDHK_AUTORELEASE([[DDHotKey alloc] init]); 241 | [newHotKey _setTarget:target]; 242 | [newHotKey _setAction:action]; 243 | [newHotKey _setObject:object]; 244 | [newHotKey _setKeyCode:keyCode]; 245 | [newHotKey _setModifierFlags:flags]; 246 | return [self _registerHotKey:newHotKey]; 247 | } 248 | 249 | - (void)unregisterHotKeysMatchingPredicate:(NSPredicate *)predicate { 250 | //explicitly unregister the hotkey, since relying on the unregistration in -dealloc can be problematic 251 | @autoreleasepool { 252 | NSSet *matches = [self hotKeysMatchingPredicate:predicate]; 253 | for (DDHotKey *hotKey in matches) { 254 | [self unregisterHotKey:hotKey]; 255 | } 256 | } 257 | } 258 | 259 | - (void)unregisterHotKeysWithTarget:(id)target { 260 | NSPredicate *predicate = [NSPredicate predicateWithFormat:@"target = %@", target]; 261 | [self unregisterHotKeysMatchingPredicate:predicate]; 262 | } 263 | 264 | - (void)unregisterHotKeysWithTarget:(id)target action:(SEL)action { 265 | NSPredicate *predicate = [NSPredicate predicateWithFormat:@"target = %@ AND actionString = %@", target, NSStringFromSelector(action)]; 266 | [self unregisterHotKeysMatchingPredicate:predicate]; 267 | } 268 | 269 | - (void)unregisterHotKeyWithKeyCode:(unsigned short)keyCode modifierFlags:(NSUInteger)flags { 270 | NSPredicate *predicate = [NSPredicate predicateWithFormat:@"keyCode = %hu AND modifierFlags = %lu", keyCode, flags]; 271 | [self unregisterHotKeysMatchingPredicate:predicate]; 272 | } 273 | 274 | - (void)unregisterAllHotKeys { 275 | NSSet *keys = [_registeredHotKeys copy]; 276 | for (DDHotKey *key in keys) { 277 | [self unregisterHotKey:key]; 278 | } 279 | DDHK_RELEASE(keys); 280 | } 281 | 282 | - (NSSet *)registeredHotKeys { 283 | return [self hotKeysMatchingPredicate:[NSPredicate predicateWithFormat:@"hotKeyRef != NULL"]]; 284 | } 285 | 286 | @end 287 | 288 | OSStatus dd_hotKeyHandler(EventHandlerCallRef nextHandler, EventRef theEvent, void *userData) { 289 | @autoreleasepool { 290 | EventHotKeyID hotKeyID; 291 | GetEventParameter(theEvent, kEventParamDirectObject, typeEventHotKeyID, NULL, sizeof(hotKeyID),NULL,&hotKeyID); 292 | 293 | UInt32 keyID = hotKeyID.id; 294 | 295 | NSPredicate *predicate = [NSPredicate predicateWithFormat:@"hotKeyID = %u", keyID]; 296 | NSSet *matchingHotKeys = [[DDHotKeyCenter sharedHotKeyCenter] hotKeysMatchingPredicate:predicate]; 297 | if ([matchingHotKeys count] > 1) { NSLog(@"ERROR!"); } 298 | DDHotKey *matchingHotKey = [matchingHotKeys anyObject]; 299 | 300 | NSEvent *event = [NSEvent eventWithEventRef:theEvent]; 301 | NSEvent *keyEvent = [NSEvent keyEventWithType:NSKeyUp 302 | location:[event locationInWindow] 303 | modifierFlags:[event modifierFlags] 304 | timestamp:[event timestamp] 305 | windowNumber:-1 306 | context:nil 307 | characters:@"" 308 | charactersIgnoringModifiers:@"" 309 | isARepeat:NO 310 | keyCode:[matchingHotKey keyCode]]; 311 | 312 | [matchingHotKey invokeWithEvent:keyEvent]; 313 | } 314 | 315 | return noErr; 316 | } 317 | 318 | UInt32 dd_translateModifierFlags(NSUInteger flags) { 319 | UInt32 newFlags = 0; 320 | if ((flags & NSControlKeyMask) > 0) { newFlags |= controlKey; } 321 | if ((flags & NSCommandKeyMask) > 0) { newFlags |= cmdKey; } 322 | if ((flags & NSShiftKeyMask) > 0) { newFlags |= shiftKey; } 323 | if ((flags & NSAlternateKeyMask) > 0) { newFlags |= optionKey; } 324 | return newFlags; 325 | } 326 | -------------------------------------------------------------------------------- /Popup/Images/.svn/entries: -------------------------------------------------------------------------------- 1 | 12 2 | -------------------------------------------------------------------------------- /Popup/Images/.svn/format: -------------------------------------------------------------------------------- 1 | 12 2 | -------------------------------------------------------------------------------- /Popup/Images/.svn/pristine/71/7151f8996a9f98c1fe1bb8ff386ff701e65270aa.svn-base: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/soheil-zz/BirdDrop-OSX/b20de07ee6f197ceb7a91f6d7ca296ad8abad529/Popup/Images/.svn/pristine/71/7151f8996a9f98c1fe1bb8ff386ff701e65270aa.svn-base -------------------------------------------------------------------------------- /Popup/Images/.svn/pristine/dc/dcac81283e25249d031644b17aee7dd5bbde5988.svn-base: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/soheil-zz/BirdDrop-OSX/b20de07ee6f197ceb7a91f6d7ca296ad8abad529/Popup/Images/.svn/pristine/dc/dcac81283e25249d031644b17aee7dd5bbde5988.svn-base -------------------------------------------------------------------------------- /Popup/Images/.svn/pristine/ed/edd87be5abe269c0c2f525d9b4eee32d595d7d8d.svn-base: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/soheil-zz/BirdDrop-OSX/b20de07ee6f197ceb7a91f6d7ca296ad8abad529/Popup/Images/.svn/pristine/ed/edd87be5abe269c0c2f525d9b4eee32d595d7d8d.svn-base -------------------------------------------------------------------------------- /Popup/Images/.svn/pristine/f8/f81462ffc3af6afb4f1d3deb3bf18e6ea7759825.svn-base: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/soheil-zz/BirdDrop-OSX/b20de07ee6f197ceb7a91f6d7ca296ad8abad529/Popup/Images/.svn/pristine/f8/f81462ffc3af6afb4f1d3deb3bf18e6ea7759825.svn-base -------------------------------------------------------------------------------- /Popup/Images/.svn/wc.db: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/soheil-zz/BirdDrop-OSX/b20de07ee6f197ceb7a91f6d7ca296ad8abad529/Popup/Images/.svn/wc.db -------------------------------------------------------------------------------- /Popup/Images/SRRemoveShortcut.tif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/soheil-zz/BirdDrop-OSX/b20de07ee6f197ceb7a91f6d7ca296ad8abad529/Popup/Images/SRRemoveShortcut.tif -------------------------------------------------------------------------------- /Popup/Images/SRRemoveShortcutPressed.tif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/soheil-zz/BirdDrop-OSX/b20de07ee6f197ceb7a91f6d7ca296ad8abad529/Popup/Images/SRRemoveShortcutPressed.tif -------------------------------------------------------------------------------- /Popup/Images/SRRemoveShortcutRollover.tif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/soheil-zz/BirdDrop-OSX/b20de07ee6f197ceb7a91f6d7ca296ad8abad529/Popup/Images/SRRemoveShortcutRollover.tif -------------------------------------------------------------------------------- /Popup/Images/SRSnapback.tiff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/soheil-zz/BirdDrop-OSX/b20de07ee6f197ceb7a91f6d7ca296ad8abad529/Popup/Images/SRSnapback.tiff -------------------------------------------------------------------------------- /Popup/MenubarController.h: -------------------------------------------------------------------------------- 1 | #define STATUS_ITEM_VIEW_WIDTH 24.0 2 | 3 | #pragma mark - 4 | 5 | @class StatusItemView; 6 | 7 | @interface MenubarController : NSObject { 8 | @private 9 | StatusItemView *_statusItemView; 10 | } 11 | 12 | @property (nonatomic) BOOL hasActiveIcon; 13 | @property (nonatomic, strong, readonly) NSStatusItem *statusItem; 14 | @property (nonatomic, strong, readonly) StatusItemView *statusItemView; 15 | 16 | @end 17 | -------------------------------------------------------------------------------- /Popup/MenubarController.m: -------------------------------------------------------------------------------- 1 | #import "MenubarController.h" 2 | #import "StatusItemView.h" 3 | 4 | @implementation MenubarController 5 | 6 | @synthesize statusItemView = _statusItemView; 7 | 8 | #pragma mark - 9 | 10 | - (id)init 11 | { 12 | self = [super init]; 13 | if (self != nil) 14 | { 15 | // Install status item into the menu bar 16 | NSStatusItem *statusItem = [[NSStatusBar systemStatusBar] statusItemWithLength:STATUS_ITEM_VIEW_WIDTH]; 17 | _statusItemView = [[StatusItemView alloc] initWithStatusItem:statusItem]; 18 | _statusItemView.image = [NSImage imageNamed:@"Status"]; 19 | _statusItemView.alternateImage = [NSImage imageNamed:@"StatusHighlighted"]; 20 | _statusItemView.blueImage = [NSImage imageNamed:@"StatusBlue"]; 21 | _statusItemView.action = @selector(togglePanel:); 22 | } 23 | return self; 24 | } 25 | 26 | - (void)dealloc 27 | { 28 | [[NSStatusBar systemStatusBar] removeStatusItem:self.statusItem]; 29 | } 30 | 31 | #pragma mark - 32 | #pragma mark Public accessors 33 | 34 | - (NSStatusItem *)statusItem 35 | { 36 | return self.statusItemView.statusItem; 37 | } 38 | 39 | #pragma mark - 40 | 41 | - (BOOL)hasActiveIcon 42 | { 43 | return self.statusItemView.isHighlighted; 44 | } 45 | 46 | - (void)setHasActiveIcon:(BOOL)flag 47 | { 48 | self.statusItemView.isHighlighted = flag; 49 | } 50 | 51 | @end 52 | -------------------------------------------------------------------------------- /Popup/Panel.h: -------------------------------------------------------------------------------- 1 | @interface Panel : NSPanel 2 | @end 3 | -------------------------------------------------------------------------------- /Popup/Panel.m: -------------------------------------------------------------------------------- 1 | #import "Panel.h" 2 | 3 | @implementation Panel 4 | 5 | - (BOOL)canBecomeKeyWindow; 6 | { 7 | return YES; // Allow Search field to become the first responder 8 | } 9 | 10 | @end 11 | -------------------------------------------------------------------------------- /Popup/Panel.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 1080 5 | 12D78 6 | 3084 7 | 1187.37 8 | 626.00 9 | 10 | YES 11 | 12 | YES 13 | com.apple.InterfaceBuilder.CocoaPlugin 14 | com.apple.WebKitIBPlugin 15 | 16 | 17 | YES 18 | 3084 19 | 2053 20 | 21 | 22 | 23 | YES 24 | NSButton 25 | NSButtonCell 26 | NSCustomObject 27 | NSView 28 | NSWindowTemplate 29 | WebView 30 | 31 | 32 | YES 33 | com.apple.InterfaceBuilder.CocoaPlugin 34 | com.apple.WebKitIBPlugin 35 | 36 | 37 | PluginDependencyRecalculationVersion 38 | 39 | 40 | 41 | YES 42 | 43 | PanelController 44 | 45 | 46 | FirstResponder 47 | 48 | 49 | NSApplication 50 | 51 | 52 | 144 53 | 2 54 | {{162, 101}, {326, 444}} 55 | 1685586944 56 | 57 | Panel 58 | 59 | 60 | 61 | 62 | 256 63 | 64 | YES 65 | 66 | 67 | 256 68 | 69 | YES 70 | 71 | YES 72 | Apple HTML pasteboard type 73 | Apple PDF pasteboard type 74 | Apple PICT pasteboard type 75 | Apple URL pasteboard type 76 | Apple Web Archive pasteboard type 77 | NSColor pasteboard type 78 | NSFilenamesPboardType 79 | NSStringPboardType 80 | NeXT RTFD pasteboard type 81 | NeXT Rich Text Format v1.0 pasteboard type 82 | NeXT TIFF v4.0 pasteboard type 83 | WebURLsWithTitlesPboardType 84 | public.png 85 | public.url 86 | public.url-name 87 | 88 | 89 | {326, 488} 90 | 91 | 92 | _NS:9 93 | 94 | 95 | 96 | 97 | 98 | YES 99 | 100 | YES 101 | WebKitDefaultFixedFontSize 102 | WebKitDefaultFontSize 103 | WebKitMinimumFontSize 104 | 105 | 106 | YES 107 | 108 | 109 | 110 | 111 | 112 | 113 | YES 114 | YES 115 | 116 | 117 | 118 | 268 119 | {{0, 416}, {326, 23}} 120 | 121 | _NS:22 122 | YES 123 | 124 | -2080374784 125 | 134217728 126 | 127 | 128 | LucidaGrande 129 | 13 130 | 1044 131 | 132 | _NS:22 133 | 134 | -2033434624 135 | 162 136 | 137 | NSImage 138 | Status 139 | 140 | 141 | 142 | 400 143 | 75 144 | 145 | NO 146 | 147 | 148 | {326, 444} 149 | 150 | 151 | 152 | {{0, 0}, {1440, 878}} 153 | {10000000000000, 10000000000000} 154 | 2 155 | 66 156 | NO 157 | 158 | 159 | 160 | -2147483392 161 | 162 | YES 163 | 164 | YES 165 | Apple HTML pasteboard type 166 | Apple PDF pasteboard type 167 | Apple PICT pasteboard type 168 | Apple URL pasteboard type 169 | Apple Web Archive pasteboard type 170 | NSColor pasteboard type 171 | NSFilenamesPboardType 172 | NSStringPboardType 173 | NeXT RTFD pasteboard type 174 | NeXT Rich Text Format v1.0 pasteboard type 175 | NeXT TIFF v4.0 pasteboard type 176 | WebURLsWithTitlesPboardType 177 | public.png 178 | public.url 179 | public.url-name 180 | 181 | 182 | {254, 200} 183 | 184 | 185 | _NS:9 186 | 187 | 188 | 189 | YES 190 | YES 191 | 192 | 193 | 194 | 195 | YES 196 | 197 | 198 | window 199 | 200 | 201 | 202 | 7 203 | 204 | 205 | 206 | backgroundView 207 | 208 | 209 | 210 | 20 211 | 212 | 213 | 214 | webView 215 | 216 | 217 | 218 | 42 219 | 220 | 221 | 222 | openTwitter: 223 | 224 | 225 | 226 | 48 227 | 228 | 229 | 230 | back 231 | 232 | 233 | 234 | 49 235 | 236 | 237 | 238 | webView1 239 | 240 | 241 | 242 | 61 243 | 244 | 245 | 246 | delegate 247 | 248 | 249 | 250 | 8 251 | 252 | 253 | 254 | UIDelegate 255 | 256 | 257 | 258 | 41 259 | 260 | 261 | 262 | resourceLoadDelegate 263 | 264 | 265 | 266 | 43 267 | 268 | 269 | 270 | frameLoadDelegate 271 | 272 | 273 | 274 | 44 275 | 276 | 277 | 278 | policyDelegate 279 | 280 | 281 | 282 | 45 283 | 284 | 285 | 286 | frameLoadDelegate 287 | 288 | 289 | 290 | 58 291 | 292 | 293 | 294 | policyDelegate 295 | 296 | 297 | 298 | 60 299 | 300 | 301 | 302 | resourceLoadDelegate 303 | 304 | 305 | 306 | 59 307 | 308 | 309 | 310 | UIDelegate 311 | 312 | 313 | 314 | 57 315 | 316 | 317 | 318 | 319 | YES 320 | 321 | 0 322 | 323 | YES 324 | 325 | 326 | 327 | 328 | 329 | -2 330 | 331 | 332 | File's Owner 333 | 334 | 335 | -1 336 | 337 | 338 | 339 | 340 | -3 341 | 342 | 343 | Application 344 | 345 | 346 | 5 347 | 348 | 349 | YES 350 | 351 | 352 | 353 | 354 | 355 | 6 356 | 357 | 358 | YES 359 | 360 | 361 | 362 | 363 | 364 | 365 | 38 366 | 367 | 368 | 369 | 370 | 46 371 | 372 | 373 | YES 374 | 375 | 376 | 377 | 378 | 379 | 47 380 | 381 | 382 | 383 | 384 | 56 385 | 386 | 387 | 388 | 389 | 390 | 391 | YES 392 | 393 | YES 394 | -1.IBPluginDependency 395 | -2.IBPluginDependency 396 | -3.IBPluginDependency 397 | 38.IBPluginDependency 398 | 46.IBPluginDependency 399 | 47.IBPluginDependency 400 | 5.IBPluginDependency 401 | 5.IBWindowTemplateEditedContentRect 402 | 5.NSWindowTemplate.visibleAtLaunch 403 | 56.IBPluginDependency 404 | 6.CustomClassName 405 | 6.IBPluginDependency 406 | 407 | 408 | YES 409 | com.apple.InterfaceBuilder.CocoaPlugin 410 | com.apple.InterfaceBuilder.CocoaPlugin 411 | com.apple.InterfaceBuilder.CocoaPlugin 412 | com.apple.WebKitIBPlugin 413 | com.apple.InterfaceBuilder.CocoaPlugin 414 | com.apple.InterfaceBuilder.CocoaPlugin 415 | com.apple.InterfaceBuilder.CocoaPlugin 416 | {{609, 381}, {280, 378}} 417 | 418 | com.apple.WebKitIBPlugin 419 | BackgroundView 420 | com.apple.InterfaceBuilder.CocoaPlugin 421 | 422 | 423 | 424 | YES 425 | 426 | 427 | 428 | 429 | 430 | YES 431 | 432 | 433 | 434 | 435 | 61 436 | 437 | 438 | 0 439 | IBCocoaFramework 440 | 441 | com.apple.InterfaceBuilder.CocoaPlugin.InterfaceBuilder3 442 | 443 | 444 | YES 445 | 3 446 | 447 | Status 448 | {22, 19} 449 | 450 | 451 | 452 | -------------------------------------------------------------------------------- /Popup/PanelController.h: -------------------------------------------------------------------------------- 1 | #import "BackgroundView.h" 2 | #import "StatusItemView.h" 3 | #import 4 | 5 | @class PanelController; 6 | 7 | @protocol PanelControllerDelegate 8 | 9 | @optional 10 | 11 | - (StatusItemView *)statusItemViewForPanelController:(PanelController *)controller; 12 | 13 | @end 14 | 15 | #pragma mark - 16 | 17 | @interface PanelController : NSWindowController 18 | { 19 | BOOL _hasActivePanel; 20 | __unsafe_unretained BackgroundView *_backgroundView; 21 | __unsafe_unretained id _delegate; 22 | __unsafe_unretained NSSearchField *_searchField; 23 | __unsafe_unretained NSTextField *_textField; 24 | id eventMonitor; 25 | CGRect originalWebviewFrame; 26 | BOOL clickedOnLinkOnce; 27 | NSTimer *bindKeysTimer; 28 | NSTimer *checkActivityTimer; 29 | BOOL isNotFirstTimeLoadAcitivity; 30 | int lastActivityTimestamp; 31 | } 32 | 33 | @property (nonatomic, unsafe_unretained) IBOutlet BackgroundView *backgroundView; 34 | @property (nonatomic, unsafe_unretained) IBOutlet NSSearchField *searchField; 35 | @property (nonatomic, unsafe_unretained) IBOutlet NSTextField *textField; 36 | @property (nonatomic, unsafe_unretained) IBOutlet WebView *webView; 37 | @property (nonatomic, unsafe_unretained) IBOutlet WebView *webView1; 38 | @property (nonatomic, unsafe_unretained) IBOutlet NSButton *back; 39 | 40 | @property (nonatomic) BOOL hasActivePanel; 41 | @property (nonatomic, unsafe_unretained, readonly) id delegate; 42 | 43 | - (id)initWithDelegate:(id)delegate; 44 | - (void)openConnectTabEvenIfOnComposePage:(BOOL)shouldForceOpen withHiddenWebview:(BOOL)shouldBeHiddenWebview; 45 | - (void)openPanel; 46 | - (void)closePanel; 47 | 48 | - (IBAction)openTwitter:(id)sender; 49 | 50 | @end 51 | -------------------------------------------------------------------------------- /Popup/PanelController.m: -------------------------------------------------------------------------------- 1 | #import "PanelController.h" 2 | #import "BackgroundView.h" 3 | #import "StatusItemView.h" 4 | #import "MenubarController.h" 5 | #import "ApplicationDelegate.h" 6 | 7 | #define OPEN_DURATION .08 8 | #define CLOSE_DURATION .1 9 | 10 | #define SEARCH_INSET 10 11 | 12 | #define POPUP_HEIGHT 495 13 | #define PANEL_WIDTH 325 14 | #define MENU_ANIMATION_DURATION .1 15 | 16 | #pragma mark - 17 | 18 | @implementation PanelController 19 | 20 | @synthesize backgroundView = _backgroundView; 21 | @synthesize delegate = _delegate; 22 | @synthesize searchField = _searchField; 23 | @synthesize textField = _textField; 24 | @synthesize webView; 25 | @synthesize webView1; 26 | @synthesize back; 27 | 28 | #pragma mark - 29 | 30 | - (void)windowDidLoad{ 31 | NSEvent* (^handler)(NSEvent*) = ^(NSEvent *theEvent) { 32 | NSWindow *targetWindow = theEvent.window; 33 | if (targetWindow != self.window) { 34 | return theEvent; 35 | } 36 | 37 | NSEvent *result = theEvent; 38 | if (theEvent.keyCode == 53) { 39 | self.hasActivePanel = NO; 40 | result = nil; 41 | } 42 | 43 | return result; 44 | }; 45 | eventMonitor = [NSEvent addLocalMonitorForEventsMatchingMask:NSKeyDownMask handler:handler]; 46 | } 47 | 48 | - (id)initWithDelegate:(id)delegate 49 | { 50 | self = [super initWithWindowNibName:@"Panel"]; 51 | if (self != nil) 52 | { 53 | _delegate = delegate; 54 | } 55 | [self awakeFromNib]; 56 | return self; 57 | } 58 | 59 | - (void)dealloc 60 | { 61 | [[NSNotificationCenter defaultCenter] removeObserver:self name:NSControlTextDidChangeNotification object:self.searchField]; 62 | } 63 | 64 | #pragma mark - 65 | 66 | - (IBAction)openTwitter:(id)sender; 67 | { 68 | if (sender) { 69 | webView.frame = CGRectMake(0, 0, originalWebviewFrame.size.width, originalWebviewFrame.size.height); 70 | } 71 | [back setHidden:YES]; 72 | NSString *iphone = @"Mozilla/5.0 (iPhone; CPU iPhone OS 9_1 like Mac OS X) AppleWebKit/601.1.46 (KHTML, like Gecko) Version/9.0 Mobile/13B143 Safari/601.1"; 73 | [webView setCustomUserAgent: iphone]; 74 | 75 | NSURL *url = [NSURL URLWithString:@"https://mobile.twitter.com/"]; 76 | NSURLRequest *urlRequest = [NSURLRequest requestWithURL:url]; 77 | [[webView mainFrame] loadRequest:urlRequest]; 78 | 79 | [webView1 setCustomUserAgent: iphone]; 80 | [[webView1 mainFrame] loadRequest:urlRequest]; 81 | } 82 | 83 | - (void)awakeFromNib 84 | { 85 | [self openTwitter:nil]; 86 | [super awakeFromNib]; 87 | 88 | // Make a fully skinned panel 89 | NSPanel *panel = (id)[self window]; 90 | [panel setAcceptsMouseMovedEvents:YES]; 91 | [panel setLevel:NSPopUpMenuWindowLevel]; 92 | [panel setOpaque:NO]; 93 | [panel setBackgroundColor:[NSColor clearColor]]; 94 | 95 | // Resize panel 96 | NSRect panelRect = [[self window] frame]; 97 | panelRect.size.height = POPUP_HEIGHT; 98 | [[self window] setFrame:panelRect display:NO]; 99 | 100 | // Follow search string 101 | [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(runSearch) name:NSControlTextDidChangeNotification object:self.searchField]; 102 | 103 | originalWebviewFrame = webView.frame; 104 | } 105 | 106 | - (void)webView:(WebView *)webView decidePolicyForNewWindowAction:(NSDictionary *)actionInformation request:(NSURLRequest *)request newFrameName:(NSString *)frameName decisionListener:(id < WebPolicyDecisionListener >)listener 107 | { 108 | clickedOnLinkOnce = YES; 109 | [[self.webView mainFrame] loadRequest:request]; 110 | } 111 | 112 | - (void)webView:(WebView *)webView decidePolicyForNavigationAction:(NSDictionary *)actionInformation request:(NSURLRequest *)request frame:(WebFrame *)frame decisionListener:(id)listener 113 | { 114 | BOOL isTwitter = [request.URL.host rangeOfString:@"twitter.com"].location != NSNotFound; 115 | if (clickedOnLinkOnce) self.webView.frame = CGRectMake(0, (isTwitter ? 0 : -10), originalWebviewFrame.size.width, originalWebviewFrame.size.height - (isTwitter ? 0 : 10)); 116 | [back setHidden:isTwitter]; 117 | [listener use]; 118 | } 119 | 120 | - (void)bindKeys 121 | { 122 | // [webView stringByEvaluatingJavaScriptFromString: 123 | // @"function addHash(){if(document.querySelector('.tweet-box-textarea') && document.querySelector('.tweet-box-textarea').value=='')document.querySelector('.tweet-box-textarea').value=' -- via #birddrop '; setTimeout(addHash, 1000);}; setTimeout(addHash, 1000);" 124 | // ]; 125 | [webView stringByEvaluatingJavaScriptFromString: 126 | @"var arrowi=0; var arrowj=0; var semaphore=0;document.body.addEventListener('keydown', function(ev) { if(semaphore)return; semaphore=1; var el = document.getSelection().anchorNode;if (el && (el.innerHTML.indexOf('input') !== -1 || el.innerHTML.indexOf('textarea') !== -1)) {return 0;}; if(document.location.pathname=='/'&&ev.keyCode==13){var el=document.querySelector('.stream-item:nth-child('+arrowj+')');if(el)doclick(el);}if(document.location.pathname=='/'&&(ev.keyCode==38||ev.keyCode==40)){if(arrowj<0)arrowj=0;var el=document.querySelector('.stream-item:nth-child('+arrowj+')'); if(el)el.style.backgroundColor='white'; if(ev.keyCode==38)arrowj--; if(ev.keyCode==40)arrowj++; el=document.querySelector('.stream-item:nth-child('+arrowj+')'); if(el){el.style.backgroundColor='lightblue';if(arrowj%4==0||ev.keyCode==38)el.scrollIntoView();}semaphore=0;/*up-down is done*/} var sel; if(!ev.metaKey&&ev.keyCode==37)arrowi--; if(!ev.metaKey&&ev.keyCode==39)arrowi++; if(arrowi<0)arrowi=3; if(arrowi>3)arrowi=0; var char=String.fromCharCode(ev.keyCode); if(!ev.metaKey&&(ev.keyCode==37||ev.keyCode==39))char=['H','C','D','M'][arrowi]; switch(char) { case 'H': sel = '.navbar div[tab=\"tweets\"]'; break; case 'C': sel = '.navbar div[tab=\"connect\"]'; break; case 'D': sel = '.navbar div[tab=\"discover\"]'; break; case 'M': sel = '.navbar div[tab=\"account\"]'; break; case 'N': sel = '.navItems div[nav=\"compose\"]'; break; case 'S': sel = '.navItems div[nav=\"search\"]'; break; } var el = document.querySelector(sel); if (el) { doclick(el); } function doclick(el) {var evt = document.createEvent('MouseEvents'); evt.initMouseEvent('click', true, true, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null); el.dispatchEvent(evt); } setTimeout(function(){semaphore=0}, 100)}, false);" 127 | ]; 128 | [webView stringByEvaluatingJavaScriptFromString: 129 | @"var styleBirddrop = document.createElement('style');styleBirddrop.innerHTML = '.navItem { cursor: pointer !important; } a { cursor: pointer !important; }'; document.body.appendChild(styleBirddrop);" 130 | ]; 131 | NSLog(@"inserting key.js & css"); 132 | } 133 | 134 | - (void)checkActivity 135 | { 136 | isNotFirstTimeLoadAcitivity = YES; 137 | NSString *resultTimestamp = [webView1 stringByEvaluatingJavaScriptFromString: 138 | @"document.querySelector('.stream-item').getAttribute('activity');" 139 | ]; 140 | NSString *resultActivityText = [webView1 stringByEvaluatingJavaScriptFromString: 141 | @"document.querySelector('.stream-item').innerText;" 142 | ]; 143 | int currentActivityTimestamp = (int)[resultTimestamp integerValue]; 144 | NSLog(@"last activity timestamp: %d", currentActivityTimestamp); 145 | [self openConnectTabEvenIfOnComposePage:YES withHiddenWebview:YES]; 146 | 147 | if (lastActivityTimestamp != 0 && lastActivityTimestamp != currentActivityTimestamp) { 148 | NSLog(@"sending notification"); 149 | StatusItemView *statusItemView = nil; 150 | if ([self.delegate respondsToSelector:@selector(statusItemViewForPanelController:)]) 151 | { 152 | statusItemView = [self.delegate statusItemViewForPanelController:self]; 153 | statusItemView.hasActivity = YES; 154 | } 155 | NSUserNotification *notification = [[NSUserNotification alloc] init]; 156 | if (notification) { 157 | notification.title = @"New Twitter BirdDropping"; 158 | notification.informativeText = resultActivityText; 159 | notification.soundName = NSUserNotificationDefaultSoundName; 160 | [[NSUserNotificationCenter defaultUserNotificationCenter] deliverNotification:notification]; 161 | NSSound *sound = [NSSound soundNamed:@"bird"]; 162 | [sound play]; 163 | } 164 | } 165 | lastActivityTimestamp = currentActivityTimestamp; 166 | } 167 | 168 | - (void)webView:(WebView *)sender didFinishLoadForFrame:(WebFrame *)frame 169 | { 170 | if ([sender isEqualTo:webView]) { 171 | [self bindKeys]; 172 | if (!bindKeysTimer) { 173 | bindKeysTimer = [NSTimer scheduledTimerWithTimeInterval:30.0 174 | target:self 175 | selector:@selector(bindKeys) 176 | userInfo:nil 177 | repeats:YES]; 178 | } 179 | } else { 180 | NSLog(@"webview1 load complete"); 181 | if (!isNotFirstTimeLoadAcitivity) [self checkActivity]; 182 | if (!checkActivityTimer) { 183 | checkActivityTimer = [NSTimer scheduledTimerWithTimeInterval:60.0 184 | target:self 185 | selector:@selector(checkActivity) 186 | userInfo:nil 187 | repeats:YES]; 188 | } 189 | } 190 | } 191 | 192 | #pragma mark - Public accessors 193 | 194 | - (BOOL)hasActivePanel 195 | { 196 | return _hasActivePanel; 197 | } 198 | 199 | - (void)setHasActivePanel:(BOOL)flag 200 | { 201 | if (_hasActivePanel != flag) 202 | { 203 | _hasActivePanel = flag; 204 | 205 | if (_hasActivePanel) 206 | { 207 | [self openPanel]; 208 | } 209 | else 210 | { 211 | [self closePanel]; 212 | } 213 | } 214 | } 215 | 216 | #pragma mark - NSWindowDelegate 217 | 218 | - (void)windowWillClose:(NSNotification *)notification 219 | { 220 | self.hasActivePanel = NO; 221 | } 222 | 223 | - (void)windowDidResignKey:(NSNotification *)notification; 224 | { 225 | if ([[self window] isVisible]) 226 | { 227 | self.hasActivePanel = NO; 228 | } 229 | } 230 | 231 | - (void)windowDidResize:(NSNotification *)notification 232 | { 233 | NSWindow *panel = [self window]; 234 | NSRect statusRect = [self statusRectForWindow:panel]; 235 | NSRect panelRect = [panel frame]; 236 | 237 | CGFloat statusX = roundf(NSMidX(statusRect)); 238 | CGFloat panelX = statusX - NSMinX(panelRect); 239 | 240 | self.backgroundView.arrowX = panelX; 241 | 242 | NSRect searchRect = [self.searchField frame]; 243 | searchRect.size.width = NSWidth([self.backgroundView bounds]) - SEARCH_INSET * 2; 244 | searchRect.origin.x = SEARCH_INSET; 245 | searchRect.origin.y = NSHeight([self.backgroundView bounds]) - ARROW_HEIGHT - SEARCH_INSET - NSHeight(searchRect); 246 | 247 | if (NSIsEmptyRect(searchRect)) 248 | { 249 | [self.searchField setHidden:YES]; 250 | } 251 | else 252 | { 253 | [self.searchField setFrame:searchRect]; 254 | [self.searchField setHidden:NO]; 255 | } 256 | 257 | NSRect textRect = [self.textField frame]; 258 | textRect.size.width = NSWidth([self.backgroundView bounds]) - SEARCH_INSET * 2; 259 | textRect.origin.x = SEARCH_INSET; 260 | textRect.size.height = NSHeight([self.backgroundView bounds]) - ARROW_HEIGHT - SEARCH_INSET * 3 - NSHeight(searchRect); 261 | textRect.origin.y = SEARCH_INSET; 262 | 263 | if (NSIsEmptyRect(textRect)) 264 | { 265 | [self.textField setHidden:YES]; 266 | } 267 | else 268 | { 269 | [self.textField setFrame:textRect]; 270 | [self.textField setHidden:NO]; 271 | } 272 | } 273 | 274 | #pragma mark - Keyboard 275 | 276 | - (void)cancelOperation:(id)sender 277 | { 278 | self.hasActivePanel = NO; 279 | } 280 | 281 | - (void)runSearch 282 | { 283 | NSString *searchFormat = @""; 284 | NSString *searchString = [self.searchField stringValue]; 285 | if ([searchString length] > 0) 286 | { 287 | searchFormat = NSLocalizedString(@"Search for ‘%@’…", @"Format for search request"); 288 | } 289 | NSString *searchRequest = [NSString stringWithFormat:searchFormat, searchString]; 290 | [self.textField setStringValue:searchRequest]; 291 | } 292 | 293 | #pragma mark - Public methods 294 | 295 | - (void)openConnectTabEvenIfOnComposePage:(BOOL)shouldForceOpen withHiddenWebview:(BOOL)shouldBeHiddenWebview 296 | { 297 | BOOL isOnComposePage = NO; 298 | if (!shouldForceOpen && isOnComposePage) return; 299 | NSURL *url = [NSURL URLWithString:@"https://mobile.twitter.com/i/connect"]; 300 | NSURLRequest *urlRequest = [NSURLRequest requestWithURL:url]; 301 | WebView *webviewToUse = shouldBeHiddenWebview ? webView1 : webView; 302 | [[webviewToUse mainFrame] loadRequest:urlRequest]; 303 | } 304 | 305 | - (NSRect)statusRectForWindow:(NSWindow *)window 306 | { 307 | NSRect screenRect = [[[NSScreen screens] objectAtIndex:0] frame]; 308 | NSRect statusRect = NSZeroRect; 309 | 310 | StatusItemView *statusItemView = nil; 311 | if ([self.delegate respondsToSelector:@selector(statusItemViewForPanelController:)]) 312 | { 313 | statusItemView = [self.delegate statusItemViewForPanelController:self]; 314 | } 315 | 316 | if (statusItemView) 317 | { 318 | statusRect = statusItemView.globalRect; 319 | statusRect.origin.y = NSMinY(statusRect) - NSHeight(statusRect); 320 | } 321 | else 322 | { 323 | statusRect.size = NSMakeSize(STATUS_ITEM_VIEW_WIDTH, [[NSStatusBar systemStatusBar] thickness]); 324 | statusRect.origin.x = roundf((NSWidth(screenRect) - NSWidth(statusRect)) / 2); 325 | statusRect.origin.y = NSHeight(screenRect) - NSHeight(statusRect) * 2; 326 | } 327 | return statusRect; 328 | } 329 | 330 | - (void)openPanel 331 | { 332 | NSWindow *panel = [self window]; 333 | 334 | NSRect screenRect = [[[NSScreen screens] objectAtIndex:0] frame]; 335 | NSRect statusRect = [self statusRectForWindow:panel]; 336 | 337 | NSRect panelRect = [panel frame]; 338 | panelRect.size.width = PANEL_WIDTH; 339 | panelRect.origin.x = roundf(NSMidX(statusRect) - NSWidth(panelRect) / 2); 340 | panelRect.origin.y = NSMaxY(statusRect) - NSHeight(panelRect); 341 | 342 | if (NSMaxX(panelRect) > (NSMaxX(screenRect) - ARROW_HEIGHT)) 343 | panelRect.origin.x -= NSMaxX(panelRect) - (NSMaxX(screenRect) - ARROW_HEIGHT); 344 | 345 | [NSApp activateIgnoringOtherApps:NO]; 346 | [panel setAlphaValue:0]; 347 | [panel setFrame:statusRect display:YES]; 348 | [panel makeKeyAndOrderFront:nil]; 349 | 350 | NSTimeInterval openDuration = OPEN_DURATION; 351 | 352 | NSEvent *currentEvent = [NSApp currentEvent]; 353 | if ([currentEvent type] == NSLeftMouseDown) 354 | { 355 | NSUInteger clearFlags = ([currentEvent modifierFlags] & NSDeviceIndependentModifierFlagsMask); 356 | BOOL shiftPressed = (clearFlags == NSShiftKeyMask); 357 | BOOL shiftOptionPressed = (clearFlags == (NSShiftKeyMask | NSAlternateKeyMask)); 358 | if (shiftPressed || shiftOptionPressed) 359 | { 360 | openDuration *= 10; 361 | 362 | if (shiftOptionPressed) 363 | NSLog(@"Icon is at %@\n\tMenu is on screen %@\n\tWill be animated to %@", 364 | NSStringFromRect(statusRect), NSStringFromRect(screenRect), NSStringFromRect(panelRect)); 365 | } 366 | } 367 | 368 | [NSAnimationContext beginGrouping]; 369 | [[NSAnimationContext currentContext] setDuration:openDuration]; 370 | [[panel animator] setFrame:panelRect display:YES]; 371 | [[panel animator] setAlphaValue:1]; 372 | [NSAnimationContext endGrouping]; 373 | } 374 | 375 | - (void)closePanel 376 | { 377 | [NSAnimationContext beginGrouping]; 378 | [[NSAnimationContext currentContext] setDuration:CLOSE_DURATION]; 379 | [[[self window] animator] setAlphaValue:0]; 380 | [NSAnimationContext endGrouping]; 381 | 382 | dispatch_after(dispatch_walltime(NULL, NSEC_PER_SEC * CLOSE_DURATION * 2), dispatch_get_main_queue(), ^{ 383 | 384 | [self.window orderOut:nil]; 385 | }); 386 | } 387 | 388 | @end 389 | -------------------------------------------------------------------------------- /Popup/PreferencesViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // PreferencesViewController.h 3 | // BirdDrop 4 | // 5 | // Created by Soheil Yasrebi on 5/1/13. 6 | // 7 | // 8 | 9 | #import 10 | 11 | @interface PreferencesViewController : NSViewController 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /Popup/PreferencesViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // PreferencesViewController.m 3 | // BirdDrop 4 | // 5 | // Created by Soheil Yasrebi on 5/1/13. 6 | // 7 | // 8 | 9 | #import "PreferencesViewController.h" 10 | 11 | @interface PreferencesViewController () 12 | 13 | @end 14 | 15 | @implementation PreferencesViewController 16 | 17 | - (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil 18 | { 19 | self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]; 20 | if (self) { 21 | // Initialization code here. 22 | } 23 | 24 | return self; 25 | } 26 | 27 | @end 28 | -------------------------------------------------------------------------------- /Popup/PreferencesViewController.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 1070 5 | 12D78 6 | 3084 7 | 1187.37 8 | 626.00 9 | 10 | com.apple.InterfaceBuilder.CocoaPlugin 11 | 3084 12 | 13 | 14 | IBNSLayoutConstraint 15 | NSActionCell 16 | NSControl 17 | NSCustomObject 18 | NSCustomView 19 | 20 | 21 | com.apple.InterfaceBuilder.CocoaPlugin 22 | 23 | 24 | PluginDependencyRecalculationVersion 25 | 26 | 27 | 28 | 29 | PreferencesViewController 30 | 31 | 32 | FirstResponder 33 | 34 | 35 | NSApplication 36 | 37 | 38 | 39 | 268 40 | 41 | 42 | 43 | 295 44 | {{109, 149}, {275, 75}} 45 | 46 | YES 47 | 48 | 0 49 | 0 50 | 51 | 52 | NO 53 | 54 | 55 | {480, 272} 56 | 57 | NSView 58 | 59 | 60 | 61 | 62 | 63 | 64 | view 65 | 66 | 67 | 68 | 2 69 | 70 | 71 | 72 | 73 | 74 | 0 75 | 76 | 77 | 78 | 79 | 80 | -2 81 | 82 | 83 | File's Owner 84 | 85 | 86 | -1 87 | 88 | 89 | First Responder 90 | 91 | 92 | -3 93 | 94 | 95 | Application 96 | 97 | 98 | 1 99 | 100 | 101 | 102 | 103 | 3 104 | 0 105 | 106 | 3 107 | 1 108 | 109 | 48 110 | 111 | 1000 112 | 113 | 3 114 | 9 115 | 3 116 | 117 | 118 | 119 | 5 120 | 0 121 | 122 | 5 123 | 1 124 | 125 | 109 126 | 127 | 1000 128 | 129 | 3 130 | 9 131 | 3 132 | 133 | 134 | 135 | 136 | 137 | 138 | 5 139 | 140 | 141 | 142 | 143 | 144 | 8 145 | 0 146 | 147 | 0 148 | 1 149 | 150 | 75 151 | 152 | 1000 153 | 154 | 3 155 | 9 156 | 1 157 | 158 | 159 | 160 | 7 161 | 0 162 | 163 | 0 164 | 1 165 | 166 | 275 167 | 168 | 1000 169 | 170 | 3 171 | 9 172 | 1 173 | 174 | 175 | 176 | 177 | 178 | 6 179 | 180 | 181 | 182 | 183 | 9 184 | 185 | 186 | 187 | 188 | 11 189 | 190 | 191 | 192 | 193 | 12 194 | 195 | 196 | 197 | 198 | 13 199 | 200 | 201 | 202 | 203 | 204 | 205 | com.apple.InterfaceBuilder.CocoaPlugin 206 | com.apple.InterfaceBuilder.CocoaPlugin 207 | com.apple.InterfaceBuilder.CocoaPlugin 208 | 209 | 210 | 211 | 212 | com.apple.InterfaceBuilder.CocoaPlugin 213 | com.apple.InterfaceBuilder.CocoaPlugin 214 | com.apple.InterfaceBuilder.CocoaPlugin 215 | com.apple.InterfaceBuilder.CocoaPlugin 216 | SRRecorderControl 217 | 218 | IBUserDefinedRuntimeAttributesPlaceholderName 219 | 220 | IBUserDefinedRuntimeAttributesPlaceholderName 221 | 222 | 223 | 224 | com.apple.InterfaceBuilder.userDefinedRuntimeAttributeType.number 225 | style 226 | 227 | 228 | 229 | com.apple.InterfaceBuilder.userDefinedRuntimeAttributeType.number 230 | allowsKeyOnly 231 | 232 | 233 | 234 | com.apple.InterfaceBuilder.userDefinedRuntimeAttributeType.number 235 | allowedFlags 236 | 237 | 238 | 239 | com.apple.InterfaceBuilder.userDefinedRuntimeAttributeType.number 240 | escapeKeysRecord 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | com.apple.InterfaceBuilder.CocoaPlugin 252 | SRRecorderCell 253 | com.apple.InterfaceBuilder.CocoaPlugin 254 | com.apple.InterfaceBuilder.CocoaPlugin 255 | 256 | 257 | 258 | 259 | 260 | 13 261 | 262 | 263 | 0 264 | IBCocoaFramework 265 | 266 | com.apple.InterfaceBuilder.CocoaPlugin.macosx 267 | 268 | 269 | YES 270 | 3 271 | YES 272 | 273 | 274 | -------------------------------------------------------------------------------- /Popup/Status.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/soheil-zz/BirdDrop-OSX/b20de07ee6f197ceb7a91f6d7ca296ad8abad529/Popup/Status.png -------------------------------------------------------------------------------- /Popup/Status@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/soheil-zz/BirdDrop-OSX/b20de07ee6f197ceb7a91f6d7ca296ad8abad529/Popup/Status@2x.png -------------------------------------------------------------------------------- /Popup/StatusBlue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/soheil-zz/BirdDrop-OSX/b20de07ee6f197ceb7a91f6d7ca296ad8abad529/Popup/StatusBlue.png -------------------------------------------------------------------------------- /Popup/StatusBlue@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/soheil-zz/BirdDrop-OSX/b20de07ee6f197ceb7a91f6d7ca296ad8abad529/Popup/StatusBlue@2x.png -------------------------------------------------------------------------------- /Popup/StatusHighlighted.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/soheil-zz/BirdDrop-OSX/b20de07ee6f197ceb7a91f6d7ca296ad8abad529/Popup/StatusHighlighted.png -------------------------------------------------------------------------------- /Popup/StatusHighlighted@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/soheil-zz/BirdDrop-OSX/b20de07ee6f197ceb7a91f6d7ca296ad8abad529/Popup/StatusHighlighted@2x.png -------------------------------------------------------------------------------- /Popup/StatusItemView.h: -------------------------------------------------------------------------------- 1 | @interface StatusItemView : NSView { 2 | @private 3 | NSImage *_image; 4 | NSImage *_alternateImage; 5 | NSImage *_blueImage; 6 | NSStatusItem *_statusItem; 7 | BOOL _isHighlighted; 8 | BOOL _hasActivity; 9 | SEL _action; 10 | __unsafe_unretained id _target; 11 | } 12 | 13 | - (id)initWithStatusItem:(NSStatusItem *)statusItem; 14 | 15 | @property (nonatomic, strong, readonly) NSStatusItem *statusItem; 16 | @property (nonatomic, strong) NSImage *image; 17 | @property (nonatomic, strong) NSImage *alternateImage; 18 | @property (nonatomic, strong) NSImage *blueImage; 19 | @property (nonatomic, setter = setHighlighted:) BOOL isHighlighted; 20 | @property (nonatomic, setter = setHasActivity:) BOOL hasActivity; 21 | @property (nonatomic, readonly) NSRect globalRect; 22 | @property (nonatomic) SEL action; 23 | @property (nonatomic, unsafe_unretained) id target; 24 | 25 | @end 26 | -------------------------------------------------------------------------------- /Popup/StatusItemView.m: -------------------------------------------------------------------------------- 1 | #import "StatusItemView.h" 2 | #import "PreferencesViewController.h" 3 | 4 | @implementation StatusItemView 5 | 6 | @synthesize statusItem = _statusItem; 7 | @synthesize image = _image; 8 | @synthesize alternateImage = _alternateImage; 9 | @synthesize blueImage = _blueImage; 10 | @synthesize isHighlighted = _isHighlighted; 11 | @synthesize hasActivity = _hasActivity; 12 | @synthesize action = _action; 13 | @synthesize target = _target; 14 | 15 | #pragma mark - 16 | 17 | - (id)initWithStatusItem:(NSStatusItem *)statusItem 18 | { 19 | CGFloat itemWidth = [statusItem length]; 20 | CGFloat itemHeight = [[NSStatusBar systemStatusBar] thickness]; 21 | NSRect itemRect = NSMakeRect(0.0, 0.0, itemWidth, itemHeight); 22 | self = [super initWithFrame:itemRect]; 23 | 24 | if (self != nil) { 25 | _statusItem = statusItem; 26 | _statusItem.view = self; 27 | } 28 | return self; 29 | } 30 | 31 | #pragma mark - 32 | 33 | - (void)drawRect:(NSRect)dirtyRect 34 | { 35 | [self.statusItem drawStatusBarBackgroundInRect:dirtyRect withHighlight:self.isHighlighted]; 36 | 37 | NSImage *icon = self.hasActivity ? self.blueImage : (self.isHighlighted ? self.alternateImage : self.image); 38 | NSSize iconSize = [icon size]; 39 | NSRect bounds = self.bounds; 40 | CGFloat iconX = roundf((NSWidth(bounds) - iconSize.width) / 2); 41 | CGFloat iconY = roundf((NSHeight(bounds) - iconSize.height) / 2); 42 | NSPoint iconPoint = NSMakePoint(iconX, iconY); 43 | 44 | [icon drawAtPoint:iconPoint fromRect:NSZeroRect operation:NSCompositeSourceOver fraction:1.0]; 45 | } 46 | 47 | #pragma mark - 48 | #pragma mark Mouse tracking 49 | 50 | - (void)mouseDown:(NSEvent *)theEvent 51 | { 52 | [NSApp sendAction:self.action to:self.target from:self]; 53 | } 54 | 55 | - (void)rightMouseDown:(NSEvent *)theEvent 56 | { 57 | [super rightMouseDown:theEvent]; 58 | 59 | NSMenu *theMenu = [[NSMenu alloc] init]; 60 | // [theMenu insertItemWithTitle:@"Preferences" action:@selector(openPreferences) keyEquivalent:@"," atIndex:0]; 61 | [theMenu insertItemWithTitle:@"Quit BirdDrop" action:@selector(terminate:) keyEquivalent:@"q" atIndex:0]; 62 | [theMenu insertItemWithTitle:[@"ver. " stringByAppendingFormat:@"%d", VERSION] action:nil keyEquivalent:@"" atIndex:1]; 63 | 64 | [self.statusItem popUpStatusItemMenu:theMenu]; 65 | } 66 | 67 | - (void)openPreferences 68 | { 69 | // PreferencesViewController *vc= [[PreferencesViewController alloc] initWithNibName:@"PreferencesViewController" bundle:nil]; 70 | } 71 | 72 | #pragma mark - 73 | #pragma mark Accessors 74 | 75 | - (void)setHighlighted:(BOOL)newFlag 76 | { 77 | if (_isHighlighted == newFlag) return; 78 | _isHighlighted = newFlag; 79 | [self setHasActivity:NO]; 80 | [self setNeedsDisplay:YES]; 81 | } 82 | 83 | - (void)setHasActivity:(BOOL)newFlag 84 | { 85 | if (_hasActivity == newFlag) return; 86 | _hasActivity = newFlag; 87 | [self setNeedsDisplay:YES]; 88 | } 89 | 90 | #pragma mark - 91 | 92 | - (void)setImage:(NSImage *)newImage 93 | { 94 | if (_image != newImage) { 95 | _image = newImage; 96 | [self setNeedsDisplay:YES]; 97 | } 98 | } 99 | 100 | - (void)setAlternateImage:(NSImage *)newImage 101 | { 102 | if (_alternateImage != newImage) { 103 | _alternateImage = newImage; 104 | if (self.isHighlighted) { 105 | [self setNeedsDisplay:YES]; 106 | } 107 | } 108 | } 109 | 110 | - (void)setBlueImage:(NSImage *)newImage 111 | { 112 | if (_blueImage != newImage) { 113 | _blueImage = newImage; 114 | if (self.hasActivity) { 115 | [self setNeedsDisplay:YES]; 116 | } 117 | } 118 | } 119 | 120 | #pragma mark - 121 | 122 | - (NSRect)globalRect 123 | { 124 | NSRect frame = [self frame]; 125 | frame.origin = [self.window convertBaseToScreen:frame.origin]; 126 | return frame; 127 | } 128 | 129 | @end 130 | -------------------------------------------------------------------------------- /Popup/bird.aif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/soheil-zz/BirdDrop-OSX/b20de07ee6f197ceb7a91f6d7ca296ad8abad529/Popup/bird.aif -------------------------------------------------------------------------------- /Popup/keys.js: -------------------------------------------------------------------------------- 1 | document.body.addEventListener('keydown', function(ev) { 2 | var sel; 3 | 4 | switch(String.fromCharCode(ev.keyCode)) { 5 | case 'H': sel = '.navbar div[tab="tweets"]'; break; 6 | case 'C': sel = '.navbar div[tab="connect"]'; break; 7 | case 'D': sel = '.navbar div[tab="discover"]'; break; 8 | case 'M': sel = '.navbar div[tab="account"]'; break; 9 | } 10 | console.log(sel); 11 | var el = document.querySelector(sel); 12 | console.log(el); 13 | if (el) { 14 | var evt = document.createEvent('MouseEvents'); 15 | evt.initMouseEvent('click', true, true, 16 | window, 0, 0, 0, 0, 0, false, 17 | false, false, false, 0, null); 18 | 19 | el.dispatchEvent(evt); 20 | } 21 | }, false); 22 | -------------------------------------------------------------------------------- /Popup/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // Popup 4 | // 5 | // Created by Vadim Shpakovski on 7/5/11. 6 | // Copyright 2011 __MyCompanyName__. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | int main(int argc, char *argv[]) 12 | { 13 | return NSApplicationMain(argc, (const char **)argv); 14 | } 15 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## BirdDrop - An OSX client for Twitter 2 | 3 | BirdDrop is a lightweight client for Twitter on OSX. For more information see [Twitter](https://twitter.com) and [Apple OSX](http://www.apple.com/osx). 4 | 5 | ![Mountain Lion Notification](screen1.png) 6 | 7 | ## Notifications 8 | 9 | BirdDrop supports notifications via notification center. 10 | ![Mountain Lion Notification](mountain-lion-notification.png) 11 | 12 | 13 | ## Keyboard Shortcuts 14 | 15 | Keyboard shortcut to slide down: 16 | ```` 17 | CTRL+SHIFT+B 18 | ``` 19 | 20 | To navigate Twitter: 21 | ``` 22 | H: Home 23 | C: Connect 24 | D: Discover 25 | M: Me 26 | N: New Tweet 27 | S: Search 28 | ``` 29 | Use left/right arrows to navigate thru different tabs and up/down arrows to select tweets on your Timeline. 30 | 31 | Select a tweet and press ```Enter``` to open it. 32 | 33 | ## Issues 34 | 35 | For issues or feature requests please use [Github](https://github.com/soheil/BirdDrop-OSX/issues). 36 | 37 | ## Features 38 | 39 | - Embedded browsing 40 | - Auto-update 41 | - Optional keyboard-only navigation/tweeting 42 | 43 | ## Author 44 | 45 | [@soheil](https://twitter.com/soheil) 46 | 47 | ## License 48 | 49 | BirdDrop is licensed under the BSD license. 50 | -------------------------------------------------------------------------------- /mountain-lion-notification.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/soheil-zz/BirdDrop-OSX/b20de07ee6f197ceb7a91f6d7ca296ad8abad529/mountain-lion-notification.png -------------------------------------------------------------------------------- /pkg-dmg: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl 2 | # ***** BEGIN LICENSE BLOCK ***** 3 | # Version: MPL 1.1/GPL 2.0/LGPL 2.1 4 | # 5 | # The contents of this file are subject to the Mozilla Public License Version 6 | # 1.1 (the "License"); you may not use this file except in compliance with 7 | # the License. You may obtain a copy of the License at 8 | # http://www.mozilla.org/MPL/ 9 | # 10 | # Software distributed under the License is distributed on an "AS IS" basis, 11 | # WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License 12 | # for the specific language governing rights and limitations under the 13 | # License. 14 | # 15 | # The Original Code is pkg-dmg, a Mac OS X disk image (.dmg) packager 16 | # 17 | # The Initial Developer of the Original Code is 18 | # Mark Mentovai . 19 | # Portions created by the Initial Developer are Copyright (C) 2005 20 | # the Initial Developer. All Rights Reserved. 21 | # 22 | # Contributor(s): 23 | # 24 | # Alternatively, the contents of this file may be used under the terms of 25 | # either the GNU General Public License Version 2 or later (the "GPL"), or 26 | # the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), 27 | # in which case the provisions of the GPL or the LGPL are applicable instead 28 | # of those above. If you wish to allow use of your version of this file only 29 | # under the terms of either the GPL or the LGPL, and not to allow others to 30 | # use your version of this file under the terms of the MPL, indicate your 31 | # decision by deleting the provisions above and replace them with the notice 32 | # and other provisions required by the GPL or the LGPL. If you do not delete 33 | # the provisions above, a recipient may use your version of this file under 34 | # the terms of any one of the MPL, the GPL or the LGPL. 35 | # 36 | # ***** END LICENSE BLOCK ***** 37 | 38 | use strict; 39 | use warnings; 40 | 41 | =pod 42 | 43 | =head1 NAME 44 | 45 | B - Mac OS X disk image (.dmg) packager 46 | 47 | =head1 SYNOPSIS 48 | 49 | B 50 | B<--source> I 51 | B<--target> I 52 | [B<--format> I] 53 | [B<--volname> I] 54 | [B<--tempdir> I] 55 | [B<--mkdir> I] 56 | [B<--copy> I[:I]] 57 | [B<--symlink> I[:I]] 58 | [B<--license> I] 59 | [B<--resource> I] 60 | [B<--icon> I] 61 | [B<--attribute> I:I[:I...] 62 | [B<--idme>] 63 | [B<--sourcefile>] 64 | [B<--verbosity> I] 65 | [B<--dry-run>] 66 | 67 | =head1 DESCRIPTION 68 | 69 | I takes a directory identified by I and transforms 70 | it into a disk image stored as I. The disk image will 71 | occupy the least space possible for its format, or the least space that the 72 | authors have been able to figure out how to achieve. 73 | 74 | =head1 OPTIONS 75 | 76 | =over 5 77 | 78 | ==item B<--source> I 79 | 80 | Identifies the directory that will be packaged up. This directory is not 81 | touched, a copy will be made in a temporary directory for staging purposes. 82 | See B<--tempdir>. 83 | 84 | ==item B<--target> I 85 | 86 | The disk image to create. If it exists and is not in use, it will be 87 | overwritten. If I already contains a suitable extension, 88 | it will be used unmodified. If no extension is present, or the extension 89 | is incorrect for the selected format, the proper extension will be added. 90 | See B<--format>. 91 | 92 | ==item B<--format> I 93 | 94 | The format to create the disk image in. Valid values for I are: 95 | - UDZO - zlib-compressed, read-only; extension I<.dmg> 96 | - UDBZ - bzip2-compressed, read-only; extension I<.dmg>; 97 | create and use on 10.4 ("Tiger") and later only 98 | - UDRW - read-write; extension I<.dmg> 99 | - UDSP - read-write, sparse; extension I<.sparseimage> 100 | 101 | UDZO is the default format. 102 | 103 | See L for a description of these formats. 104 | 105 | =item B<--volname> I 106 | 107 | The name of the volume in the disk image. If not specified, I 108 | defaults to the name of the source directory from B<--source>. 109 | 110 | =item B<--tempdir> I 111 | 112 | A temporary directory to stage intermediate files in. I must 113 | have enough space available to accommodate twice the size of the files 114 | being packaged. If not specified, defaults to the same directory that 115 | the I is to be placed in. B will remove any 116 | temporary files it places in I. 117 | 118 | =item B<--mkdir> I 119 | 120 | Specifies a directory that should be created in the disk image. 121 | I and any ancestor directories will be created. This is 122 | useful in conjunction with B<--copy>, when copying files to directories 123 | that may not exist in I. B<--mkdir> may appear multiple 124 | times. 125 | 126 | =item B<--copy> I[:I] 127 | 128 | Additional files to copy into the disk image. If I is 129 | specified, I is copied to the location I identifies, 130 | otherwise, I is copied to the root of the new volume. B<--copy> 131 | provides a way to package up a I by adding files to it 132 | without modifying the original I. B<--copy> may appear 133 | multiple times. 134 | 135 | This option is useful for adding .DS_Store files and window backgrounds 136 | to disk images. 137 | 138 | =item B<--symlink> I[:I] 139 | 140 | Like B<--copy>, but allows symlinks to point out of the volume. Empty symlink 141 | destinations are interpreted as "like the source path, but inside the dmg" 142 | 143 | This option is useful for adding symlinks to external resources, 144 | e.g. to /Applications. 145 | 146 | =item B<--license> I 147 | 148 | A plain text file containing a license agreement to be displayed before 149 | the disk image is mounted. English is the only supported language. To 150 | include license agreements in other languages, in multiple languages, 151 | or to use formatted text, prepare a resource and use L<--resource>. 152 | 153 | =item B<--resource> I 154 | 155 | A resource file to merge into I. If I is UDZO or 156 | UDBZ, the disk image will be flattened to a single-fork file that contains 157 | the resource but may be freely transferred without any special encodings. 158 | I must be in a format suitable for L. See L for a 159 | description of the format, and L for a discussion on flattened 160 | disk images. B<--resource> may appear multiple times. 161 | 162 | This option is useful for adding license agreements and other messages 163 | to disk images. 164 | 165 | =item B<--icon> I 166 | 167 | Specifies an I file that will be used as the icon for the root of 168 | the volume. This file will be copied to the new volume and the custom 169 | icon attribute will be set on the root folder. 170 | 171 | =item B<--attribute> I:I[:I...] 172 | 173 | Sets the attributes of I to the attribute list in I. See 174 | L 175 | 176 | =item B<--idme> 177 | 178 | Enable IDME to make the disk image "Internet-enabled." The first time 179 | the image is mounted, if IDME processing is enabled on the system, the 180 | contents of the image will be copied out of the image and the image will 181 | be placed in the trash with IDME disabled. 182 | 183 | =item B<--sourcefile> 184 | 185 | If this option is present, I is treated as a file, and is 186 | placed as a file within the volume's root folder. Without this option, 187 | I is treated as the volume root itself. 188 | 189 | =item B<--verbosity> I 190 | 191 | Adjusts the level of loudness of B. The possible values for 192 | I are: 193 | 0 - Only error messages are displayed. 194 | 1 - Print error messages and command invocations. 195 | 2 - Print everything, including command output. 196 | 197 | The default I is 2. 198 | 199 | =item B<--dry-run> 200 | 201 | When specified, the commands that would be executed are printed, without 202 | actually executing them. When commands depend on the output of previous 203 | commands, dummy values are displayed. 204 | 205 | =back 206 | 207 | =head1 NON-OPTIONS 208 | 209 | =over 5 210 | 211 | =item 212 | 213 | Resource forks aren't copied. 214 | 215 | =item 216 | 217 | The root folder of the created volume is designated as the folder 218 | to open when the volume is mounted. See L. 219 | 220 | =item 221 | 222 | All files in the volume are set to be world-readable, only writable 223 | by the owner, and world-executable when appropriate. All other 224 | permissions bits are cleared. 225 | 226 | =item 227 | 228 | When possible, disk images are created without any partition tables. This 229 | is what L refers to as I<-layout NONE>, and saves a handful of 230 | kilobytes. The alternative, I, contains a partition table that 231 | is not terribly handy on disk images that are not intended to represent any 232 | physical disk. 233 | 234 | =item 235 | 236 | Read-write images are created with journaling off. Any read-write image 237 | created by this tool is expected to be transient, and the goal of this tool 238 | is to create images which consume a minimum of space. 239 | 240 | =back 241 | 242 | =head1 EXAMPLE 243 | 244 | pkg-dmg --source /Applications/DeerPark.app --target ~/DeerPark.dmg 245 | --sourcefile --volname DeerPark --icon ~/DeerPark.icns 246 | --mkdir /.background 247 | --copy DeerParkBackground.png:/.background/background.png 248 | --copy DeerParkDSStore:/.DS_Store 249 | --symlink /Applications:"/Drag to here" 250 | 251 | =head1 REQUIREMENTS 252 | 253 | I has been tested with Mac OS X releases 10.2 ("Jaguar") 254 | through 10.4 ("Tiger"). Certain adjustments to behavior are made 255 | depending on the host system's release. Mac OS X 10.3 ("Panther") or 256 | later are recommended. 257 | 258 | =head1 LICENSE 259 | 260 | MPL 1.1/GPL 2.0/LGPL 2.1. Your choice. 261 | 262 | =head1 AUTHOR 263 | 264 | Mark Mentovai 265 | 266 | =head1 SEE ALSO 267 | 268 | L, L, L, L, L, 269 | L, L 270 | 271 | =cut 272 | 273 | use Fcntl; 274 | use POSIX; 275 | use Getopt::Long; 276 | 277 | sub argumentEscape(@); 278 | sub cleanupDie($); 279 | sub command(@); 280 | sub commandInternal($@); 281 | sub commandInternalVerbosity($$@); 282 | sub commandOutput(@); 283 | sub commandOutputVerbosity($@); 284 | sub commandVerbosity($@); 285 | sub copyFiles($@); 286 | sub diskImageMaker($$$$$$$$); 287 | sub giveExtension($$); 288 | sub hdidMountImage($@); 289 | sub isFormatCompressed($); 290 | sub licenseMaker($$); 291 | sub pathSplit($); 292 | sub setAttributes($@); 293 | sub trapSignal($); 294 | sub usage(); 295 | 296 | # Variables used as globals 297 | my(@gCleanup, %gConfig, $gDarwinMajor, $gDryRun, $gVerbosity); 298 | 299 | # Use the commands by name if they're expected to be in the user's 300 | # $PATH (/bin:/sbin:/usr/bin:/usr/sbin). Otherwise, go by absolute 301 | # path. These may be overridden with --config. 302 | %gConfig = ('cmd_bless' => 'bless', 303 | 'cmd_chmod' => 'chmod', 304 | 'cmd_diskutil' => 'diskutil', 305 | 'cmd_du' => 'du', 306 | 'cmd_hdid' => 'hdid', 307 | 'cmd_hdiutil' => 'hdiutil', 308 | 'cmd_mkdir' => 'mkdir', 309 | 'cmd_mktemp' => 'mktemp', 310 | 'cmd_Rez' => '/Developer/Tools/Rez', 311 | 'cmd_rm' => 'rm', 312 | 'cmd_rsync' => 'rsync', 313 | 'cmd_SetFile' => 'SetFile', 314 | 315 | # create_directly indicates whether hdiutil create supports 316 | # -srcfolder and -srcdevice. It does on >= 10.3 (Panther). 317 | # This is fixed up for earlier systems below. If false, 318 | # hdiutil create is used to create empty disk images that 319 | # are manually filled. 320 | 'create_directly' => 1, 321 | 322 | # If hdiutil attach -mountpoint exists, use it to avoid 323 | # mounting disk images in the default /Volumes. This reduces 324 | # the likelihood that someone will notice a mounted image and 325 | # interfere with it. Only available on >= 10.3 (Panther), 326 | # fixed up for earlier systems below. 327 | # 328 | # This is presently turned off for all systems, because there 329 | # is an infrequent synchronization problem during ejection. 330 | # diskutil eject might return before the image is actually 331 | # unmounted. If pkg-dmg then attempts to clean up its 332 | # temporary directory, it could remove items from a read-write 333 | # disk image or attempt to remove items from a read-only disk 334 | # image (or a read-only item from a read-write image) and fail, 335 | # causing pkg-dmg to abort. This problem is experienced 336 | # under Tiger, which appears to eject asynchronously where 337 | # previous systems treated it as a synchronous operation. 338 | # Using hdiutil attach -mountpoint didn't always keep images 339 | # from showing up on the desktop anyway. 340 | 'hdiutil_mountpoint' => 0, 341 | 342 | # hdiutil makehybrid results in optimized disk images that 343 | # consume less space and mount more quickly. Use it when 344 | # it's available, but that's only on >= 10.3 (Panther). 345 | # If false, hdiutil create is used instead. Fixed up for 346 | # earlier systems below. 347 | 'makehybrid' => 1, 348 | 349 | # hdiutil create doesn't allow specifying a folder to open 350 | # at volume mount time, so those images are mounted and 351 | # their root folders made holy with bless -openfolder. But 352 | # only on >= 10.3 (Panther). Earlier systems are out of luck. 353 | # Even on Panther, bless refuses to run unless root. 354 | # Fixed up below. 355 | 'openfolder_bless' => 1, 356 | 357 | # It's possible to save a few more kilobytes by including the 358 | # partition only without any partition table in the image. 359 | # This is a good idea on any system, so turn this option off. 360 | # 361 | # Except it's buggy. "-layout NONE" seems to be creating 362 | # disk images with more data than just the partition table 363 | # stripped out. You might wind up losing the end of the 364 | # filesystem - the last file (or several) might be incomplete. 365 | 'partition_table' => 1, 366 | 367 | # To create a partition table-less image from something 368 | # created by makehybrid, the hybrid image needs to be 369 | # mounted and a new image made from the device associated 370 | # with the relevant partition. This requires >= 10.4 371 | # (Tiger), presumably because earlier systems have 372 | # problems creating images from devices themselves attached 373 | # to images. If this is false, makehybrid images will 374 | # have partition tables, regardless of the partition_table 375 | # setting. Fixed up for earlier systems below. 376 | 'recursive_access' => 1); 377 | 378 | # --verbosity 379 | $gVerbosity = 2; 380 | 381 | # --dry-run 382 | $gDryRun = 0; 383 | 384 | # %gConfig fix-ups based on features and bugs present in certain releases. 385 | my($ignore, $uname_r, $uname_s); 386 | ($uname_s, $ignore, $uname_r, $ignore, $ignore) = POSIX::uname(); 387 | if($uname_s eq 'Darwin') { 388 | ($gDarwinMajor, $ignore) = split(/\./, $uname_r, 2); 389 | 390 | # $major is the Darwin major release, which for our purposes, is 4 higher 391 | # than the interesting digit in a Mac OS X release. 392 | if($gDarwinMajor <= 6) { 393 | # <= 10.2 (Jaguar) 394 | # hdiutil create does not support -srcfolder or -srcdevice 395 | $gConfig{'create_directly'} = 0; 396 | # hdiutil attach does not support -mountpoint 397 | $gConfig{'hdiutil_mountpoint'} = 0; 398 | # hdiutil mkhybrid does not exist 399 | $gConfig{'makehybrid'} = 0; 400 | } 401 | if($gDarwinMajor <= 7) { 402 | # <= 10.3 (Panther) 403 | # Can't mount a disk image and then make a disk image from the device 404 | $gConfig{'recursive_access'} = 0; 405 | # bless does not support -openfolder on 10.2 (Jaguar) and must run 406 | # as root under 10.3 (Panther) 407 | $gConfig{'openfolder_bless'} = 0; 408 | } 409 | } 410 | else { 411 | # If it's not Mac OS X, just assume all of those good features are 412 | # available. They're not, but things will fail long before they 413 | # have a chance to make a difference. 414 | # 415 | # Now, if someone wanted to document some of these private formats... 416 | print STDERR ($0.": warning, not running on Mac OS X, ". 417 | "this could be interesting.\n"); 418 | } 419 | 420 | # Non-global variables used in Getopt 421 | my(@attributes, @copyFiles, @createSymlinks, $iconFile, $idme, $licenseFile, 422 | @makeDirs, $outputFormat, @resourceFiles, $sourceFile, $sourceFolder, 423 | $targetImage, $tempDir, $volumeName); 424 | 425 | # --format 426 | $outputFormat = 'UDZO'; 427 | 428 | # --idme 429 | $idme = 0; 430 | 431 | # --sourcefile 432 | $sourceFile = 0; 433 | 434 | # Leaving this might screw up the Apple tools. 435 | delete $ENV{'NEXT_ROOT'}; 436 | 437 | # This script can get pretty messy, so trap a few signals. 438 | $SIG{'INT'} = \&trapSignal; 439 | $SIG{'HUP'} = \&trapSignal; 440 | $SIG{'TERM'} = \&trapSignal; 441 | 442 | Getopt::Long::Configure('pass_through'); 443 | GetOptions('source=s' => \$sourceFolder, 444 | 'target=s' => \$targetImage, 445 | 'volname=s' => \$volumeName, 446 | 'format=s' => \$outputFormat, 447 | 'tempdir=s' => \$tempDir, 448 | 'mkdir=s' => \@makeDirs, 449 | 'copy=s' => \@copyFiles, 450 | 'symlink=s' => \@createSymlinks, 451 | 'license=s' => \$licenseFile, 452 | 'resource=s' => \@resourceFiles, 453 | 'icon=s' => \$iconFile, 454 | 'attribute=s' => \@attributes, 455 | 'idme' => \$idme, 456 | 'sourcefile' => \$sourceFile, 457 | 'verbosity=i' => \$gVerbosity, 458 | 'dry-run' => \$gDryRun, 459 | 'config=s' => \%gConfig); # "hidden" option not in usage() 460 | 461 | if(@ARGV) { 462 | # All arguments are parsed by Getopt 463 | usage(); 464 | exit(1); 465 | } 466 | 467 | if($gVerbosity<0 || $gVerbosity>2) { 468 | usage(); 469 | exit(1); 470 | } 471 | 472 | if(!defined($sourceFolder) || $sourceFolder eq '' || 473 | !defined($targetImage) || $targetImage eq '') { 474 | # --source and --target are required arguments 475 | usage(); 476 | exit(1); 477 | } 478 | 479 | # Make sure $sourceFolder doesn't contain trailing slashes. It messes with 480 | # rsync. 481 | while(substr($sourceFolder, -1) eq '/') { 482 | chop($sourceFolder); 483 | } 484 | 485 | if(!defined($volumeName)) { 486 | # Default volumeName is the name of the source directory. 487 | my(@components); 488 | @components = pathSplit($sourceFolder); 489 | $volumeName = pop(@components); 490 | } 491 | 492 | my(@tempDirComponents, $targetImageFilename); 493 | @tempDirComponents = pathSplit($targetImage); 494 | $targetImageFilename = pop(@tempDirComponents); 495 | 496 | if(defined($tempDir)) { 497 | @tempDirComponents = pathSplit($tempDir); 498 | } 499 | else { 500 | # Default tempDir is the same directory as what is specified for 501 | # targetImage 502 | $tempDir = join('/', @tempDirComponents); 503 | } 504 | 505 | # Ensure that the path of the target image has a suitable extension. If 506 | # it didn't, hdiutil would add one, and we wouldn't be able to find the 507 | # file. 508 | # 509 | # Note that $targetImageFilename is not being reset. This is because it's 510 | # used to build other names below, and we don't need to be adding all sorts 511 | # of extra unnecessary extensions to the name. 512 | my($originalTargetImage, $requiredExtension); 513 | $originalTargetImage = $targetImage; 514 | if($outputFormat eq 'UDSP') { 515 | $requiredExtension = '.sparseimage'; 516 | } 517 | else { 518 | $requiredExtension = '.dmg'; 519 | } 520 | $targetImage = giveExtension($originalTargetImage, $requiredExtension); 521 | 522 | if($targetImage ne $originalTargetImage) { 523 | print STDERR ($0.": warning: target image extension is being added\n"); 524 | print STDERR (' The new filename is '. 525 | giveExtension($targetImageFilename,$requiredExtension)."\n"); 526 | } 527 | 528 | # Make a temporary directory in $tempDir for our own nefarious purposes. 529 | my(@output, $tempSubdir, $tempSubdirTemplate); 530 | $tempSubdirTemplate=join('/', @tempDirComponents, 531 | 'pkg-dmg.'.$$.'.XXXXXXXX'); 532 | if(!(@output = commandOutput($gConfig{'cmd_mktemp'}, '-d', 533 | $tempSubdirTemplate)) || $#output != 0) { 534 | cleanupDie('mktemp failed'); 535 | } 536 | 537 | if($gDryRun) { 538 | (@output)=($tempSubdirTemplate); 539 | } 540 | 541 | ($tempSubdir) = @output; 542 | 543 | push(@gCleanup, 544 | sub {commandVerbosity(0, $gConfig{'cmd_rm'}, '-rf', $tempSubdir);}); 545 | 546 | my($tempMount, $tempRoot, @tempsToMake); 547 | $tempRoot = $tempSubdir.'/stage'; 548 | $tempMount = $tempSubdir.'/mount'; 549 | push(@tempsToMake, $tempRoot); 550 | if($gConfig{'hdiutil_mountpoint'}) { 551 | push(@tempsToMake, $tempMount); 552 | } 553 | 554 | if(command($gConfig{'cmd_mkdir'}, @tempsToMake) != 0) { 555 | cleanupDie('mkdir tempRoot/tempMount failed'); 556 | } 557 | 558 | # This cleanup object is not strictly necessary, because $tempRoot is inside 559 | # of $tempSubdir, but the rest of the script relies on this object being 560 | # on the cleanup stack and expects to remove it. 561 | push(@gCleanup, 562 | sub {commandVerbosity(0, $gConfig{'cmd_rm'}, '-rf', $tempRoot);}); 563 | 564 | # If $sourceFile is true, it means that $sourceFolder is to be treated as 565 | # a file and placed as a file within the volume root, as opposed to being 566 | # treated as the volume root itself. rsync will do this by default, if no 567 | # trailing '/' is present. With a trailing '/', $sourceFolder becomes 568 | # $tempRoot, instead of becoming an entry in $tempRoot. 569 | if(command($gConfig{'cmd_rsync'}, '-a', '--copy-unsafe-links', 570 | $sourceFolder.($sourceFile?'':'/'),$tempRoot) != 0) { 571 | cleanupDie('rsync failed'); 572 | } 573 | 574 | if(@makeDirs) { 575 | my($makeDir, @tempDirsToMake); 576 | foreach $makeDir (@makeDirs) { 577 | if($makeDir =~ /^\//) { 578 | push(@tempDirsToMake, $tempRoot.$makeDir); 579 | } 580 | else { 581 | push(@tempDirsToMake, $tempRoot.'/'.$makeDir); 582 | } 583 | } 584 | if(command($gConfig{'cmd_mkdir'}, '-p', @tempDirsToMake) != 0) { 585 | cleanupDie('mkdir failed'); 586 | } 587 | } 588 | 589 | # copy files and/or create symlinks 590 | copyFiles($tempRoot, 'copy', @copyFiles); 591 | copyFiles($tempRoot, 'symlink', @createSymlinks); 592 | 593 | if($gConfig{'create_directly'}) { 594 | # If create_directly is false, the contents will be rsynced into a 595 | # disk image and they would lose their attributes. 596 | setAttributes($tempRoot, @attributes); 597 | } 598 | 599 | if(defined($iconFile)) { 600 | if(command($gConfig{'cmd_rsync'}, '-a', '--copy-unsafe-links', $iconFile, 601 | $tempRoot.'/.VolumeIcon.icns') != 0) { 602 | cleanupDie('rsync failed for volume icon'); 603 | } 604 | 605 | # It's pointless to set the attributes of the root when diskutil create 606 | # -srcfolder is being used. In that case, the attributes will be set 607 | # later, after the image is already created. 608 | if(isFormatCompressed($outputFormat) && 609 | (command($gConfig{'cmd_SetFile'}, '-a', 'C', $tempRoot) != 0)) { 610 | cleanupDie('SetFile failed'); 611 | } 612 | } 613 | 614 | if(command($gConfig{'cmd_chmod'}, '-R', 'a+rX,a-st,u+w,go-w', 615 | $tempRoot) != 0) { 616 | cleanupDie('chmod failed'); 617 | } 618 | 619 | my($unflattenable); 620 | if(isFormatCompressed($outputFormat)) { 621 | $unflattenable = 1; 622 | } 623 | else { 624 | $unflattenable = 0; 625 | } 626 | 627 | diskImageMaker($tempRoot, $targetImage, $outputFormat, $volumeName, 628 | $tempSubdir, $tempMount, $targetImageFilename, defined($iconFile)); 629 | 630 | if(defined($licenseFile) && $licenseFile ne '') { 631 | my($licenseResource); 632 | $licenseResource = $tempSubdir.'/license.r'; 633 | if(!licenseMaker($licenseFile, $licenseResource)) { 634 | cleanupDie('licenseMaker failed'); 635 | } 636 | push(@resourceFiles, $licenseResource); 637 | # Don't add a cleanup object because licenseResource is in tempSubdir. 638 | } 639 | 640 | if(@resourceFiles) { 641 | # Add resources, such as a license agreement. 642 | 643 | # Only unflatten read-only and compressed images. It's not supported 644 | # on other image times. 645 | if($unflattenable && 646 | (command($gConfig{'cmd_hdiutil'}, 'unflatten', $targetImage)) != 0) { 647 | cleanupDie('hdiutil unflatten failed'); 648 | } 649 | # Don't push flatten onto the cleanup stack. If we fail now, we'll be 650 | # removing $targetImage anyway. 651 | 652 | # Type definitions come from Carbon.r. 653 | if(command($gConfig{'cmd_Rez'}, 'Carbon.r', @resourceFiles, '-a', '-o', 654 | $targetImage) != 0) { 655 | cleanupDie('Rez failed'); 656 | } 657 | 658 | # Flatten. This merges the resource fork into the data fork, so no 659 | # special encoding is needed to transfer the file. 660 | if($unflattenable && 661 | (command($gConfig{'cmd_hdiutil'}, 'flatten', $targetImage)) != 0) { 662 | cleanupDie('hdiutil flatten failed'); 663 | } 664 | } 665 | 666 | # $tempSubdir is no longer needed. It's buried on the stack below the 667 | # rm of the fresh image file. Splice in this fashion is equivalent to 668 | # pop-save, pop, push-save. 669 | splice(@gCleanup, -2, 1); 670 | # No need to remove licenseResource separately, it's in tempSubdir. 671 | if(command($gConfig{'cmd_rm'}, '-rf', $tempSubdir) != 0) { 672 | cleanupDie('rm -rf tempSubdir failed'); 673 | } 674 | 675 | if($idme) { 676 | if(command($gConfig{'cmd_hdiutil'}, 'internet-enable', '-yes', 677 | $targetImage) != 0) { 678 | cleanupDie('hdiutil internet-enable failed'); 679 | } 680 | } 681 | 682 | # Done. 683 | 684 | exit(0); 685 | 686 | # argumentEscape(@arguments) 687 | # 688 | # Takes a list of @arguments and makes them shell-safe. 689 | sub argumentEscape(@) { 690 | my(@arguments); 691 | @arguments = @_; 692 | my($argument, @argumentsOut); 693 | foreach $argument (@arguments) { 694 | $argument =~ s%([^A-Za-z0-9_\-/.=+,])%\\$1%g; 695 | push(@argumentsOut, $argument); 696 | } 697 | return @argumentsOut; 698 | } 699 | 700 | # cleanupDie($message) 701 | # 702 | # Displays $message as an error message, and then runs through the 703 | # @gCleanup stack, performing any cleanup operations needed before 704 | # exiting. Does not return, exits with exit status 1. 705 | sub cleanupDie($) { 706 | my($message); 707 | ($message) = @_; 708 | print STDERR ($0.': '.$message.(@gCleanup?' (cleaning up)':'')."\n"); 709 | while(@gCleanup) { 710 | my($subroutine); 711 | $subroutine = pop(@gCleanup); 712 | &$subroutine; 713 | } 714 | exit(1); 715 | } 716 | 717 | # command(@arguments) 718 | # 719 | # Runs the specified command at the verbosity level defined by $gVerbosity. 720 | # Returns nonzero on failure, returning the exit status if appropriate. 721 | # Discards command output. 722 | sub command(@) { 723 | my(@arguments); 724 | @arguments = @_; 725 | return commandVerbosity($gVerbosity,@arguments); 726 | } 727 | 728 | # commandInternal($command, @arguments) 729 | # 730 | # Runs the specified internal command at the verbosity level defined by 731 | # $gVerbosity. 732 | # Returns zero(!) on failure, because commandInternal is supposed to be a 733 | # direct replacement for the Perl system call wrappers, which, unlike shell 734 | # commands and C equivalent system calls, return true (instead of 0) to 735 | # indicate success. 736 | sub commandInternal($@) { 737 | my(@arguments, $command); 738 | ($command, @arguments) = @_; 739 | return commandInternalVerbosity($gVerbosity, $command, @arguments); 740 | } 741 | 742 | # commandInternalVerbosity($verbosity, $command, @arguments) 743 | # 744 | # Run an internal command, printing a bogus command invocation message if 745 | # $verbosity is true. 746 | # 747 | # If $command is unlink: 748 | # Removes the files specified by @arguments. Wraps unlink. 749 | # 750 | # If $command is symlink: 751 | # Creates the symlink specified by @arguments. Wraps symlink. 752 | sub commandInternalVerbosity($$@) { 753 | my(@arguments, $command, $verbosity); 754 | ($verbosity, $command, @arguments) = @_; 755 | if($command eq 'unlink') { 756 | if($verbosity || $gDryRun) { 757 | print(join(' ', 'rm', '-f', argumentEscape(@arguments))."\n"); 758 | } 759 | if($gDryRun) { 760 | return $#arguments+1; 761 | } 762 | return unlink(@arguments); 763 | } 764 | elsif($command eq 'symlink') { 765 | if($verbosity || $gDryRun) { 766 | print(join(' ', 'ln', '-s', argumentEscape(@arguments))."\n"); 767 | } 768 | if($gDryRun) { 769 | return 1; 770 | } 771 | my($source, $target); 772 | ($source, $target) = @arguments; 773 | return symlink($source, $target); 774 | } 775 | } 776 | 777 | # commandOutput(@arguments) 778 | # 779 | # Runs the specified command at the verbosity level defined by $gVerbosity. 780 | # Output is returned in an array of lines. undef is returned on failure. 781 | # The exit status is available in $?. 782 | sub commandOutput(@) { 783 | my(@arguments); 784 | @arguments = @_; 785 | return commandOutputVerbosity($gVerbosity, @arguments); 786 | } 787 | 788 | # commandOutputVerbosity($verbosity, @arguments) 789 | # 790 | # Runs the specified command at the verbosity level defined by the 791 | # $verbosity argument. Output is returned in an array of lines. undef is 792 | # returned on failure. The exit status is available in $?. 793 | # 794 | # If an error occurs in fork or exec, an error message is printed to 795 | # stderr and undef is returned. 796 | # 797 | # If $verbosity is 0, the command invocation is not printed, and its 798 | # stdout is not echoed back to stdout. 799 | # 800 | # If $verbosity is 1, the command invocation is printed. 801 | # 802 | # If $verbosity is 2, the command invocation is printed and the output 803 | # from stdout is echoed back to stdout. 804 | # 805 | # Regardless of $verbosity, stderr is left connected. 806 | sub commandOutputVerbosity($@) { 807 | my(@arguments, $verbosity); 808 | ($verbosity, @arguments) = @_; 809 | my($pid); 810 | if($verbosity || $gDryRun) { 811 | print(join(' ', argumentEscape(@arguments))."\n"); 812 | } 813 | if($gDryRun) { 814 | return(1); 815 | } 816 | if (!defined($pid = open(*COMMAND, '-|'))) { 817 | printf STDERR ($0.': fork: '.$!."\n"); 818 | return undef; 819 | } 820 | elsif ($pid) { 821 | # parent 822 | my(@lines); 823 | while(!eof(*COMMAND)) { 824 | my($line); 825 | chop($line = ); 826 | if($verbosity > 1) { 827 | print($line."\n"); 828 | } 829 | push(@lines, $line); 830 | } 831 | close(*COMMAND); 832 | if ($? == -1) { 833 | printf STDERR ($0.': fork: '.$!."\n"); 834 | return undef; 835 | } 836 | elsif ($? & 127) { 837 | printf STDERR ($0.': exited on signal '.($? & 127). 838 | ($? & 128 ? ', core dumped' : '')."\n"); 839 | return undef; 840 | } 841 | return @lines; 842 | } 843 | else { 844 | # child; this form of exec is immune to shell games 845 | if(!exec {$arguments[0]} (@arguments)) { 846 | printf STDERR ($0.': exec: '.$!."\n"); 847 | exit(-1); 848 | } 849 | } 850 | } 851 | 852 | # commandVerbosity($verbosity, @arguments) 853 | # 854 | # Runs the specified command at the verbosity level defined by the 855 | # $verbosity argument. Returns nonzero on failure, returning the exit 856 | # status if appropriate. Discards command output. 857 | sub commandVerbosity($@) { 858 | my(@arguments, $verbosity); 859 | ($verbosity, @arguments) = @_; 860 | if(!defined(commandOutputVerbosity($verbosity, @arguments))) { 861 | return -1; 862 | } 863 | return $?; 864 | } 865 | 866 | # copyFiles($tempRoot, $method, @arguments) 867 | # 868 | # Copies files or create symlinks in the disk image. 869 | # See --copy and --symlink descriptions for details. 870 | # If $method is 'copy', @arguments are interpreted as source:target, if $method 871 | # is 'symlink', @arguments are interpreted as symlink:target. 872 | sub copyFiles($@) { 873 | my(@fileList, $method, $tempRoot); 874 | ($tempRoot, $method, @fileList) = @_; 875 | my($file, $isSymlink); 876 | $isSymlink = ($method eq 'symlink'); 877 | foreach $file (@fileList) { 878 | my($source, $target); 879 | ($source, $target) = split(/:/, $file); 880 | if(!defined($target) and $isSymlink) { 881 | # empty symlink targets would result in an invalid target and fail, 882 | # but they shall be interpreted as "like source path, but inside dmg" 883 | $target = $source; 884 | } 885 | if(!defined($target)) { 886 | $target = $tempRoot; 887 | } 888 | elsif($target =~ /^\//) { 889 | $target = $tempRoot.$target; 890 | } 891 | else { 892 | $target = $tempRoot.'/'.$target; 893 | } 894 | 895 | my($success); 896 | if($isSymlink) { 897 | $success = commandInternal('symlink', $source, $target); 898 | } 899 | else { 900 | $success = !command($gConfig{'cmd_rsync'}, '-a', '--copy-unsafe-links', 901 | $source, $target); 902 | } 903 | if(!$success) { 904 | cleanupDie('copyFiles failed for method '.$method); 905 | } 906 | } 907 | } 908 | 909 | # diskImageMaker($source, $destination, $format, $name, $tempDir, $tempMount, 910 | # $baseName, $setRootIcon) 911 | # 912 | # Creates a disk image in $destination of format $format corresponding to the 913 | # source directory $source. $name is the volume name. $tempDir is a good 914 | # place to write temporary files, which should be empty (aside from the other 915 | # things that this script might create there, like stage and mount). 916 | # $tempMount is a mount point for temporary disk images. $baseName is the 917 | # name of the disk image, and is presently unused. $setRootIcon is true if 918 | # a volume icon was added to the staged $source and indicates that the 919 | # custom volume icon bit on the volume root needs to be set. 920 | sub diskImageMaker($$$$$$$$) { 921 | my($baseName, $destination, $format, $name, $setRootIcon, $source, 922 | $tempDir, $tempMount); 923 | ($source, $destination, $format, $name, $tempDir, $tempMount, 924 | $baseName, $setRootIcon) = @_; 925 | if(isFormatCompressed($format)) { 926 | my($uncompressedImage); 927 | 928 | if($gConfig{'makehybrid'}) { 929 | my($hybridImage); 930 | $hybridImage = giveExtension($tempDir.'/hybrid', '.dmg'); 931 | 932 | if(command($gConfig{'cmd_hdiutil'}, 'makehybrid', '-hfs', 933 | '-hfs-volume-name', $name, '-hfs-openfolder', $source, '-ov', 934 | $source, '-o', $hybridImage) != 0) { 935 | cleanupDie('hdiutil makehybrid failed'); 936 | } 937 | 938 | $uncompressedImage = $hybridImage; 939 | 940 | # $source is no longer needed and will be removed before anything 941 | # else can fail. splice in this form is the same as pop/push. 942 | splice(@gCleanup, -1, 1, 943 | sub {commandInternalVerbosity(0, 'unlink', $hybridImage);}); 944 | 945 | if(command($gConfig{'cmd_rm'}, '-rf', $source) != 0) { 946 | cleanupDie('rm -rf failed'); 947 | } 948 | 949 | if(!$gConfig{'partition_table'} && $gConfig{'recursive_access'}) { 950 | # Even if we do want to create disk images without partition tables, 951 | # it's impossible unless recursive_access is set. 952 | my($rootDevice, $partitionDevice, $partitionMountPoint); 953 | 954 | if(!(($rootDevice, $partitionDevice, $partitionMountPoint) = 955 | hdidMountImage($tempMount, '-readonly', $hybridImage))) { 956 | cleanupDie('hdid mount failed'); 957 | } 958 | 959 | push(@gCleanup, sub {commandVerbosity(0, 960 | $gConfig{'cmd_diskutil'}, 'eject', $rootDevice);}); 961 | 962 | my($udrwImage); 963 | $udrwImage = giveExtension($tempDir.'/udrw', '.dmg'); 964 | 965 | if(command($gConfig{'cmd_hdiutil'}, 'create', '-format', 'UDRW', 966 | '-ov', '-srcdevice', $partitionDevice, $udrwImage) != 0) { 967 | cleanupDie('hdiutil create failed'); 968 | } 969 | 970 | $uncompressedImage = $udrwImage; 971 | 972 | # Going to eject before anything else can fail. Get the eject off 973 | # the stack. 974 | pop(@gCleanup); 975 | 976 | # $hybridImage will be removed soon, but until then, it needs to 977 | # stay on the cleanup stack. It needs to wait until after 978 | # ejection. $udrwImage is staying around. Make it appear as 979 | # though it's been done before $hybridImage. 980 | # 981 | # splice in this form is the same as popping one element to 982 | # @tempCleanup and pushing the subroutine. 983 | my(@tempCleanup); 984 | @tempCleanup = splice(@gCleanup, -1, 1, 985 | sub {commandInternalVerbosity(0, 'unlink', $udrwImage);}); 986 | push(@gCleanup, @tempCleanup); 987 | 988 | if(command($gConfig{'cmd_diskutil'}, 'eject', $rootDevice) != 0) { 989 | cleanupDie('diskutil eject failed'); 990 | } 991 | 992 | # Pop unlink of $uncompressedImage 993 | pop(@gCleanup); 994 | 995 | if(commandInternal('unlink', $hybridImage) != 1) { 996 | cleanupDie('unlink hybridImage failed: '.$!); 997 | } 998 | } 999 | } 1000 | else { 1001 | # makehybrid is not available, fall back to making a UDRW and 1002 | # converting to a compressed image. It ought to be possible to 1003 | # create a compressed image directly, but those come out far too 1004 | # large (journaling?) and need to be read-write to fix up the 1005 | # volume icon anyway. Luckily, we can take advantage of a single 1006 | # call back into this function. 1007 | my($udrwImage); 1008 | $udrwImage = giveExtension($tempDir.'/udrw', '.dmg'); 1009 | 1010 | diskImageMaker($source, $udrwImage, 'UDRW', $name, $tempDir, 1011 | $tempMount, $baseName, $setRootIcon); 1012 | 1013 | # The call back into diskImageMaker already removed $source. 1014 | 1015 | $uncompressedImage = $udrwImage; 1016 | } 1017 | 1018 | # The uncompressed disk image is now in its final form. Compress it. 1019 | # Jaguar doesn't support hdiutil convert -ov, but it always allows 1020 | # overwriting. 1021 | # bzip2-compressed UDBZ images can only be created and mounted on 10.4 1022 | # and later. The bzip2-level imagekey is only effective when creating 1023 | # images in 10.5. In 10.4, bzip2-level is harmlessly ignored, and the 1024 | # default value of 1 is always used. 1025 | if(command($gConfig{'cmd_hdiutil'}, 'convert', '-format', $format, 1026 | '-imagekey', ($format eq 'UDBZ' ? 'bzip2-level=9' : 'zlib-level=9'), 1027 | (defined($gDarwinMajor) && $gDarwinMajor <= 6 ? () : ('-ov')), 1028 | $uncompressedImage, '-o', $destination) != 0) { 1029 | cleanupDie('hdiutil convert failed'); 1030 | } 1031 | 1032 | # $uncompressedImage is going to be unlinked before anything else can 1033 | # fail. splice in this form is the same as pop/push. 1034 | splice(@gCleanup, -1, 1, 1035 | sub {commandInternalVerbosity(0, 'unlink', $destination);}); 1036 | 1037 | if(commandInternal('unlink', $uncompressedImage) != 1) { 1038 | cleanupDie('unlink uncompressedImage failed: '.$!); 1039 | } 1040 | 1041 | # At this point, the only thing that the compressed block has added to 1042 | # the cleanup stack is the removal of $destination. $source has already 1043 | # been removed, and its cleanup entry has been removed as well. 1044 | } 1045 | elsif($format eq 'UDRW' || $format eq 'UDSP') { 1046 | my(@extraArguments); 1047 | if(!$gConfig{'partition_table'}) { 1048 | @extraArguments = ('-layout', 'NONE'); 1049 | } 1050 | 1051 | if($gConfig{'create_directly'}) { 1052 | # Use -fs HFS+ to suppress the journal. 1053 | if(command($gConfig{'cmd_hdiutil'}, 'create', '-format', $format, 1054 | @extraArguments, '-fs', 'HFS+', '-volname', $name, 1055 | '-ov', '-srcfolder', $source, $destination) != 0) { 1056 | cleanupDie('hdiutil create failed'); 1057 | } 1058 | 1059 | # $source is no longer needed and will be removed before anything 1060 | # else can fail. splice in this form is the same as pop/push. 1061 | splice(@gCleanup, -1, 1, 1062 | sub {commandInternalVerbosity(0, 'unlink', $destination);}); 1063 | 1064 | if(command($gConfig{'cmd_rm'}, '-rf', $source) != 0) { 1065 | cleanupDie('rm -rf failed'); 1066 | } 1067 | } 1068 | else { 1069 | # hdiutil create does not support -srcfolder or -srcdevice, it only 1070 | # knows how to create blank images. Figure out how large an image 1071 | # is needed, create it, and fill it. This is needed for Jaguar. 1072 | 1073 | # Use native block size for hdiutil create -sectors. 1074 | delete $ENV{'BLOCKSIZE'}; 1075 | 1076 | my(@duOutput, $ignore, $sizeBlocks, $sizeOverhead, $sizeTotal, $type); 1077 | if(!(@output = commandOutput($gConfig{'cmd_du'}, '-s', $tempRoot)) || 1078 | $? != 0) { 1079 | cleanupDie('du failed'); 1080 | } 1081 | ($sizeBlocks, $ignore) = split(' ', $output[0], 2); 1082 | 1083 | # The filesystem itself takes up 152 blocks of its own blocks for the 1084 | # filesystem up to 8192 blocks, plus 64 blocks for every additional 1085 | # 4096 blocks or portion thereof. 1086 | $sizeOverhead = 152 + 64 * POSIX::ceil( 1087 | (($sizeBlocks - 8192) > 0) ? (($sizeBlocks - 8192) / (4096 - 64)) : 0); 1088 | 1089 | # The number of blocks must be divisible by 8. 1090 | my($mod); 1091 | if($mod = ($sizeOverhead % 8)) { 1092 | $sizeOverhead += 8 - $mod; 1093 | } 1094 | 1095 | # sectors is taken as the size of a disk, not a filesystem, so the 1096 | # partition table eats into it. 1097 | if($gConfig{'partition_table'}) { 1098 | $sizeOverhead += 80; 1099 | } 1100 | 1101 | # That was hard. Leave some breathing room anyway. Use 1024 sectors 1102 | # (512kB). These read-write images wouldn't be useful if they didn't 1103 | # have at least a little free space. 1104 | $sizeTotal = $sizeBlocks + $sizeOverhead + 1024; 1105 | 1106 | # Minimum sizes - these numbers are larger on Jaguar than on later 1107 | # systems. Just use the Jaguar numbers, since it's unlikely to wind 1108 | # up here on any other release. 1109 | if($gConfig{'partition_table'} && $sizeTotal < 8272) { 1110 | $sizeTotal = 8272; 1111 | } 1112 | if(!$gConfig{'partition_table'} && $sizeTotal < 8192) { 1113 | $sizeTotal = 8192; 1114 | } 1115 | 1116 | # hdiutil create without -srcfolder or -srcdevice will not accept 1117 | # -format. It uses -type. Fortunately, the two supported formats 1118 | # here map directly to the only two supported types. 1119 | if ($format eq 'UDSP') { 1120 | $type = 'SPARSE'; 1121 | } 1122 | else { 1123 | $type = 'UDIF'; 1124 | } 1125 | 1126 | if(command($gConfig{'cmd_hdiutil'}, 'create', '-type', $type, 1127 | @extraArguments, '-fs', 'HFS+', '-volname', $name, 1128 | '-ov', '-sectors', $sizeTotal, $destination) != 0) { 1129 | cleanupDie('hdiutil create failed'); 1130 | } 1131 | 1132 | push(@gCleanup, 1133 | sub {commandInternalVerbosity(0, 'unlink', $destination);}); 1134 | 1135 | # The rsync will occur shortly. 1136 | } 1137 | 1138 | my($mounted, $rootDevice, $partitionDevice, $partitionMountPoint); 1139 | 1140 | $mounted=0; 1141 | if(!$gConfig{'create_directly'} || $gConfig{'openfolder_bless'} || 1142 | $setRootIcon) { 1143 | # The disk image only needs to be mounted if: 1144 | # create_directly is false, because the content needs to be copied 1145 | # openfolder_bless is true, because bless -openfolder needs to run 1146 | # setRootIcon is true, because the root needs its attributes set. 1147 | if(!(($rootDevice, $partitionDevice, $partitionMountPoint) = 1148 | hdidMountImage($tempMount, $destination))) { 1149 | cleanupDie('hdid mount failed'); 1150 | } 1151 | 1152 | $mounted=1; 1153 | 1154 | push(@gCleanup, sub {commandVerbosity(0, 1155 | $gConfig{'cmd_diskutil'}, 'eject', $rootDevice);}); 1156 | } 1157 | 1158 | if(!$gConfig{'create_directly'}) { 1159 | # Couldn't create and copy directly in one fell swoop. Now that 1160 | # the volume is mounted, copy the files. --copy-unsafe-links is 1161 | # unnecessary since it was used to copy everything to the staging 1162 | # area. There can be no more unsafe links. 1163 | if(command($gConfig{'cmd_rsync'}, '-a', 1164 | $source.'/',$partitionMountPoint) != 0) { 1165 | cleanupDie('rsync to new volume failed'); 1166 | } 1167 | 1168 | # We need to get the rm -rf of $source off the stack, because it's 1169 | # being cleaned up here. There are two items now on top of it: 1170 | # removing the target image and, above that, ejecting it. Splice it 1171 | # out. 1172 | my(@tempCleanup); 1173 | @tempCleanup = splice(@gCleanup, -2); 1174 | # The next splice is the same as popping once and pushing @tempCleanup. 1175 | splice(@gCleanup, -1, 1, @tempCleanup); 1176 | 1177 | if(command($gConfig{'cmd_rm'}, '-rf', $source) != 0) { 1178 | cleanupDie('rm -rf failed'); 1179 | } 1180 | } 1181 | 1182 | if($gConfig{'openfolder_bless'}) { 1183 | # On Tiger, the bless docs say to use --openfolder, but only 1184 | # --openfolder is accepted on Panther. Tiger takes it with a single 1185 | # dash too. Jaguar is out of luck. 1186 | if(command($gConfig{'cmd_bless'}, '-openfolder', 1187 | $partitionMountPoint) != 0) { 1188 | cleanupDie('bless failed'); 1189 | } 1190 | } 1191 | 1192 | setAttributes($partitionMountPoint, @attributes); 1193 | 1194 | if($setRootIcon) { 1195 | # When "hdiutil create -srcfolder" is used, the root folder's 1196 | # attributes are not copied to the new volume. Fix up. 1197 | 1198 | if(command($gConfig{'cmd_SetFile'}, '-a', 'C', 1199 | $partitionMountPoint) != 0) { 1200 | cleanupDie('SetFile failed'); 1201 | } 1202 | } 1203 | 1204 | if($mounted) { 1205 | # Pop diskutil eject 1206 | pop(@gCleanup); 1207 | 1208 | if(command($gConfig{'cmd_diskutil'}, 'eject', $rootDevice) != 0) { 1209 | cleanupDie('diskutil eject failed'); 1210 | } 1211 | } 1212 | 1213 | # End of UDRW/UDSP section. At this point, $source has been removed 1214 | # and its cleanup entry has been removed from the stack. 1215 | } 1216 | else { 1217 | cleanupDie('unrecognized format'); 1218 | print STDERR ($0.": unrecognized format\n"); 1219 | exit(1); 1220 | } 1221 | } 1222 | 1223 | # giveExtension($file, $extension) 1224 | # 1225 | # If $file does not end in $extension, $extension is added. The new 1226 | # filename is returned. 1227 | sub giveExtension($$) { 1228 | my($extension, $file); 1229 | ($file, $extension) = @_; 1230 | if(substr($file, -length($extension)) ne $extension) { 1231 | return $file.$extension; 1232 | } 1233 | return $file; 1234 | } 1235 | 1236 | # hdidMountImage($mountPoint, @arguments) 1237 | # 1238 | # Runs the hdid command with arguments specified by @arguments. 1239 | # @arguments may be a single-element array containing the name of the 1240 | # disk image to mount. Returns a three-element array, with elements 1241 | # corresponding to: 1242 | # - The root device of the mounted image, suitable for ejection 1243 | # - The device corresponding to the mounted partition 1244 | # - The mounted partition's mount point 1245 | # 1246 | # If running on a system that supports easy mounting at points outside 1247 | # of the default /Volumes with hdiutil attach, it is used instead of hdid, 1248 | # and $mountPoint is used as the mount point. 1249 | # 1250 | # The root device will differ from the partition device when the disk 1251 | # image contains a partition table, otherwise, they will be identical. 1252 | # 1253 | # If hdid fails, undef is returned. 1254 | sub hdidMountImage($@) { 1255 | my(@arguments, @command, $mountPoint); 1256 | ($mountPoint, @arguments) = @_; 1257 | my(@output); 1258 | 1259 | if($gConfig{'hdiutil_mountpoint'}) { 1260 | @command=($gConfig{'cmd_hdiutil'}, 'attach', @arguments, 1261 | '-mountpoint', $mountPoint); 1262 | } 1263 | else { 1264 | @command=($gConfig{'cmd_hdid'}, @arguments); 1265 | } 1266 | 1267 | if(!(@output = commandOutput(@command)) || 1268 | $? != 0) { 1269 | return undef; 1270 | } 1271 | 1272 | if($gDryRun) { 1273 | return('/dev/diskX','/dev/diskXsY','/Volumes/'.$volumeName); 1274 | } 1275 | 1276 | my($line, $restOfLine, $rootDevice); 1277 | 1278 | foreach $line (@output) { 1279 | my($device, $mountpoint); 1280 | if($line !~ /^\/dev\//) { 1281 | # Consider only lines that correspond to /dev entries 1282 | next; 1283 | } 1284 | ($device, $restOfLine) = split(' ', $line, 2); 1285 | 1286 | if(!defined($rootDevice) || $rootDevice eq '') { 1287 | # If this is the first device seen, it's the root device to be 1288 | # used for ejection. Keep it. 1289 | $rootDevice = $device; 1290 | } 1291 | 1292 | if($restOfLine =~ /(\/.*)/) { 1293 | # The first partition with a mount point is the interesting one. It's 1294 | # usually Apple_HFS and usually the last one in the list, but beware of 1295 | # the possibility of other filesystem types and the Apple_Free partition. 1296 | # If the disk image contains no partition table, the partition will not 1297 | # have a type, so look for the mount point by looking for a slash. 1298 | $mountpoint = $1; 1299 | return($rootDevice, $device, $mountpoint); 1300 | } 1301 | } 1302 | 1303 | # No mount point? This is bad. If there's a root device, eject it. 1304 | if(defined($rootDevice) && $rootDevice ne '') { 1305 | # Failing anyway, so don't care about failure 1306 | commandVerbosity(0, $gConfig{'cmd_diskutil'}, 'eject', $rootDevice); 1307 | } 1308 | 1309 | return undef; 1310 | } 1311 | 1312 | # isFormatCompressed($format) 1313 | # 1314 | # Returns true if $format corresponds to a compressed disk image format. 1315 | # Returns false otherwise. 1316 | sub isFormatCompressed($) { 1317 | my($format); 1318 | ($format) = @_; 1319 | return $format eq 'UDZO' || $format eq 'UDBZ'; 1320 | } 1321 | 1322 | # licenseMaker($text, $resource) 1323 | # 1324 | # Takes a plain text file at path $text and creates a license agreement 1325 | # resource containing the text at path $license. English-only, and 1326 | # no special formatting. This is the bare-bones stuff. For more 1327 | # intricate license agreements, create your own resource. 1328 | # 1329 | # ftp://ftp.apple.com/developer/Development_Kits/SLAs_for_UDIFs_1.0.dmg 1330 | sub licenseMaker($$) { 1331 | my($resource, $text); 1332 | ($text, $resource) = @_; 1333 | if(!sysopen(*TEXT, $text, O_RDONLY)) { 1334 | print STDERR ($0.': licenseMaker: sysopen text: '.$!."\n"); 1335 | return 0; 1336 | } 1337 | if(!sysopen(*RESOURCE, $resource, O_WRONLY|O_CREAT|O_EXCL)) { 1338 | print STDERR ($0.': licenseMaker: sysopen resource: '.$!."\n"); 1339 | return 0; 1340 | } 1341 | print RESOURCE << '__EOT__'; 1342 | // See /System/Library/Frameworks/CoreServices.framework/Frameworks/CarbonCore.framework/Headers/Script.h for language IDs. 1343 | data 'LPic' (5000) { 1344 | // Default language ID, 0 = English 1345 | $"0000" 1346 | // Number of entries in list 1347 | $"0001" 1348 | 1349 | // Entry 1 1350 | // Language ID, 0 = English 1351 | $"0000" 1352 | // Resource ID, 0 = STR#/TEXT/styl 5000 1353 | $"0000" 1354 | // Multibyte language, 0 = no 1355 | $"0000" 1356 | }; 1357 | 1358 | resource 'STR#' (5000, "English") { 1359 | { 1360 | // Language (unused?) = English 1361 | "English", 1362 | // Agree 1363 | "Agree", 1364 | // Disagree 1365 | "Disagree", 1366 | __EOT__ 1367 | # This stuff needs double-quotes for interpolations to work. 1368 | print RESOURCE (" // Print, ellipsis is 0xC9\n"); 1369 | print RESOURCE (" \"Print\xc9\",\n"); 1370 | print RESOURCE (" // Save As, ellipsis is 0xC9\n"); 1371 | print RESOURCE (" \"Save As\xc9\",\n"); 1372 | print RESOURCE (' // Descriptive text, curly quotes are 0xD2 and 0xD3'. 1373 | "\n"); 1374 | print RESOURCE (' "If you agree to the terms of this license '. 1375 | "agreement, click \xd2Agree\xd3 to access the software. If you ". 1376 | "do not agree, press \xd2Disagree.\xd3\"\n"); 1377 | print RESOURCE << '__EOT__'; 1378 | }; 1379 | }; 1380 | 1381 | // Beware of 1024(?) byte (character?) line length limitation. Split up long 1382 | // lines. 1383 | // If straight quotes are used ("), remember to escape them (\"). 1384 | // Newline is \n, to leave a blank line, use two of them. 1385 | // 0xD2 and 0xD3 are curly double-quotes ("), 0xD4 and 0xD5 are curly 1386 | // single quotes ('), 0xD5 is also the apostrophe. 1387 | data 'TEXT' (5000, "English") { 1388 | __EOT__ 1389 | 1390 | while(!eof(*TEXT)) { 1391 | my($line); 1392 | chop($line = ); 1393 | 1394 | while(defined($line)) { 1395 | my($chunk); 1396 | 1397 | # Rez doesn't care for lines longer than (1024?) characters. Split 1398 | # at less than half of that limit, in case everything needs to be 1399 | # backwhacked. 1400 | if(length($line)>500) { 1401 | $chunk = substr($line, 0, 500); 1402 | $line = substr($line, 500); 1403 | } 1404 | else { 1405 | $chunk = $line; 1406 | $line = undef; 1407 | } 1408 | 1409 | if(length($chunk) > 0) { 1410 | # Unsafe characters are the double-quote (") and backslash (\), escape 1411 | # them with backslashes. 1412 | $chunk =~ s/(["\\])/\\$1/g; 1413 | 1414 | print RESOURCE ' "'.$chunk.'"'."\n"; 1415 | } 1416 | } 1417 | print RESOURCE ' "\n"'."\n"; 1418 | } 1419 | close(*TEXT); 1420 | 1421 | print RESOURCE << '__EOT__'; 1422 | }; 1423 | 1424 | data 'styl' (5000, "English") { 1425 | // Number of styles following = 1 1426 | $"0001" 1427 | 1428 | // Style 1. This is used to display the first two lines in bold text. 1429 | // Start character = 0 1430 | $"0000 0000" 1431 | // Height = 16 1432 | $"0010" 1433 | // Ascent = 12 1434 | $"000C" 1435 | // Font family = 1024 (Lucida Grande) 1436 | $"0400" 1437 | // Style bitfield, 0x1=bold 0x2=italic 0x4=underline 0x8=outline 1438 | // 0x10=shadow 0x20=condensed 0x40=extended 1439 | $"00" 1440 | // Style, unused? 1441 | $"02" 1442 | // Size = 12 point 1443 | $"000C" 1444 | // Color, RGB 1445 | $"0000 0000 0000" 1446 | }; 1447 | __EOT__ 1448 | close(*RESOURCE); 1449 | 1450 | return 1; 1451 | } 1452 | 1453 | # pathSplit($pathname) 1454 | # 1455 | # Splits $pathname into an array of path components. 1456 | sub pathSplit($) { 1457 | my($pathname); 1458 | ($pathname) = @_; 1459 | return split(/\//, $pathname); 1460 | } 1461 | 1462 | # setAttributes($root, @attributeList) 1463 | # 1464 | # @attributeList is an array, each element of which must be in the form 1465 | # :. is a list of attributes, per SetFile. is a file 1466 | # which is taken as relative to $root (even if it appears as an absolute 1467 | # path.) SetFile is called to set the attributes on each file in 1468 | # @attributeList. 1469 | sub setAttributes($@) { 1470 | my(@attributes, $root); 1471 | ($root, @attributes) = @_; 1472 | my($attribute); 1473 | foreach $attribute (@attributes) { 1474 | my($attrList, $file, @fileList, @fixedFileList); 1475 | ($attrList, @fileList) = split(/:/, $attribute); 1476 | if(!defined($attrList) || !@fileList) { 1477 | cleanupDie('--attribute requires :'); 1478 | } 1479 | @fixedFileList=(); 1480 | foreach $file (@fileList) { 1481 | if($file =~ /^\//) { 1482 | push(@fixedFileList, $root.$file); 1483 | } 1484 | else { 1485 | push(@fixedFileList, $root.'/'.$file); 1486 | } 1487 | } 1488 | if(command($gConfig{'cmd_SetFile'}, '-a', $attrList, @fixedFileList)) { 1489 | cleanupDie('SetFile failed to set attributes'); 1490 | } 1491 | } 1492 | return; 1493 | } 1494 | 1495 | sub trapSignal($) { 1496 | my($signalName); 1497 | ($signalName) = @_; 1498 | cleanupDie('exiting on SIG'.$signalName); 1499 | } 1500 | 1501 | sub usage() { 1502 | print STDERR ( 1503 | "usage: pkg-dmg --source \n". 1504 | " --target \n". 1505 | " [--format ] (default: UDZO)\n". 1506 | " [--volname ] (default: same name as source)\n". 1507 | " [--tempdir ] (default: same dir as target)\n". 1508 | " [--mkdir ] (make directory in image)\n". 1509 | " [--copy [:]] (extra files to add)\n". 1510 | " [--symlink [:]] (extra symlinks to add)\n". 1511 | " [--license ] (plain text license agreement)\n". 1512 | " [--resource ] (flat .r files to merge)\n". 1513 | " [--icon ] (volume icon)\n". 1514 | " [--attribute :] (set file attributes)\n". 1515 | " [--idme] (make Internet-enabled image)\n". 1516 | " [--sourcefile] (treat --source as a file)\n". 1517 | " [--verbosity ] (0, 1, 2; default=2)\n". 1518 | " [--dry-run] (print what would be done)\n"); 1519 | return; 1520 | } 1521 | -------------------------------------------------------------------------------- /screen1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/soheil-zz/BirdDrop-OSX/b20de07ee6f197ceb7a91f6d7ca296ad8abad529/screen1.png -------------------------------------------------------------------------------- /version.txt: -------------------------------------------------------------------------------- 1 | 10210 2 | --------------------------------------------------------------------------------