diff options
author | Remko Tronçon <git@el-tramo.be> | 2010-03-28 15:46:49 (GMT) |
---|---|---|
committer | Remko Tronçon <git@el-tramo.be> | 2010-03-28 15:46:49 (GMT) |
commit | f53a1ef582494458301b97bf6e546be52d7ff7e8 (patch) | |
tree | 7571b5cbcbd8a8f1dd1c966c9045b6cb69f0e295 /Slimber | |
parent | 638345680d72ca6acaf123f2c8c1c391f696e371 (diff) | |
download | swift-contrib-f53a1ef582494458301b97bf6e546be52d7ff7e8.zip swift-contrib-f53a1ef582494458301b97bf6e546be52d7ff7e8.tar.bz2 |
Moving submodule contents back.
Diffstat (limited to 'Slimber')
44 files changed, 2646 insertions, 0 deletions
diff --git a/Slimber/.gitignore b/Slimber/.gitignore new file mode 100644 index 0000000..5f565e9 --- /dev/null +++ b/Slimber/.gitignore @@ -0,0 +1,5 @@ +*.o +*.a +*.app +*.nib +slimber diff --git a/Slimber/CLI/SConscript b/Slimber/CLI/SConscript new file mode 100644 index 0000000..b65843d --- /dev/null +++ b/Slimber/CLI/SConscript @@ -0,0 +1,8 @@ +Import("env") + +myenv = env.Clone() +myenv.MergeFlags(env["SLIMBER_FLAGS"]) +myenv.MergeFlags(env["SWIFTEN_FLAGS"]) +myenv.MergeFlags(env["BOOST_FLAGS"]) + +myenv.Program("slimber", ["main.cpp"]) diff --git a/Slimber/CLI/main.cpp b/Slimber/CLI/main.cpp new file mode 100644 index 0000000..09e7869 --- /dev/null +++ b/Slimber/CLI/main.cpp @@ -0,0 +1,32 @@ +#include <string> +#include <boost/bind.hpp> + +#include "Swiften/Base/Platform.h" +#include "Slimber/Server.h" +#include "Slimber/FileVCardCollection.h" +#include "Swiften/LinkLocal/LinkLocalServiceBrowser.h" +#include "Swiften/LinkLocal/DNSSD/DNSSDBrowseQuery.h" +#include "Swiften/LinkLocal/DNSSD/DNSSDRegisterQuery.h" +//#include "Swiften/LinkLocal/DNSSD/Bonjour/BonjourQuerier.h" +#include "Swiften/EventLoop/SimpleEventLoop.h" +#include "Swiften/Application/Platform/PlatformApplication.h" + +using namespace Swift; + +int main() { + SimpleEventLoop eventLoop; + /* + boost::shared_ptr<BonjourQuerier> querier(new BonjourQuerier()); + querier->start(); + LinkLocalServiceBrowser browser(querier); + browser.start(); + */ + +/* + FileVCardCollection vCardCollection(PlatformApplication("Slimber").getSettingsDir()); + Server server(5222, 5562, linkLocalRoster, dnsSDService, &vCardCollection); + */ + + eventLoop.run(); + return 0; +} diff --git a/Slimber/Cocoa/.gitignore b/Slimber/Cocoa/.gitignore new file mode 100644 index 0000000..66f3f9e --- /dev/null +++ b/Slimber/Cocoa/.gitignore @@ -0,0 +1,5 @@ +build +*.mode1v3 +*.pbxuser +Slimber +PkgInfo diff --git a/Slimber/Cocoa/CocoaAction.h b/Slimber/Cocoa/CocoaAction.h new file mode 100644 index 0000000..d02c8b5 --- /dev/null +++ b/Slimber/Cocoa/CocoaAction.h @@ -0,0 +1,20 @@ +#pragma once + +#include <Cocoa/Cocoa.h> +#include <boost/function.hpp> + +@interface CocoaAction : NSObject { + boost::function<void ()>* function; +} + +/** + * Acquires ownership of 'f'. + */ +- (id) initWithFunction: (boost::function<void()>*) f; + +/** + * Calls the functor passed as a parameter to the contsructor. + */ +- (void) doAction: (id) sender; + +@end diff --git a/Slimber/Cocoa/CocoaAction.mm b/Slimber/Cocoa/CocoaAction.mm new file mode 100644 index 0000000..15498a1 --- /dev/null +++ b/Slimber/Cocoa/CocoaAction.mm @@ -0,0 +1,22 @@ +#include "Slimber/Cocoa/CocoaAction.h" + +@implementation CocoaAction + +- (id) initWithFunction: (boost::function<void()>*) f { + if ([super init]) { + function = f; + } + return self; +} + +- (void) dealloc { + delete function; + [super dealloc]; +} + +- (void) doAction: (id) sender { + (void) sender; + (*function)(); +} + +@end diff --git a/Slimber/Cocoa/CocoaController.h b/Slimber/Cocoa/CocoaController.h new file mode 100644 index 0000000..f4be87d --- /dev/null +++ b/Slimber/Cocoa/CocoaController.h @@ -0,0 +1,11 @@ +#include <Cocoa/Cocoa.h> + +class MainController; +class CocoaMenulet; + +@interface CocoaController : NSObject { + CocoaMenulet* menulet; + MainController* main; +} + +@end diff --git a/Slimber/Cocoa/CocoaController.mm b/Slimber/Cocoa/CocoaController.mm new file mode 100644 index 0000000..437d85a --- /dev/null +++ b/Slimber/Cocoa/CocoaController.mm @@ -0,0 +1,19 @@ +#include "Slimber/Cocoa/CocoaController.h" + +#include "Slimber/MainController.h" +#include "Slimber/Cocoa/CocoaMenulet.h" + +@implementation CocoaController + +- (void) dealloc { + delete main; + delete menulet; + [super dealloc]; +} + +- (void) awakeFromNib { + menulet = new CocoaMenulet(); + main = new MainController(menulet); +} + +@end diff --git a/Slimber/Cocoa/CocoaMenulet.h b/Slimber/Cocoa/CocoaMenulet.h new file mode 100644 index 0000000..913731f --- /dev/null +++ b/Slimber/Cocoa/CocoaMenulet.h @@ -0,0 +1,26 @@ +#pragma once + +#include <Cocoa/Cocoa.h> + +#include "Slimber/Menulet.h" +#include "Slimber/Cocoa/CocoaAction.h" + +class CocoaMenulet : public Menulet { + public: + CocoaMenulet(); + ~CocoaMenulet(); + + private: + virtual void clear(); + virtual void addItem(const Swift::String& name, const Swift::String& icon); + virtual void addSeparator(); + void setIcon(const Swift::String& icon); + virtual void addAboutItem(); + virtual void addRestartItem(); + virtual void addExitItem(); + + private: + NSStatusItem* statusItem; + NSMenu* menu; + CocoaAction* restartAction; +}; diff --git a/Slimber/Cocoa/CocoaMenulet.mm b/Slimber/Cocoa/CocoaMenulet.mm new file mode 100644 index 0000000..72ab000 --- /dev/null +++ b/Slimber/Cocoa/CocoaMenulet.mm @@ -0,0 +1,79 @@ +#include "Slimber/Cocoa/CocoaMenulet.h" + +#include <boost/function.hpp> + +using namespace Swift; + +CocoaMenulet::CocoaMenulet() { + restartAction = [[CocoaAction alloc] initWithFunction: + new boost::function<void()>(boost::ref(onRestartClicked))]; + menu = [[NSMenu alloc] init]; + + statusItem = [[[NSStatusBar systemStatusBar] + statusItemWithLength: NSVariableStatusItemLength] retain]; + [statusItem setHighlightMode: YES]; + [statusItem setEnabled: YES]; + [statusItem setToolTip: @"Slimber"]; + [statusItem setMenu: menu]; +} + +CocoaMenulet::~CocoaMenulet() { + [statusItem release]; + [menu release]; + [restartAction release]; +} + +void CocoaMenulet::setIcon(const String& icon) { + NSString* path = [[NSBundle mainBundle] pathForResource: + [NSString stringWithUTF8String: icon.getUTF8Data()] ofType:@"png"]; + NSImage* image = [[NSImage alloc] initWithContentsOfFile: path]; + [statusItem setImage: image]; + [image release]; +} + +void CocoaMenulet::clear() { + while ([menu numberOfItems] > 0) { + [menu removeItemAtIndex: 0]; + } +} + +void CocoaMenulet::addItem(const Swift::String& name, const String& icon) { + NSMenuItem* item = [[NSMenuItem alloc] initWithTitle: + [NSString stringWithUTF8String: name.getUTF8Data()] + action: NULL keyEquivalent: @""]; + if (!icon.isEmpty()) { + NSString* path = [[NSBundle mainBundle] pathForResource: + [NSString stringWithUTF8String: icon.getUTF8Data()] ofType:@"png"]; + NSImage* image = [[NSImage alloc] initWithContentsOfFile: path]; + [item setImage: [[NSImage alloc] initWithContentsOfFile: path]]; + [image release]; + } + [menu addItem: item]; + [item release]; +} + +void CocoaMenulet::addAboutItem() { + NSMenuItem* item = [[NSMenuItem alloc] initWithTitle: @"About Slimber" action: @selector(orderFrontStandardAboutPanel:) keyEquivalent: @""]; + [item setTarget: [NSApplication sharedApplication]]; + [menu addItem: item]; + [item release]; +} + +void CocoaMenulet::addRestartItem() { + NSMenuItem* item = [[NSMenuItem alloc] initWithTitle: + @"Restart" action: @selector(doAction:) keyEquivalent: @""]; + [item setTarget: restartAction]; + [menu addItem: item]; + [item release]; +} + +void CocoaMenulet::addExitItem() { + NSMenuItem* item = [[NSMenuItem alloc] initWithTitle: @"Exit" action: @selector(terminate:) keyEquivalent: @""]; + [item setTarget: [NSApplication sharedApplication]]; + [menu addItem: item]; + [item release]; +} + +void CocoaMenulet::addSeparator() { + [menu addItem: [NSMenuItem separatorItem]]; +} diff --git a/Slimber/Cocoa/MainMenu.xib b/Slimber/Cocoa/MainMenu.xib new file mode 100644 index 0000000..bed7223 --- /dev/null +++ b/Slimber/Cocoa/MainMenu.xib @@ -0,0 +1,498 @@ +<?xml version="1.0" encoding="UTF-8"?> +<archive type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="7.03"> + <data> + <int key="IBDocument.SystemTarget">1050</int> + <string key="IBDocument.SystemVersion">9J61</string> + <string key="IBDocument.InterfaceBuilderVersion">677</string> + <string key="IBDocument.AppKitVersion">949.46</string> + <string key="IBDocument.HIToolboxVersion">353.00</string> + <object class="NSMutableArray" key="IBDocument.EditedObjectIDs"> + <bool key="EncodedWithXMLCoder">YES</bool> + <integer value="136"/> + </object> + <object class="NSArray" key="IBDocument.PluginDependencies"> + <bool key="EncodedWithXMLCoder">YES</bool> + <string>com.apple.InterfaceBuilderKit</string> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + </object> + <object class="NSMutableDictionary" key="IBDocument.Metadata"> + <bool key="EncodedWithXMLCoder">YES</bool> + <object class="NSArray" key="dict.sortedKeys"> + <bool key="EncodedWithXMLCoder">YES</bool> + </object> + <object class="NSMutableArray" key="dict.values"> + <bool key="EncodedWithXMLCoder">YES</bool> + </object> + </object> + <object class="NSMutableArray" key="IBDocument.RootObjects" id="1048"> + <bool key="EncodedWithXMLCoder">YES</bool> + <object class="NSCustomObject" id="1021"> + <string key="NSClassName">NSApplication</string> + </object> + <object class="NSCustomObject" id="1014"> + <string key="NSClassName">FirstResponder</string> + </object> + <object class="NSCustomObject" id="1050"> + <string key="NSClassName">NSApplication</string> + </object> + <object class="NSMenu" id="649796088"> + <string key="NSTitle">AMainMenu</string> + <object class="NSMutableArray" key="NSMenuItems"> + <bool key="EncodedWithXMLCoder">YES</bool> + <object class="NSMenuItem" id="694149608"> + <reference key="NSMenu" ref="649796088"/> + <string key="NSTitle">Slimber</string> + <string key="NSKeyEquiv"/> + <int key="NSKeyEquivModMask">1048576</int> + <int key="NSMnemonicLoc">2147483647</int> + <object class="NSCustomResource" key="NSOnImage" id="35465992"> + <string key="NSClassName">NSImage</string> + <string key="NSResourceName">NSMenuCheckmark</string> + </object> + <object class="NSCustomResource" key="NSMixedImage" id="502551668"> + <string key="NSClassName">NSImage</string> + <string key="NSResourceName">NSMenuMixedState</string> + </object> + <string key="NSAction">submenuAction:</string> + <object class="NSMenu" key="NSSubmenu" id="110575045"> + <string key="NSTitle">Slimber</string> + <object class="NSMutableArray" key="NSMenuItems"> + <bool key="EncodedWithXMLCoder">YES</bool> + <object class="NSMenuItem" id="238522557"> + <reference key="NSMenu" ref="110575045"/> + <string key="NSTitle">About Slimber</string> + <string key="NSKeyEquiv"/> + <int key="NSMnemonicLoc">2147483647</int> + <reference key="NSOnImage" ref="35465992"/> + <reference key="NSMixedImage" ref="502551668"/> + </object> + <object class="NSMenuItem" id="304266470"> + <reference key="NSMenu" ref="110575045"/> + <bool key="NSIsDisabled">YES</bool> + <bool key="NSIsSeparator">YES</bool> + <string key="NSTitle"/> + <string key="NSKeyEquiv"/> + <int key="NSKeyEquivModMask">1048576</int> + <int key="NSMnemonicLoc">2147483647</int> + <reference key="NSOnImage" ref="35465992"/> + <reference key="NSMixedImage" ref="502551668"/> + </object> + <object class="NSMenuItem" id="609285721"> + <reference key="NSMenu" ref="110575045"/> + <string type="base64-UTF8" key="NSTitle">UHJlZmVyZW5jZXPigKY</string> + <string key="NSKeyEquiv">,</string> + <int key="NSKeyEquivModMask">1048576</int> + <int key="NSMnemonicLoc">2147483647</int> + <reference key="NSOnImage" ref="35465992"/> + <reference key="NSMixedImage" ref="502551668"/> + </object> + <object class="NSMenuItem" id="481834944"> + <reference key="NSMenu" ref="110575045"/> + <bool key="NSIsDisabled">YES</bool> + <bool key="NSIsSeparator">YES</bool> + <string key="NSTitle"/> + <string key="NSKeyEquiv"/> + <int key="NSKeyEquivModMask">1048576</int> + <int key="NSMnemonicLoc">2147483647</int> + <reference key="NSOnImage" ref="35465992"/> + <reference key="NSMixedImage" ref="502551668"/> + </object> + <object class="NSMenuItem" id="1046388886"> + <reference key="NSMenu" ref="110575045"/> + <string key="NSTitle">Services</string> + <string key="NSKeyEquiv"/> + <int key="NSKeyEquivModMask">1048576</int> + <int key="NSMnemonicLoc">2147483647</int> + <reference key="NSOnImage" ref="35465992"/> + <reference key="NSMixedImage" ref="502551668"/> + <string key="NSAction">submenuAction:</string> + <object class="NSMenu" key="NSSubmenu" id="752062318"> + <string key="NSTitle">Services</string> + <object class="NSMutableArray" key="NSMenuItems"> + <bool key="EncodedWithXMLCoder">YES</bool> + </object> + <string key="NSName">_NSServicesMenu</string> + </object> + </object> + <object class="NSMenuItem" id="646227648"> + <reference key="NSMenu" ref="110575045"/> + <bool key="NSIsDisabled">YES</bool> + <bool key="NSIsSeparator">YES</bool> + <string key="NSTitle"/> + <string key="NSKeyEquiv"/> + <int key="NSKeyEquivModMask">1048576</int> + <int key="NSMnemonicLoc">2147483647</int> + <reference key="NSOnImage" ref="35465992"/> + <reference key="NSMixedImage" ref="502551668"/> + </object> + <object class="NSMenuItem" id="755159360"> + <reference key="NSMenu" ref="110575045"/> + <string key="NSTitle">Hide Slimber</string> + <string key="NSKeyEquiv">h</string> + <int key="NSKeyEquivModMask">1048576</int> + <int key="NSMnemonicLoc">2147483647</int> + <reference key="NSOnImage" ref="35465992"/> + <reference key="NSMixedImage" ref="502551668"/> + </object> + <object class="NSMenuItem" id="342932134"> + <reference key="NSMenu" ref="110575045"/> + <string key="NSTitle">Hide Others</string> + <string key="NSKeyEquiv">h</string> + <int key="NSKeyEquivModMask">1572864</int> + <int key="NSMnemonicLoc">2147483647</int> + <reference key="NSOnImage" ref="35465992"/> + <reference key="NSMixedImage" ref="502551668"/> + </object> + <object class="NSMenuItem" id="908899353"> + <reference key="NSMenu" ref="110575045"/> + <string key="NSTitle">Show All</string> + <string key="NSKeyEquiv"/> + <int key="NSKeyEquivModMask">1048576</int> + <int key="NSMnemonicLoc">2147483647</int> + <reference key="NSOnImage" ref="35465992"/> + <reference key="NSMixedImage" ref="502551668"/> + </object> + <object class="NSMenuItem" id="1056857174"> + <reference key="NSMenu" ref="110575045"/> + <bool key="NSIsDisabled">YES</bool> + <bool key="NSIsSeparator">YES</bool> + <string key="NSTitle"/> + <string key="NSKeyEquiv"/> + <int key="NSKeyEquivModMask">1048576</int> + <int key="NSMnemonicLoc">2147483647</int> + <reference key="NSOnImage" ref="35465992"/> + <reference key="NSMixedImage" ref="502551668"/> + </object> + <object class="NSMenuItem" id="632727374"> + <reference key="NSMenu" ref="110575045"/> + <string key="NSTitle">Quit Slimber</string> + <string key="NSKeyEquiv">q</string> + <int key="NSKeyEquivModMask">1048576</int> + <int key="NSMnemonicLoc">2147483647</int> + <reference key="NSOnImage" ref="35465992"/> + <reference key="NSMixedImage" ref="502551668"/> + </object> + </object> + <string key="NSName">_NSAppleMenu</string> + </object> + </object> + </object> + <string key="NSName">_NSMainMenu</string> + </object> + <object class="NSCustomObject" id="16040424"> + <string key="NSClassName">CocoaController</string> + </object> + </object> + <object class="IBObjectContainer" key="IBDocument.Objects"> + <object class="NSMutableArray" key="connectionRecords"> + <bool key="EncodedWithXMLCoder">YES</bool> + <object class="IBConnectionRecord"> + <object class="IBActionConnection" key="connection"> + <string key="label">orderFrontStandardAboutPanel:</string> + <reference key="source" ref="1021"/> + <reference key="destination" ref="238522557"/> + </object> + <int key="connectionID">142</int> + </object> + <object class="IBConnectionRecord"> + <object class="IBActionConnection" key="connection"> + <string key="label">hide:</string> + <reference key="source" ref="1014"/> + <reference key="destination" ref="755159360"/> + </object> + <int key="connectionID">367</int> + </object> + <object class="IBConnectionRecord"> + <object class="IBActionConnection" key="connection"> + <string key="label">hideOtherApplications:</string> + <reference key="source" ref="1014"/> + <reference key="destination" ref="342932134"/> + </object> + <int key="connectionID">368</int> + </object> + <object class="IBConnectionRecord"> + <object class="IBActionConnection" key="connection"> + <string key="label">unhideAllApplications:</string> + <reference key="source" ref="1014"/> + <reference key="destination" ref="908899353"/> + </object> + <int key="connectionID">370</int> + </object> + <object class="IBConnectionRecord"> + <object class="IBActionConnection" key="connection"> + <string key="label">terminate:</string> + <reference key="source" ref="1050"/> + <reference key="destination" ref="632727374"/> + </object> + <int key="connectionID">449</int> + </object> + </object> + <object class="IBMutableOrderedSet" key="objectRecords"> + <object class="NSArray" key="orderedObjects"> + <bool key="EncodedWithXMLCoder">YES</bool> + <object class="IBObjectRecord"> + <int key="objectID">0</int> + <object class="NSArray" key="object" id="1049"> + <bool key="EncodedWithXMLCoder">YES</bool> + </object> + <reference key="children" ref="1048"/> + <nil key="parent"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">-2</int> + <reference key="object" ref="1021"/> + <reference key="parent" ref="1049"/> + <string type="base64-UTF8" key="objectName">RmlsZSdzIE93bmVyA</string> + </object> + <object class="IBObjectRecord"> + <int key="objectID">-1</int> + <reference key="object" ref="1014"/> + <reference key="parent" ref="1049"/> + <string key="objectName">First Responder</string> + </object> + <object class="IBObjectRecord"> + <int key="objectID">-3</int> + <reference key="object" ref="1050"/> + <reference key="parent" ref="1049"/> + <string key="objectName">Application</string> + </object> + <object class="IBObjectRecord"> + <int key="objectID">29</int> + <reference key="object" ref="649796088"/> + <object class="NSMutableArray" key="children"> + <bool key="EncodedWithXMLCoder">YES</bool> + <reference ref="694149608"/> + </object> + <reference key="parent" ref="1049"/> + <string key="objectName">MainMenu</string> + </object> + <object class="IBObjectRecord"> + <int key="objectID">458</int> + <reference key="object" ref="16040424"/> + <reference key="parent" ref="1049"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">56</int> + <reference key="object" ref="694149608"/> + <object class="NSMutableArray" key="children"> + <bool key="EncodedWithXMLCoder">YES</bool> + <reference ref="110575045"/> + </object> + <reference key="parent" ref="649796088"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">57</int> + <reference key="object" ref="110575045"/> + <object class="NSMutableArray" key="children"> + <bool key="EncodedWithXMLCoder">YES</bool> + <reference ref="342932134"/> + <reference ref="1056857174"/> + <reference ref="1046388886"/> + <reference ref="304266470"/> + <reference ref="481834944"/> + <reference ref="609285721"/> + <reference ref="646227648"/> + <reference ref="632727374"/> + <reference ref="908899353"/> + <reference ref="755159360"/> + <reference ref="238522557"/> + </object> + <reference key="parent" ref="694149608"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">145</int> + <reference key="object" ref="342932134"/> + <reference key="parent" ref="110575045"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">149</int> + <reference key="object" ref="1056857174"/> + <reference key="parent" ref="110575045"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">131</int> + <reference key="object" ref="1046388886"/> + <object class="NSMutableArray" key="children"> + <bool key="EncodedWithXMLCoder">YES</bool> + <reference ref="752062318"/> + </object> + <reference key="parent" ref="110575045"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">130</int> + <reference key="object" ref="752062318"/> + <reference key="parent" ref="1046388886"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">236</int> + <reference key="object" ref="304266470"/> + <reference key="parent" ref="110575045"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">143</int> + <reference key="object" ref="481834944"/> + <reference key="parent" ref="110575045"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">129</int> + <reference key="object" ref="609285721"/> + <reference key="parent" ref="110575045"/> + <string key="objectName">121</string> + </object> + <object class="IBObjectRecord"> + <int key="objectID">144</int> + <reference key="object" ref="646227648"/> + <reference key="parent" ref="110575045"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">136</int> + <reference key="object" ref="632727374"/> + <reference key="parent" ref="110575045"/> + <string key="objectName">1111</string> + </object> + <object class="IBObjectRecord"> + <int key="objectID">150</int> + <reference key="object" ref="908899353"/> + <reference key="parent" ref="110575045"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">134</int> + <reference key="object" ref="755159360"/> + <reference key="parent" ref="110575045"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">58</int> + <reference key="object" ref="238522557"/> + <reference key="parent" ref="110575045"/> + </object> + </object> + </object> + <object class="NSMutableDictionary" key="flattenedProperties"> + <bool key="EncodedWithXMLCoder">YES</bool> + <object class="NSMutableArray" key="dict.sortedKeys"> + <bool key="EncodedWithXMLCoder">YES</bool> + <string>-1.IBPluginDependency</string> + <string>-2.IBPluginDependency</string> + <string>-3.IBPluginDependency</string> + <string>129.IBPluginDependency</string> + <string>129.ImportedFromIB2</string> + <string>130.IBPluginDependency</string> + <string>130.ImportedFromIB2</string> + <string>130.editorWindowContentRectSynchronizationRect</string> + <string>131.IBPluginDependency</string> + <string>131.ImportedFromIB2</string> + <string>134.IBPluginDependency</string> + <string>134.ImportedFromIB2</string> + <string>136.IBPluginDependency</string> + <string>136.ImportedFromIB2</string> + <string>143.IBPluginDependency</string> + <string>143.ImportedFromIB2</string> + <string>144.IBPluginDependency</string> + <string>144.ImportedFromIB2</string> + <string>145.IBPluginDependency</string> + <string>145.ImportedFromIB2</string> + <string>149.IBPluginDependency</string> + <string>149.ImportedFromIB2</string> + <string>150.IBPluginDependency</string> + <string>150.ImportedFromIB2</string> + <string>236.IBPluginDependency</string> + <string>236.ImportedFromIB2</string> + <string>29.IBEditorWindowLastContentRect</string> + <string>29.IBPluginDependency</string> + <string>29.ImportedFromIB2</string> + <string>29.WindowOrigin</string> + <string>29.editorWindowContentRectSynchronizationRect</string> + <string>458.IBPluginDependency</string> + <string>56.IBPluginDependency</string> + <string>56.ImportedFromIB2</string> + <string>57.IBEditorWindowLastContentRect</string> + <string>57.IBPluginDependency</string> + <string>57.ImportedFromIB2</string> + <string>57.editorWindowContentRectSynchronizationRect</string> + <string>58.IBPluginDependency</string> + <string>58.ImportedFromIB2</string> + </object> + <object class="NSMutableArray" key="dict.values"> + <bool key="EncodedWithXMLCoder">YES</bool> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + <string>com.apple.InterfaceBuilderKit</string> + <string>com.apple.InterfaceBuilderKit</string> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + <integer value="1" id="9"/> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + <reference ref="9"/> + <string>{{436, 809}, {64, 6}}</string> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + <reference ref="9"/> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + <reference ref="9"/> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + <reference ref="9"/> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + <reference ref="9"/> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + <reference ref="9"/> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + <reference ref="9"/> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + <reference ref="9"/> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + <reference ref="9"/> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + <reference ref="9"/> + <string>{{306, 359}, {97, 20}}</string> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + <reference ref="9"/> + <string>{74, 862}</string> + <string>{{6, 978}, {478, 20}}</string> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + <reference ref="9"/> + <string>{{318, 176}, {190, 183}}</string> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + <reference ref="9"/> + <string>{{23, 794}, {245, 183}}</string> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + <reference ref="9"/> + </object> + </object> + <object class="NSMutableDictionary" key="unlocalizedProperties"> + <bool key="EncodedWithXMLCoder">YES</bool> + <object class="NSArray" key="dict.sortedKeys"> + <bool key="EncodedWithXMLCoder">YES</bool> + </object> + <object class="NSMutableArray" key="dict.values"> + <bool key="EncodedWithXMLCoder">YES</bool> + </object> + </object> + <nil key="activeLocalization"/> + <object class="NSMutableDictionary" key="localizations"> + <bool key="EncodedWithXMLCoder">YES</bool> + <object class="NSArray" key="dict.sortedKeys"> + <bool key="EncodedWithXMLCoder">YES</bool> + </object> + <object class="NSMutableArray" key="dict.values"> + <bool key="EncodedWithXMLCoder">YES</bool> + </object> + </object> + <nil key="sourceID"/> + <int key="maxID">463</int> + </object> + <object class="IBClassDescriber" key="IBDocument.Classes"> + <object class="NSMutableArray" key="referencedPartialClassDescriptionsV3.1+"> + <bool key="EncodedWithXMLCoder">YES</bool> + <object class="IBPartialClassDescription"> + <string key="className">CocoaController</string> + <string key="superclassName">NSObject</string> + <object class="IBClassDescriptionSource" key="sourceIdentifier"> + <string key="majorKey">IBDocumentRelativeSource</string> + <string key="minorKey">CocoaController.h</string> + </object> + </object> + </object> + </object> + <int key="IBDocument.localizationMode">0</int> + <string key="IBDocument.LastKnownRelativeProjectPath">../Slimber.xcodeproj</string> + <int key="IBDocument.defaultPropertyAccessControl">3</int> + </data> +</archive> diff --git a/Slimber/Cocoa/SConscript b/Slimber/Cocoa/SConscript new file mode 100644 index 0000000..a354bc8 --- /dev/null +++ b/Slimber/Cocoa/SConscript @@ -0,0 +1,32 @@ +Import("env") + +myenv = env.Clone() +myenv.MergeFlags(env["SLIMBER_FLAGS"]) +myenv.MergeFlags(env["SWIFTEN_FLAGS"]) +myenv.MergeFlags(env["LIBIDN_FLAGS"]) +myenv.MergeFlags(env["BOOST_FLAGS"]) +myenv.MergeFlags(env.get("LIBXML_FLAGS", "")) +myenv.MergeFlags(env.get("EXPAT_FLAGS", "")) +myenv.Append(FRAMEWORKS = "Cocoa") + +myenv.Program("Slimber", [ + "main.mm", + "CocoaController.mm", + "CocoaMenulet.mm", + "CocoaAction.mm" + ]) + +myenv.Nib("MainMenu") + +myenv.AppBundle("Slimber", resources = [ + "MainMenu.nib", + "../Resources/Slimber.icns", + "../Resources/Credits.html", + "../Resources/Online.png", + "../Resources/Offline.png", + "../Resources/UsersOnline.png", + "../Resources/UsersOffline.png" + ], info = { + "NSMainNibFile" : "MainMenu", + "LSUIElement" : "1", + }) diff --git a/Slimber/Cocoa/main.mm b/Slimber/Cocoa/main.mm new file mode 100644 index 0000000..e267477 --- /dev/null +++ b/Slimber/Cocoa/main.mm @@ -0,0 +1,8 @@ +#include <Cocoa/Cocoa.h> + +#include "Swiften/EventLoop/Cocoa/CocoaEventLoop.h" + +int main(int argc, char *argv[]) { + Swift::CocoaEventLoop eventLoop; + return NSApplicationMain(argc, const_cast<const char **>(argv)); +} diff --git a/Slimber/FileVCardCollection.cpp b/Slimber/FileVCardCollection.cpp new file mode 100644 index 0000000..eb7d9cc --- /dev/null +++ b/Slimber/FileVCardCollection.cpp @@ -0,0 +1,37 @@ +#include "Slimber/FileVCardCollection.h" + +#include <boost/filesystem/fstream.hpp> + +#include "Swiften/Base/ByteArray.h" +#include "Swiften/Elements/VCard.h" +#include "Swiften/Serializer/PayloadSerializers/VCardSerializer.h" +#include "Swiften/Parser/PayloadParsers/UnitTest/PayloadParserTester.h" +#include "Swiften/Parser/PayloadParsers/VCardParser.h" + +namespace Swift { + +FileVCardCollection::FileVCardCollection(boost::filesystem::path dir) : vcardsPath(dir) { +} + +boost::shared_ptr<VCard> FileVCardCollection::getOwnVCard() const { + if (boost::filesystem::exists(vcardsPath / std::string("vcard.xml"))) { + ByteArray data; + data.readFromFile(boost::filesystem::path(vcardsPath / std::string("vcard.xml")).string()); + + VCardParser parser; + PayloadParserTester tester(&parser); + tester.parse(String(data.getData(), data.getSize())); + return boost::dynamic_pointer_cast<VCard>(parser.getPayload()); + } + else { + return boost::shared_ptr<VCard>(new VCard()); + } +} + +void FileVCardCollection::setOwnVCard(boost::shared_ptr<VCard> v) { + boost::filesystem::ofstream file(vcardsPath / std::string("vcard.xml")); + file << VCardSerializer().serializePayload(v); + file.close(); +} + +} diff --git a/Slimber/FileVCardCollection.h b/Slimber/FileVCardCollection.h new file mode 100644 index 0000000..dde47df --- /dev/null +++ b/Slimber/FileVCardCollection.h @@ -0,0 +1,19 @@ +#pragma once + +#include <boost/shared_ptr.hpp> +#include <boost/filesystem.hpp> + +#include "Slimber/VCardCollection.h" + +namespace Swift { + class FileVCardCollection : public VCardCollection { + public: + FileVCardCollection(boost::filesystem::path dir); + + boost::shared_ptr<VCard> getOwnVCard() const; + void setOwnVCard(boost::shared_ptr<VCard> vcard); + + private: + boost::filesystem::path vcardsPath; + }; +} diff --git a/Slimber/LinkLocalPresenceManager.cpp b/Slimber/LinkLocalPresenceManager.cpp new file mode 100644 index 0000000..d5834c7 --- /dev/null +++ b/Slimber/LinkLocalPresenceManager.cpp @@ -0,0 +1,103 @@ +#include "Slimber/LinkLocalPresenceManager.h" + +#include <boost/bind.hpp> + +#include "Swiften/Base/foreach.h" +#include "Swiften/LinkLocal/LinkLocalServiceBrowser.h" +#include "Swiften/Elements/RosterPayload.h" +#include "Swiften/Elements/Presence.h" + +namespace Swift { + +LinkLocalPresenceManager::LinkLocalPresenceManager(LinkLocalServiceBrowser* browser) : browser(browser) { + browser->onServiceAdded.connect( + boost::bind(&LinkLocalPresenceManager::handleServiceAdded, this, _1)); + browser->onServiceChanged.connect( + boost::bind(&LinkLocalPresenceManager::handleServiceChanged, this, _1)); + browser->onServiceRemoved.connect( + boost::bind(&LinkLocalPresenceManager::handleServiceRemoved, this, _1)); +} + +boost::optional<LinkLocalService> LinkLocalPresenceManager::getServiceForJID(const JID& j) const { + foreach(const LinkLocalService& service, browser->getServices()) { + if (service.getJID() == j) { + return service; + } + } + return boost::optional<LinkLocalService>(); +} + +void LinkLocalPresenceManager::handleServiceAdded(const LinkLocalService& service) { + boost::shared_ptr<RosterPayload> roster(new RosterPayload()); + roster->addItem(getRosterItem(service)); + onRosterChanged(roster); + onPresenceChanged(getPresence(service)); +} + +void LinkLocalPresenceManager::handleServiceChanged(const LinkLocalService& service) { + onPresenceChanged(getPresence(service)); +} + +void LinkLocalPresenceManager::handleServiceRemoved(const LinkLocalService& service) { + boost::shared_ptr<RosterPayload> roster(new RosterPayload()); + roster->addItem(RosterItemPayload(service.getJID(), "", RosterItemPayload::Remove)); + onRosterChanged(roster); +} + +boost::shared_ptr<RosterPayload> LinkLocalPresenceManager::getRoster() const { + boost::shared_ptr<RosterPayload> roster(new RosterPayload()); + foreach(const LinkLocalService& service, browser->getServices()) { + roster->addItem(getRosterItem(service)); + } + return roster; +} + +std::vector<boost::shared_ptr<Presence> > LinkLocalPresenceManager::getAllPresence() const { + std::vector<boost::shared_ptr<Presence> > result; + foreach(const LinkLocalService& service, browser->getServices()) { + result.push_back(getPresence(service)); + } + return result; +} + +RosterItemPayload LinkLocalPresenceManager::getRosterItem(const LinkLocalService& service) const { + return RosterItemPayload(service.getJID(), getRosterName(service), RosterItemPayload::Both); +} + +String LinkLocalPresenceManager::getRosterName(const LinkLocalService& service) const { + LinkLocalServiceInfo info = service.getInfo(); + if (!info.getNick().isEmpty()) { + return info.getNick(); + } + else if (!info.getFirstName().isEmpty()) { + String result = info.getFirstName(); + if (!info.getLastName().isEmpty()) { + result += " " + info.getLastName(); + } + return result; + } + else if (!info.getLastName().isEmpty()) { + return info.getLastName(); + } + return ""; +} + +boost::shared_ptr<Presence> LinkLocalPresenceManager::getPresence(const LinkLocalService& service) const { + boost::shared_ptr<Presence> presence(new Presence()); + presence->setFrom(service.getJID()); + switch (service.getInfo().getStatus()) { + case LinkLocalServiceInfo::Available: + presence->setShow(StatusShow::Online); + break; + case LinkLocalServiceInfo::Away: + presence->setShow(StatusShow::Away); + break; + case LinkLocalServiceInfo::DND: + presence->setShow(StatusShow::DND); + break; + } + presence->setStatus(service.getInfo().getMessage()); + return presence; +} + +} diff --git a/Slimber/LinkLocalPresenceManager.h b/Slimber/LinkLocalPresenceManager.h new file mode 100644 index 0000000..c8f77e9 --- /dev/null +++ b/Slimber/LinkLocalPresenceManager.h @@ -0,0 +1,40 @@ +#pragma once + +#include <boost/shared_ptr.hpp> +#include <boost/signal.hpp> + +#include "Swiften/Elements/RosterItemPayload.h" +#include "Swiften/Base/String.h" +#include "Swiften/JID/JID.h" + +namespace Swift { + class LinkLocalService; + class LinkLocalServiceBrowser; + class RosterPayload; + class Presence; + + class LinkLocalPresenceManager : public boost::bsignals::trackable { + public: + LinkLocalPresenceManager(LinkLocalServiceBrowser*); + + boost::shared_ptr<RosterPayload> getRoster() const; + std::vector<boost::shared_ptr<Presence> > getAllPresence() const; + + boost::optional<LinkLocalService> getServiceForJID(const JID&) const; + + boost::signal<void (boost::shared_ptr<RosterPayload>)> onRosterChanged; + boost::signal<void (boost::shared_ptr<Presence>)> onPresenceChanged; + + private: + void handleServiceAdded(const LinkLocalService&); + void handleServiceChanged(const LinkLocalService&); + void handleServiceRemoved(const LinkLocalService&); + + RosterItemPayload getRosterItem(const LinkLocalService& service) const; + String getRosterName(const LinkLocalService& service) const; + boost::shared_ptr<Presence> getPresence(const LinkLocalService& service) const; + + private: + LinkLocalServiceBrowser* browser; + }; +} diff --git a/Slimber/MainController.cpp b/Slimber/MainController.cpp new file mode 100644 index 0000000..c8c2ad1 --- /dev/null +++ b/Slimber/MainController.cpp @@ -0,0 +1,123 @@ +#include "Slimber/MainController.h" + +#include <boost/bind.hpp> +#include <boost/lexical_cast.hpp> +#include <iostream> + +#include "Swiften/Base/foreach.h" +#include "Swiften/Application/Platform/PlatformApplication.h" +#include "Swiften/LinkLocal/LinkLocalService.h" +#include "Swiften/LinkLocal/LinkLocalServiceBrowser.h" +#include "Swiften/LinkLocal/DNSSD/PlatformDNSSDQuerierFactory.h" +#include "Slimber/Server.h" +#include "Slimber/FileVCardCollection.h" +#include "Slimber/MenuletController.h" +#include "Slimber/Menulet.h" + +using namespace Swift; + +MainController::MainController(Menulet* menulet) : menulet(menulet) { + menuletController = new MenuletController(menulet); + menuletController->onRestartRequested.connect(boost::bind( + &MainController::handleRestartRequested, this)); + + dnsSDQuerier = PlatformDNSSDQuerierFactory().createQuerier(); + if (!dnsSDQuerier) { + // TODO + assert(false); + } + + linkLocalServiceBrowser = new LinkLocalServiceBrowser(dnsSDQuerier); + linkLocalServiceBrowser->onServiceAdded.connect( + boost::bind(&MainController::handleServicesChanged, this)); + linkLocalServiceBrowser->onServiceRemoved.connect( + boost::bind(&MainController::handleServicesChanged, this)); + linkLocalServiceBrowser->onServiceChanged.connect( + boost::bind(&MainController::handleServicesChanged, this)); + + vCardCollection = new FileVCardCollection( + PlatformApplication("Slimber").getSettingsDir()); + + server = new Server(5222, 5562, linkLocalServiceBrowser, vCardCollection); + server->onStopped.connect( + boost::bind(&MainController::handleServerStopped, this, _1)); + server->onSelfConnected.connect( + boost::bind(&MainController::handleSelfConnected, this, _1)); + + start(); +} + +MainController::~MainController() { + delete menuletController; + delete server; + delete vCardCollection; + linkLocalServiceBrowser->stop(); + delete linkLocalServiceBrowser; + dnsSDQuerier->stop(); +} + +void MainController::start() { + dnsSDQuerier->start(); + linkLocalServiceBrowser->start(); + + handleSelfConnected(false); + handleServicesChanged(); + + server->start(); +} + +void MainController::stop() { + server->stop(); + linkLocalServiceBrowser->stop(); + dnsSDQuerier->stop(); +} + +void MainController::handleSelfConnected(bool b) { + if (b) { + menuletController->setXMPPStatus("You are logged in", MenuletController::Online); + } + else { + menuletController->setXMPPStatus("You are not logged in", MenuletController::Offline); + } +} + +void MainController::handleServicesChanged() { + std::vector<String> names; + foreach(const LinkLocalService& service, linkLocalServiceBrowser->getServices()) { + String description = service.getDescription(); + if (description != service.getName()) { + description += " (" + service.getName() + ")"; + } + names.push_back(description); + } + menuletController->setUserNames(names); +} + +void MainController::handleServerStopped(boost::optional<ServerError> error) { + if (error) { + String message; + switch (error->getType()) { + case ServerError::C2SPortConflict: + message = String("Error: Port ") + boost::lexical_cast<std::string>(server->getClientToServerPort()) + String(" in use"); + break; + case ServerError::C2SError: + message = String("Local connection server error"); + break; + case ServerError::LinkLocalPortConflict: + message = String("Error: Port ") + boost::lexical_cast<std::string>(server->getLinkLocalPort()) + String(" in use"); + break; + case ServerError::LinkLocalError: + message = String("External connection server error"); + break; + } + menuletController->setXMPPStatus(message, MenuletController::Offline); + } + else { + menuletController->setXMPPStatus("XMPP Server Not Running", MenuletController::Offline); + } +} + +void MainController::handleRestartRequested() { + stop(); + start(); +} diff --git a/Slimber/MainController.h b/Slimber/MainController.h new file mode 100644 index 0000000..2c74e4c --- /dev/null +++ b/Slimber/MainController.h @@ -0,0 +1,39 @@ +#pragma once + +#include <boost/shared_ptr.hpp> +#include <boost/optional.hpp> + +#include "Slimber/ServerError.h" + +namespace Swift { + class DNSSDQuerier; + class LinkLocalServiceBrowser; + class VCardCollection; + class Server; +} + +class MenuletController; +class Menulet; + +class MainController { + public: + MainController(Menulet* menulet); + virtual ~MainController(); + + private: + void handleSelfConnected(bool b); + void handleServicesChanged(); + void handleServerStopped(boost::optional<Swift::ServerError> error); + void handleRestartRequested(); + + void start(); + void stop(); + + private: + Menulet* menulet; + boost::shared_ptr<Swift::DNSSDQuerier> dnsSDQuerier; + Swift::LinkLocalServiceBrowser* linkLocalServiceBrowser; + Swift::VCardCollection* vCardCollection; + Swift::Server* server; + MenuletController* menuletController; +}; diff --git a/Slimber/Menulet.cpp b/Slimber/Menulet.cpp new file mode 100644 index 0000000..bdadb98 --- /dev/null +++ b/Slimber/Menulet.cpp @@ -0,0 +1,4 @@ +#include "Slimber/Menulet.h" + +Menulet::~Menulet() { +} diff --git a/Slimber/Menulet.h b/Slimber/Menulet.h new file mode 100644 index 0000000..1b8ed18 --- /dev/null +++ b/Slimber/Menulet.h @@ -0,0 +1,20 @@ +#pragma once + +#include <boost/signal.hpp> + +#include "Swiften/Base/String.h" + +class Menulet { + public: + virtual ~Menulet(); + + virtual void clear() = 0; + virtual void addItem(const Swift::String& name, const Swift::String& icon = Swift::String()) = 0; + virtual void addAboutItem() = 0; + virtual void addRestartItem() = 0; + virtual void addExitItem() = 0; + virtual void addSeparator() = 0; + virtual void setIcon(const Swift::String&) = 0; + + boost::signal<void ()> onRestartClicked; +}; diff --git a/Slimber/MenuletController.cpp b/Slimber/MenuletController.cpp new file mode 100644 index 0000000..1532459 --- /dev/null +++ b/Slimber/MenuletController.cpp @@ -0,0 +1,51 @@ +#include "Slimber/MenuletController.h" + +#include "Swiften/Base/foreach.h" +#include "Swiften/Base/String.h" +#include "Slimber/Menulet.h" + +#include <iostream> + +using namespace Swift; + +MenuletController::MenuletController(Menulet* menulet) : + menulet(menulet), xmppStatus(Offline) { + menulet->onRestartClicked.connect(boost::ref(onRestartRequested)); + update(); +} + +MenuletController::~MenuletController() { +} + +void MenuletController::setXMPPStatus(const String& message, Status status) { + xmppStatus = status; + xmppStatusMessage = message; + update(); +} + +void MenuletController::setUserNames(const std::vector<String>& users) { + linkLocalUsers = users; + update(); +} + +void MenuletController::update() { + menulet->clear(); + if (linkLocalUsers.empty()) { + menulet->setIcon("UsersOffline"); + menulet->addItem("No online users"); + } + else { + menulet->setIcon("UsersOnline"); + menulet->addItem("Online users:"); + foreach(const String& user, linkLocalUsers) { + menulet->addItem(String(" ") + user); + } + } + menulet->addSeparator(); + menulet->addItem(xmppStatusMessage, (xmppStatus == Online ? "Online" : "Offline")); + menulet->addSeparator(); + menulet->addAboutItem(); + menulet->addSeparator(); + menulet->addRestartItem(); + menulet->addExitItem(); +} diff --git a/Slimber/MenuletController.h b/Slimber/MenuletController.h new file mode 100644 index 0000000..5e45038 --- /dev/null +++ b/Slimber/MenuletController.h @@ -0,0 +1,33 @@ +#pragma once + +#include <vector> +#include <boost/signal.hpp> + +#include "Swiften/Base/String.h" + +class Menulet; + +class MenuletController { + public: + enum Status { + Online, + Offline + }; + + MenuletController(Menulet*); + virtual ~MenuletController(); + + void setXMPPStatus(const Swift::String& message, Status status); + void setUserNames(const std::vector<Swift::String>&); + + boost::signal<void ()> onRestartRequested; + + private: + void update(); + + private: + Menulet* menulet; + Status xmppStatus; + Swift::String xmppStatusMessage; + std::vector<Swift::String> linkLocalUsers; +}; diff --git a/Slimber/Qt/QtMenulet.cpp b/Slimber/Qt/QtMenulet.cpp new file mode 100644 index 0000000..58c7d50 --- /dev/null +++ b/Slimber/Qt/QtMenulet.cpp @@ -0,0 +1,2 @@ +#include "Slimber/Qt/QtMenulet.h" + diff --git a/Slimber/Qt/QtMenulet.h b/Slimber/Qt/QtMenulet.h new file mode 100644 index 0000000..4ac9140 --- /dev/null +++ b/Slimber/Qt/QtMenulet.h @@ -0,0 +1,65 @@ +#pragma once + +#include <QCoreApplication> +#include <QMenu> +#include <QString> +#include <QSystemTrayIcon> +#include <QObject> +#include <QPixmap> + +#include "Slimber/Menulet.h" + +class QtMenulet : public QObject, public Menulet { + Q_OBJECT + public: + QtMenulet() { + trayIcon.setIcon(QPixmap(":/icons/UsersOffline.png")); + trayIcon.setContextMenu(&menu); + trayIcon.show(); + } + + void clear() { + menu.clear(); + } + + void addItem(const Swift::String& name, const Swift::String& icon) { + menu.addAction(getIcon(icon), QString::fromUtf8(name.getUTF8Data())); + } + + void addAboutItem() { + menu.addAction("About"); + } + + void addRestartItem() { + menu.addAction("Restart", this, SLOT(restart())); + } + + void addExitItem() { + menu.addAction("Exit", qApp, SLOT(quit())); + } + + void addSeparator() { + menu.addSeparator(); + } + + void setIcon(const Swift::String& icon) { + trayIcon.setIcon(getIcon(icon)); + } + + private: + QPixmap getIcon(const Swift::String& name) { + return QPixmap(":/icons/" + QString::fromUtf8(name.getUTF8Data()) + ".png"); + } + + private slots: + void showAboutDialog() { + } + + void restart() { + onRestartClicked(); + } + + private: + QMenu menu; + QSystemTrayIcon trayIcon; +}; diff --git a/Slimber/Qt/SConscript b/Slimber/Qt/SConscript new file mode 100644 index 0000000..b2b8320 --- /dev/null +++ b/Slimber/Qt/SConscript @@ -0,0 +1,45 @@ +import os, shutil, datetime + +Import("env") + +myenv = env.Clone() + +myenv.MergeFlags(env["SLIMBER_FLAGS"]) +myenv.MergeFlags(env["SWIFTEN_FLAGS"]) +myenv.MergeFlags(env["CPPUNIT_FLAGS"]) +myenv.MergeFlags(env["LIBIDN_FLAGS"]) +myenv.MergeFlags(env["BOOST_FLAGS"]) +myenv.MergeFlags(env.get("LIBXML_FLAGS", "")) +myenv.MergeFlags(env.get("EXPAT_FLAGS", "")) + +myenv.Tool("qt4", toolpath = ["#/BuildTools/SCons/Tools"]) +myenv.Tool("nsis", toolpath = ["#/BuildTools/SCons/Tools"]) +myenv.EnableQt4Modules(['QtCore', 'QtGui'], debug = False) + +myenv.Append(CPPPATH = ["."]) + +if env["PLATFORM"] == "win32" : + myenv.Append(LINKFLAGS = ["/SUBSYSTEM:WINDOWS"]) + myenv.Append(LIBS = "qtmain") + +sources = ["main.cpp", "QtMenulet.cpp"] + +#if env["PLATFORM"] == "win32" : +# myenv.RES("../resources/Windows/Slimber.rc") +# sources += ["../resources/Windows/Slimber.res"] + +if env["PLATFORM"] == "win32" : + slimberProgram = myenv.Program("Slimber", sources) +else : + slimberProgram = myenv.Program("slimber", sources) + +myenv.Qrc("Slimber.qrc") + +if env["PLATFORM"] == "win32" : + if "dist" in COMMAND_LINE_TARGETS or env.GetOption("clean") : + myenv.WindowsBundle("Slimber", resources = [], qtlibs = ["QtCore4", "QtGui4"]) + myenv.Append(NSIS_OPTIONS = [ + "/DmsvccRedistributableDir=\"" + env["vcredist"] + "\"", + "/DbuildDate=" + datetime.date.today().strftime("%Y%m%d") + ]) + #myenv.Nsis("../Packaging/nsis/slimber.nsi") diff --git a/Slimber/Qt/Slimber.qrc b/Slimber/Qt/Slimber.qrc new file mode 100644 index 0000000..6cc21ff --- /dev/null +++ b/Slimber/Qt/Slimber.qrc @@ -0,0 +1,9 @@ +<!DOCTYPE RCC> +<RCC version="1.0"> + <qresource> + <file alias="icons/Offline.png">../Resources/Offline.png</file> + <file alias="icons/Online.png">../Resources/Online.png</file> + <file alias="icons/UsersOffline.png">../Resources/UsersOffline.png</file> + <file alias="icons/UsersOnline.png">../Resources/UsersOnline.png</file> + </qresource> +</RCC> diff --git a/Slimber/Qt/main.cpp b/Slimber/Qt/main.cpp new file mode 100644 index 0000000..0988e7c --- /dev/null +++ b/Slimber/Qt/main.cpp @@ -0,0 +1,19 @@ +#include <QApplication> +#include <QSystemTrayIcon> +#include <QMessageBox> + +#include "QtMenulet.h" +#include "Slimber/MainController.h" + +int main(int argc, char* argv[]) { + QApplication app(argc, argv); + if (!QSystemTrayIcon::isSystemTrayAvailable()) { +QMessageBox::critical(0, QObject::tr("Systray"), QObject::tr("No system tray")); + return 1; + } + + QtMenulet menulet; + MainController controller(&menulet); + + return app.exec(); +} diff --git a/Slimber/Resources/Credits.html b/Slimber/Resources/Credits.html new file mode 100644 index 0000000..66ecb13 --- /dev/null +++ b/Slimber/Resources/Credits.html @@ -0,0 +1 @@ +<a href="http://swift.im/slimber">http://swift.im/slimber</a> diff --git a/Slimber/Resources/Icon-Single.svg b/Slimber/Resources/Icon-Single.svg new file mode 100644 index 0000000..ce1d7ff --- /dev/null +++ b/Slimber/Resources/Icon-Single.svg @@ -0,0 +1,108 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!-- Created with Inkscape (http://www.inkscape.org/) --> +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:xlink="http://www.w3.org/1999/xlink" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + width="128" + height="128" + id="svg2459" + sodipodi:version="0.32" + inkscape:version="0.46" + sodipodi:docname="Self.svg" + inkscape:output_extension="org.inkscape.output.svg.inkscape" + version="1.0" + inkscape:export-filename="/Users/remko/src/swift/Slimber/Resources/Onlien.png" + inkscape:export-xdpi="11.25" + inkscape:export-ydpi="11.25"> + <defs + id="defs2461"> + <linearGradient + id="linearGradient3217"> + <stop + style="stop-color:#000000;stop-opacity:1;" + offset="0" + id="stop3219" /> + <stop + style="stop-color:#ffffff;stop-opacity:1;" + offset="1" + id="stop3221" /> + </linearGradient> + <linearGradient + id="linearGradient3156"> + <stop + style="stop-color:#ff9b45;stop-opacity:1;" + offset="0" + id="stop3158" /> + <stop + style="stop-color:#ac4a00;stop-opacity:1" + offset="1" + id="stop3160" /> + </linearGradient> + <inkscape:perspective + sodipodi:type="inkscape:persp3d" + inkscape:vp_x="0 : 526.18109 : 1" + inkscape:vp_y="0 : 1000 : 0" + inkscape:vp_z="744.09448 : 526.18109 : 1" + inkscape:persp3d-origin="372.04724 : 350.78739 : 1" + id="perspective2390" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient3156" + id="linearGradient3162" + x1="59.865948" + y1="27.180033" + x2="68.798233" + y2="66.251015" + gradientUnits="userSpaceOnUse" /> + </defs> + <sodipodi:namedview + id="base" + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1.0" + gridtolerance="10000" + guidetolerance="10" + objecttolerance="10" + inkscape:pageopacity="0.0" + inkscape:pageshadow="2" + inkscape:zoom="4.0390625" + inkscape:cx="64" + inkscape:cy="69.358025" + inkscape:document-units="px" + inkscape:current-layer="layer1" + showgrid="false" + inkscape:window-width="886" + inkscape:window-height="713" + inkscape:window-x="59" + inkscape:window-y="31" /> + <metadata + id="metadata2464"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + </cc:Work> + </rdf:RDF> + </metadata> + <g + inkscape:label="Layer 1" + inkscape:groupmode="layer" + id="layer1"> + <path + sodipodi:type="inkscape:offset" + inkscape:radius="0" + inkscape:original="M 35.75 21.4375 C 67.770833 28.589068 61.962345 38.980958 48.59375 47.71875 C 42.117938 49.826519 34.459217 52.284088 23.875 55.28125 C 28.767848 54.681471 33.106944 54.336611 37.125 54.125 C 33.141375 56.073855 29.188794 57.883088 25.75 59.34375 C 34.172431 57.138896 40.17062 55.635095 46.34375 54.09375 C 68.404841 54.936763 70.366936 64.588264 50.03125 83.5 C 80.52166 67.640036 88.246281 56.011556 70.21875 48.46875 C 79.529352 46.508168 83.310469 46.138239 85.28125 46.3125 C 80.487749 43.302683 77.123885 41.944256 73.40625 41.6875 C 81.713537 30.753386 64.586454 23.918546 35.75 21.4375 z M 73.9375 43.25 C 74.54083 43.25 75.03125 43.724312 75.03125 44.28125 C 75.031251 44.83819 74.54083 45.28125 73.9375 45.28125 C 73.33417 45.281249 72.84375 44.838189 72.84375 44.28125 C 72.84375 43.724313 73.33417 43.25 73.9375 43.25 z " + style="fill:url(#linearGradient3162);fill-opacity:1;fill-rule:nonzero;stroke:#682b00;stroke-width:0.50000000000000000;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path2382" + d="M 35.75,21.4375 C 67.770833,28.589068 61.962345,38.980958 48.59375,47.71875 C 42.117938,49.826519 34.459217,52.284088 23.875,55.28125 C 28.767848,54.681471 33.106944,54.336611 37.125,54.125 C 33.141375,56.073855 29.188794,57.883088 25.75,59.34375 C 34.172431,57.138896 40.17062,55.635095 46.34375,54.09375 C 68.404841,54.936763 70.366936,64.588264 50.03125,83.5 C 80.52166,67.640036 88.246281,56.011556 70.21875,48.46875 C 79.529352,46.508168 83.310469,46.138239 85.28125,46.3125 C 80.487749,43.302683 77.123885,41.944256 73.40625,41.6875 C 81.713537,30.753386 64.586454,23.918546 35.75,21.4375 z M 73.9375,43.25 C 74.54083,43.25 75.03125,43.724312 75.03125,44.28125 C 75.031251,44.83819 74.54083,45.28125 73.9375,45.28125 C 73.33417,45.281249 72.84375,44.838189 72.84375,44.28125 C 72.84375,43.724313 73.33417,43.25 73.9375,43.25 z" + transform="matrix(2.0215945,0,0,1.995459,-47.005332,-40.698125)" /> + </g> +</svg> diff --git a/Slimber/Resources/Icon.svg b/Slimber/Resources/Icon.svg new file mode 100644 index 0000000..1e701a6 --- /dev/null +++ b/Slimber/Resources/Icon.svg @@ -0,0 +1,124 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!-- Created with Inkscape (http://www.inkscape.org/) --> +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:xlink="http://www.w3.org/1999/xlink" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + width="128" + height="128" + id="svg2459" + sodipodi:version="0.32" + inkscape:version="0.46" + sodipodi:docname="Icon.svg" + inkscape:output_extension="org.inkscape.output.svg.inkscape" + version="1.0" + inkscape:export-filename="/Users/remko/src/swift/Slimber/Resources/Icon-16.png" + inkscape:export-xdpi="11.25" + inkscape:export-ydpi="11.25"> + <defs + id="defs2461"> + <linearGradient + id="linearGradient3217"> + <stop + style="stop-color:#000000;stop-opacity:1;" + offset="0" + id="stop3219" /> + <stop + style="stop-color:#ffffff;stop-opacity:1;" + offset="1" + id="stop3221" /> + </linearGradient> + <linearGradient + id="linearGradient3156"> + <stop + style="stop-color:#fe9b50;stop-opacity:1;" + offset="0" + id="stop3158" /> + <stop + style="stop-color:#9f4500;stop-opacity:1;" + offset="1" + id="stop3160" /> + </linearGradient> + <inkscape:perspective + sodipodi:type="inkscape:persp3d" + inkscape:vp_x="0 : 526.18109 : 1" + inkscape:vp_y="0 : 1000 : 0" + inkscape:vp_z="744.09448 : 526.18109 : 1" + inkscape:persp3d-origin="372.04724 : 350.78739 : 1" + id="perspective2390" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient3156" + id="linearGradient3162" + x1="68.071358" + y1="68.620354" + x2="67.328606" + y2="19.351492" + gradientUnits="userSpaceOnUse" /> + </defs> + <sodipodi:namedview + id="base" + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1.0" + gridtolerance="10000" + guidetolerance="10" + objecttolerance="10" + inkscape:pageopacity="0.0" + inkscape:pageshadow="2" + inkscape:zoom="4.0390625" + inkscape:cx="64" + inkscape:cy="66.885091" + inkscape:document-units="px" + inkscape:current-layer="layer1" + showgrid="false" + inkscape:window-width="886" + inkscape:window-height="713" + inkscape:window-x="181" + inkscape:window-y="32" /> + <metadata + id="metadata2464"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + </cc:Work> + </rdf:RDF> + </metadata> + <g + inkscape:label="Layer 1" + inkscape:groupmode="layer" + id="layer1"> + <path + sodipodi:type="inkscape:offset" + inkscape:radius="0" + inkscape:original="M 35.75 21.4375 C 67.770833 28.589068 61.962345 38.980958 48.59375 47.71875 C 42.117938 49.826519 34.459217 52.284088 23.875 55.28125 C 28.767848 54.681471 33.106944 54.336611 37.125 54.125 C 33.141375 56.073855 29.188794 57.883088 25.75 59.34375 C 34.172431 57.138896 40.17062 55.635095 46.34375 54.09375 C 68.404841 54.936763 70.366936 64.588264 50.03125 83.5 C 80.52166 67.640036 88.246281 56.011556 70.21875 48.46875 C 79.529352 46.508168 83.310469 46.138239 85.28125 46.3125 C 80.487749 43.302683 77.123885 41.944256 73.40625 41.6875 C 81.713537 30.753386 64.586454 23.918546 35.75 21.4375 z M 73.9375 43.25 C 74.54083 43.25 75.03125 43.724312 75.03125 44.28125 C 75.031251 44.83819 74.54083 45.28125 73.9375 45.28125 C 73.33417 45.281249 72.84375 44.838189 72.84375 44.28125 C 72.84375 43.724313 73.33417 43.25 73.9375 43.25 z " + style="fill:#585858;fill-opacity:1;fill-rule:nonzero;stroke:#333333;stroke-width:0.59638305999999996;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;stroke-miterlimit:4;stroke-dasharray:none" + id="path3154" + d="M 35.75,21.4375 C 67.770833,28.589068 61.962345,38.980958 48.59375,47.71875 C 42.117938,49.826519 34.459217,52.284088 23.875,55.28125 C 28.767848,54.681471 33.106944,54.336611 37.125,54.125 C 33.141375,56.073855 29.188794,57.883088 25.75,59.34375 C 34.172431,57.138896 40.17062,55.635095 46.34375,54.09375 C 68.404841,54.936763 70.366936,64.588264 50.03125,83.5 C 80.52166,67.640036 88.246281,56.011556 70.21875,48.46875 C 79.529352,46.508168 83.310469,46.138239 85.28125,46.3125 C 80.487749,43.302683 77.123885,41.944256 73.40625,41.6875 C 81.713537,30.753386 64.586454,23.918546 35.75,21.4375 z M 73.9375,43.25 C 74.54083,43.25 75.03125,43.724312 75.03125,44.28125 C 75.031251,44.83819 74.54083,45.28125 73.9375,45.28125 C 73.33417,45.281249 72.84375,44.838189 72.84375,44.28125 C 72.84375,43.724313 73.33417,43.25 73.9375,43.25 z" + transform="matrix(1.5329782,0,0,1.5087872,-34.813863,0.3399684)" /> + <path + sodipodi:type="inkscape:offset" + inkscape:radius="0" + inkscape:original="M 35.75 21.4375 C 67.770833 28.589068 61.962345 38.980958 48.59375 47.71875 C 42.117938 49.826519 34.459217 52.284088 23.875 55.28125 C 28.767848 54.681471 33.106944 54.336611 37.125 54.125 C 33.141375 56.073855 29.188794 57.883088 25.75 59.34375 C 34.172431 57.138896 40.17062 55.635095 46.34375 54.09375 C 68.404841 54.936763 70.366936 64.588264 50.03125 83.5 C 80.52166 67.640036 88.246281 56.011556 70.21875 48.46875 C 79.529352 46.508168 83.310469 46.138239 85.28125 46.3125 C 80.487749 43.302683 77.123885 41.944256 73.40625 41.6875 C 81.713537 30.753386 64.586454 23.918546 35.75 21.4375 z M 73.9375 43.25 C 74.54083 43.25 75.03125 43.724312 75.03125 44.28125 C 75.031251 44.83819 74.54083 45.28125 73.9375 45.28125 C 73.33417 45.281249 72.84375 44.838189 72.84375 44.28125 C 72.84375 43.724313 73.33417 43.25 73.9375 43.25 z " + style="fill:url(#linearGradient3162);fill-opacity:1;fill-rule:nonzero;stroke:#a34500;stroke-width:0.5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path2382" + d="M 35.75,21.4375 C 67.770833,28.589068 61.962345,38.980958 48.59375,47.71875 C 42.117938,49.826519 34.459217,52.284088 23.875,55.28125 C 28.767848,54.681471 33.106944,54.336611 37.125,54.125 C 33.141375,56.073855 29.188794,57.883088 25.75,59.34375 C 34.172431,57.138896 40.17062,55.635095 46.34375,54.09375 C 68.404841,54.936763 70.366936,64.588264 50.03125,83.5 C 80.52166,67.640036 88.246281,56.011556 70.21875,48.46875 C 79.529352,46.508168 83.310469,46.138239 85.28125,46.3125 C 80.487749,43.302683 77.123885,41.944256 73.40625,41.6875 C 81.713537,30.753386 64.586454,23.918546 35.75,21.4375 z M 73.9375,43.25 C 74.54083,43.25 75.03125,43.724312 75.03125,44.28125 C 75.031251,44.83819 74.54083,45.28125 73.9375,45.28125 C 73.33417,45.281249 72.84375,44.838189 72.84375,44.28125 C 72.84375,43.724313 73.33417,43.25 73.9375,43.25 z" + transform="matrix(1.8135246,0,0,1.8135246,-28.469382,-25.953029)" /> + <path + sodipodi:type="inkscape:offset" + inkscape:radius="0" + inkscape:original="M 35.75 21.4375 C 67.770833 28.589068 61.962345 38.980958 48.59375 47.71875 C 42.117938 49.826519 34.459217 52.284088 23.875 55.28125 C 28.767848 54.681471 33.106944 54.336611 37.125 54.125 C 33.141375 56.073855 29.188794 57.883088 25.75 59.34375 C 34.172431 57.138896 40.17062 55.635095 46.34375 54.09375 C 68.404841 54.936763 70.366936 64.588264 50.03125 83.5 C 80.52166 67.640036 88.246281 56.011556 70.21875 48.46875 C 79.529352 46.508168 83.310469 46.138239 85.28125 46.3125 C 80.487749 43.302683 77.123885 41.944256 73.40625 41.6875 C 81.713537 30.753386 64.586454 23.918546 35.75 21.4375 z M 73.9375 43.25 C 74.54083 43.25 75.03125 43.724312 75.03125 44.28125 C 75.031251 44.83819 74.54083 45.28125 73.9375 45.28125 C 73.33417 45.281249 72.84375 44.838189 72.84375 44.28125 C 72.84375 43.724313 73.33417 43.25 73.9375 43.25 z " + style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:0.10801075000000000px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + id="path2380" + d="M 35.75,21.4375 C 67.770833,28.589068 61.962345,38.980958 48.59375,47.71875 C 42.117938,49.826519 34.459217,52.284088 23.875,55.28125 C 28.767848,54.681471 33.106944,54.336611 37.125,54.125 C 33.141375,56.073855 29.188794,57.883088 25.75,59.34375 C 34.172431,57.138896 40.17062,55.635095 46.34375,54.09375 C 68.404841,54.936763 70.366936,64.588264 50.03125,83.5 C 80.52166,67.640036 88.246281,56.011556 70.21875,48.46875 C 79.529352,46.508168 83.310469,46.138239 85.28125,46.3125 C 80.487749,43.302683 77.123885,41.944256 73.40625,41.6875 C 81.713537,30.753386 64.586454,23.918546 35.75,21.4375 z M 73.9375,43.25 C 74.54083,43.25 75.03125,43.724312 75.03125,44.28125 C 75.031251,44.83819 74.54083,45.28125 73.9375,45.28125 C 73.33417,45.281249 72.84375,44.838189 72.84375,44.28125 C 72.84375,43.724313 73.33417,43.25 73.9375,43.25 z" + transform="matrix(1.4645354,0,0,1.4645354,-32.624604,-27.384051)" /> + </g> +</svg> diff --git a/Slimber/Resources/Offline.png b/Slimber/Resources/Offline.png Binary files differnew file mode 100644 index 0000000..6fed6ea --- /dev/null +++ b/Slimber/Resources/Offline.png diff --git a/Slimber/Resources/Online.png b/Slimber/Resources/Online.png Binary files differnew file mode 100644 index 0000000..b8c5cb5 --- /dev/null +++ b/Slimber/Resources/Online.png diff --git a/Slimber/Resources/Slimber.icns b/Slimber/Resources/Slimber.icns Binary files differnew file mode 100644 index 0000000..5deeb40 --- /dev/null +++ b/Slimber/Resources/Slimber.icns diff --git a/Slimber/Resources/UsersOffline.png b/Slimber/Resources/UsersOffline.png Binary files differnew file mode 100644 index 0000000..ad8062e --- /dev/null +++ b/Slimber/Resources/UsersOffline.png diff --git a/Slimber/Resources/UsersOnline.png b/Slimber/Resources/UsersOnline.png Binary files differnew file mode 100644 index 0000000..fb6b3e8 --- /dev/null +++ b/Slimber/Resources/UsersOnline.png diff --git a/Slimber/SConscript b/Slimber/SConscript new file mode 100644 index 0000000..b3ceba6 --- /dev/null +++ b/Slimber/SConscript @@ -0,0 +1,40 @@ +Import("env") + +################################################################################ +# Flags +################################################################################ + +if env["SCONS_STAGE"] == "flags" : + env["SLIMBER_FLAGS"] = { + "LIBPATH": [Dir(".")], + "LIBS": ["Slimber"] + } + +################################################################################ +# Build +################################################################################ + +if env["SCONS_STAGE"] == "build" : + myenv = env.Clone() + myenv.MergeFlags(env["BOOST_FLAGS"]) + myenv.MergeFlags(env["SWIFTEN_FLAGS"]) + myenv.StaticLibrary("Slimber", [ + "LinkLocalPresenceManager.cpp", + "FileVCardCollection.cpp", + "VCardCollection.cpp", + "Server.cpp", + "MainController.cpp", + "MenuletController.cpp", + "Menulet.cpp" + ]) + + env.Append(UNITTEST_SOURCES = [ + File("UnitTest/LinkLocalPresenceManagerTest.cpp"), + File("UnitTest/MenuletControllerTest.cpp") + ]) + + SConscript("CLI/SConscript") + if env["PLATFORM"] == "darwin" : + SConscript("Cocoa/SConscript") + else : + SConscript("Qt/SConscript") diff --git a/Slimber/Server.cpp b/Slimber/Server.cpp new file mode 100644 index 0000000..278a572 --- /dev/null +++ b/Slimber/Server.cpp @@ -0,0 +1,427 @@ +#include "Slimber/Server.h" + +#include <string> +#include <boost/bind.hpp> + +#include "Swiften/LinkLocal/LinkLocalConnector.h" +#include "Swiften/Network/Connection.h" +#include "Swiften/Session/SessionTracer.h" +#include "Swiften/Elements/Element.h" +#include "Swiften/Elements/Presence.h" +#include "Swiften/Elements/RosterPayload.h" +#include "Swiften/Network/BoostConnection.h" +#include "Swiften/Network/BoostConnectionServer.h" +#include "Swiften/Session/SessionTracer.h" +#include "Swiften/Elements/IQ.h" +#include "Swiften/Elements/VCard.h" +#include "Swiften/Server/UserRegistry.h" +#include "Swiften/Base/String.h" +#include "Swiften/LinkLocal/LinkLocalServiceInfo.h" +#include "Swiften/LinkLocal/OutgoingLinkLocalSession.h" +#include "Swiften/LinkLocal/IncomingLinkLocalSession.h" +#include "Swiften/LinkLocal/LinkLocalServiceBrowser.h" +#include "Swiften/Network/ConnectionServer.h" +#include "Slimber/VCardCollection.h" +#include "Slimber/LinkLocalPresenceManager.h" +#include "Swiften/Server/ServerFromClientSession.h" + +namespace Swift { + +Server::Server( + int clientConnectionPort, + int linkLocalConnectionPort, + LinkLocalServiceBrowser* linkLocalServiceBrowser, + VCardCollection* vCardCollection) : + linkLocalServiceRegistered(false), + rosterRequested(false), + clientConnectionPort(clientConnectionPort), + linkLocalConnectionPort(linkLocalConnectionPort), + linkLocalServiceBrowser(linkLocalServiceBrowser), + vCardCollection(vCardCollection), + presenceManager(NULL), + stopping(false) { + linkLocalServiceBrowser->onServiceRegistered.connect( + boost::bind(&Server::handleServiceRegistered, this, _1)); +} + +Server::~Server() { + stop(); +} + +void Server::start() { + assert(!serverFromClientConnectionServer); + serverFromClientConnectionServer = + boost::shared_ptr<BoostConnectionServer>(new BoostConnectionServer( + clientConnectionPort, &boostIOServiceThread.getIOService())); + serverFromClientConnectionServerSignalConnections.push_back( + serverFromClientConnectionServer->onNewConnection.connect( + boost::bind(&Server::handleNewClientConnection, this, _1))); + serverFromClientConnectionServerSignalConnections.push_back( + serverFromClientConnectionServer->onStopped.connect( + boost::bind(&Server::handleClientConnectionServerStopped, this, _1))); + + assert(!serverFromNetworkConnectionServer); + serverFromNetworkConnectionServer = + boost::shared_ptr<BoostConnectionServer>(new BoostConnectionServer( + linkLocalConnectionPort, &boostIOServiceThread.getIOService())); + serverFromNetworkConnectionServerSignalConnections.push_back( + serverFromNetworkConnectionServer->onNewConnection.connect( + boost::bind(&Server::handleNewLinkLocalConnection, this, _1))); + serverFromNetworkConnectionServerSignalConnections.push_back( + serverFromNetworkConnectionServer->onStopped.connect( + boost::bind(&Server::handleLinkLocalConnectionServerStopped, this, _1))); + + assert(!presenceManager); + presenceManager = new LinkLocalPresenceManager(linkLocalServiceBrowser); + presenceManager->onRosterChanged.connect( + boost::bind(&Server::handleRosterChanged, this, _1)); + presenceManager->onPresenceChanged.connect( + boost::bind(&Server::handlePresenceChanged, this, _1)); + + serverFromClientConnectionServer->start(); + serverFromNetworkConnectionServer->start(); +} + +void Server::stop() { + stop(boost::optional<ServerError>()); +} + +void Server::stop(boost::optional<ServerError> e) { + if (stopping) { + return; + } + + stopping = true; + + delete presenceManager; + presenceManager = NULL; + + if (serverFromClientSession) { + serverFromClientSession->finishSession(); + } + serverFromClientSession.reset(); + foreach(boost::shared_ptr<Session> session, linkLocalSessions) { + session->finishSession(); + } + linkLocalSessions.clear(); + foreach(boost::shared_ptr<LinkLocalConnector> connector, connectors) { + connector->cancel(); + } + connectors.clear(); + tracers.clear(); + + if (serverFromNetworkConnectionServer) { + serverFromNetworkConnectionServer->stop(); + foreach(boost::bsignals::connection& connection, serverFromNetworkConnectionServerSignalConnections) { + connection.disconnect(); + } + serverFromNetworkConnectionServerSignalConnections.clear(); + serverFromNetworkConnectionServer.reset(); + } + if (serverFromClientConnectionServer) { + serverFromClientConnectionServer->stop(); + foreach(boost::bsignals::connection& connection, serverFromClientConnectionServerSignalConnections) { + connection.disconnect(); + } + serverFromClientConnectionServerSignalConnections.clear(); + serverFromClientConnectionServer.reset(); + } + + stopping = false; + onStopped(e); +} + +void Server::handleNewClientConnection(boost::shared_ptr<Connection> connection) { + if (serverFromClientSession) { + connection->disconnect(); + } + serverFromClientSession = boost::shared_ptr<ServerFromClientSession>( + new ServerFromClientSession(idGenerator.generateID(), connection, + &payloadParserFactories, &payloadSerializers, &userRegistry)); + serverFromClientSession->onSessionStarted.connect( + boost::bind(&Server::handleSessionStarted, this)); + serverFromClientSession->onElementReceived.connect( + boost::bind(&Server::handleElementReceived, this, _1, + serverFromClientSession)); + serverFromClientSession->onSessionFinished.connect( + boost::bind(&Server::handleSessionFinished, this, + serverFromClientSession)); + //tracers.push_back(boost::shared_ptr<SessionTracer>( + // new SessionTracer(serverFromClientSession))); + serverFromClientSession->startSession(); +} + +void Server::handleSessionStarted() { + onSelfConnected(true); +} + +void Server::handleSessionFinished(boost::shared_ptr<ServerFromClientSession>) { + serverFromClientSession.reset(); + unregisterService(); + selfJID = JID(); + rosterRequested = false; + onSelfConnected(false); + lastPresence.reset(); +} + +void Server::unregisterService() { + if (linkLocalServiceRegistered) { + linkLocalServiceRegistered = false; + linkLocalServiceBrowser->unregisterService(); + } +} + +void Server::handleElementReceived(boost::shared_ptr<Element> element, boost::shared_ptr<ServerFromClientSession> session) { + boost::shared_ptr<Stanza> stanza = boost::dynamic_pointer_cast<Stanza>(element); + if (!stanza) { + return; + } + + stanza->setFrom(session->getRemoteJID()); + if (!stanza->getTo().isValid()) { + stanza->setTo(session->getLocalJID()); + } + + if (boost::shared_ptr<Presence> presence = boost::dynamic_pointer_cast<Presence>(stanza)) { + if (presence->getType() == Presence::Available) { + if (!linkLocalServiceRegistered) { + linkLocalServiceRegistered = true; + linkLocalServiceBrowser->registerService( + session->getRemoteJID().toBare().toString(), + linkLocalConnectionPort, getLinkLocalServiceInfo(presence)); + } + else { + linkLocalServiceBrowser->updateService( + getLinkLocalServiceInfo(presence)); + } + lastPresence = presence; + } + else { + unregisterService(); + } + } + else if (!stanza->getTo().isValid() || stanza->getTo() == session->getLocalJID() || stanza->getTo() == session->getRemoteJID().toBare()) { + if (boost::shared_ptr<IQ> iq = boost::dynamic_pointer_cast<IQ>(stanza)) { + if (iq->getPayload<RosterPayload>()) { + if (iq->getType() == IQ::Get) { + session->sendElement(IQ::createResult(iq->getFrom(), iq->getID(), presenceManager->getRoster())); + rosterRequested = true; + foreach(const boost::shared_ptr<Presence> presence, presenceManager->getAllPresence()) { + session->sendElement(presence); + } + } + else { + session->sendElement(IQ::createError(iq->getFrom(), iq->getID(), ErrorPayload::Forbidden, ErrorPayload::Cancel)); + } + } + if (boost::shared_ptr<VCard> vcard = iq->getPayload<VCard>()) { + if (iq->getType() == IQ::Get) { + session->sendElement(IQ::createResult(iq->getFrom(), iq->getID(), vCardCollection->getOwnVCard())); + } + else { + vCardCollection->setOwnVCard(vcard); + session->sendElement(IQ::createResult(iq->getFrom(), iq->getID())); + if (lastPresence) { + linkLocalServiceBrowser->updateService(getLinkLocalServiceInfo(lastPresence)); + } + } + } + else { + session->sendElement(IQ::createError(iq->getFrom(), iq->getID(), ErrorPayload::FeatureNotImplemented, ErrorPayload::Cancel)); + } + } + } + else { + JID toJID = stanza->getTo(); + boost::shared_ptr<Session> outgoingSession = + getLinkLocalSessionForJID(toJID); + if (outgoingSession) { + outgoingSession->sendElement(stanza); + } + else { + boost::optional<LinkLocalService> service = + presenceManager->getServiceForJID(toJID); + if (service) { + boost::shared_ptr<LinkLocalConnector> connector = + getLinkLocalConnectorForJID(toJID); + if (!connector) { + connector = boost::shared_ptr<LinkLocalConnector>( + new LinkLocalConnector( + *service, + linkLocalServiceBrowser->getQuerier(), + boost::shared_ptr<BoostConnection>(new BoostConnection(&boostIOServiceThread.getIOService())))); + connector->onConnectFinished.connect( + boost::bind(&Server::handleConnectFinished, this, connector, _1)); + connectors.push_back(connector); + connector->connect(); + } + connector->queueElement(element); + } + else { + session->sendElement(IQ::createError( + stanza->getFrom(), stanza->getID(), + ErrorPayload::RecipientUnavailable, ErrorPayload::Wait)); + } + } + } +} + +void Server::handleNewLinkLocalConnection(boost::shared_ptr<Connection> connection) { + boost::shared_ptr<IncomingLinkLocalSession> session( + new IncomingLinkLocalSession( + selfJID, connection, + &payloadParserFactories, &payloadSerializers)); + registerLinkLocalSession(session); +} + +void Server::handleLinkLocalSessionFinished(boost::shared_ptr<Session> session) { + //std::cout << "Link local session from " << session->getRemoteJID() << " ended" << std::endl; + linkLocalSessions.erase( + std::remove(linkLocalSessions.begin(), linkLocalSessions.end(), session), + linkLocalSessions.end()); +} + +void Server::handleLinkLocalElementReceived(boost::shared_ptr<Element> element, boost::shared_ptr<Session> session) { + if (boost::shared_ptr<Stanza> stanza = boost::dynamic_pointer_cast<Stanza>(element)) { + JID fromJID = session->getRemoteJID(); + if (!presenceManager->getServiceForJID(fromJID.toBare())) { + return; // TODO: Send error back + } + stanza->setFrom(fromJID); + serverFromClientSession->sendElement(stanza); + } +} + +void Server::handleConnectFinished(boost::shared_ptr<LinkLocalConnector> connector, bool error) { + if (error) { + std::cerr << "Error connecting" << std::endl; + // TODO: Send back queued stanzas + } + else { + boost::shared_ptr<OutgoingLinkLocalSession> outgoingSession( + new OutgoingLinkLocalSession( + selfJID, connector->getService().getJID(), connector->getConnection(), + &payloadParserFactories, &payloadSerializers)); + foreach(const boost::shared_ptr<Element> element, connector->getQueuedElements()) { + outgoingSession->queueElement(element); + } + registerLinkLocalSession(outgoingSession); + } + connectors.erase(std::remove(connectors.begin(), connectors.end(), connector), connectors.end()); +} + +void Server::registerLinkLocalSession(boost::shared_ptr<Session> session) { + session->onSessionFinished.connect( + boost::bind(&Server::handleLinkLocalSessionFinished, this, session)); + session->onElementReceived.connect( + boost::bind(&Server::handleLinkLocalElementReceived, this, _1, session)); + linkLocalSessions.push_back(session); + //tracers.push_back(boost::shared_ptr<SessionTracer>(new SessionTracer(session))); + session->startSession(); +} + +boost::shared_ptr<Session> Server::getLinkLocalSessionForJID(const JID& jid) { + foreach(const boost::shared_ptr<Session> session, linkLocalSessions) { + if (session->getRemoteJID() == jid) { + return session; + } + } + return boost::shared_ptr<Session>(); +} + +boost::shared_ptr<LinkLocalConnector> Server::getLinkLocalConnectorForJID(const JID& jid) { + foreach(const boost::shared_ptr<LinkLocalConnector> connector, connectors) { + if (connector->getService().getJID() == jid) { + return connector; + } + } + return boost::shared_ptr<LinkLocalConnector>(); +} + +void Server::handleServiceRegistered(const DNSSDServiceID& service) { + selfJID = JID(service.getName()); +} + +void Server::handleRosterChanged(boost::shared_ptr<RosterPayload> roster) { + if (rosterRequested) { + assert(serverFromClientSession); + boost::shared_ptr<IQ> iq = IQ::createRequest( + IQ::Set, serverFromClientSession->getRemoteJID(), + idGenerator.generateID(), roster); + iq->setFrom(serverFromClientSession->getRemoteJID().toBare()); + serverFromClientSession->sendElement(iq); + } +} + +void Server::handlePresenceChanged(boost::shared_ptr<Presence> presence) { + if (rosterRequested) { + serverFromClientSession->sendElement(presence); + } +} + +void Server::handleClientConnectionServerStopped(boost::optional<BoostConnectionServer::Error> e) { + if (e) { + if (*e == BoostConnectionServer::Conflict) { + stop(ServerError(ServerError::C2SPortConflict)); + } + else { + stop(ServerError(ServerError::C2SError)); + } + } + else { + stop(); + } +} + +void Server::handleLinkLocalConnectionServerStopped(boost::optional<BoostConnectionServer::Error> e) { + if (e) { + if (*e == BoostConnectionServer::Conflict) { + stop(ServerError(ServerError::LinkLocalPortConflict)); + } + else { + stop(ServerError(ServerError::LinkLocalError)); + } + } + else { + stop(); + } +} + +LinkLocalServiceInfo Server::getLinkLocalServiceInfo(boost::shared_ptr<Presence> presence) { + LinkLocalServiceInfo info; + boost::shared_ptr<VCard> vcard = vCardCollection->getOwnVCard(); + if (!vcard->getFamilyName().isEmpty() || !vcard->getGivenName().isEmpty()) { + info.setFirstName(vcard->getGivenName()); + info.setLastName(vcard->getFamilyName()); + } + else if (!vcard->getFullName().isEmpty()) { + std::pair<String,String> p = vcard->getFullName().getSplittedAtFirst(' '); + info.setFirstName(p.first); + info.setLastName(p.second); + } + if (!vcard->getNickname().isEmpty()) { + info.setNick(vcard->getNickname()); + } + if (!vcard->getEMail().isEmpty()) { + info.setEMail(vcard->getEMail()); + } + info.setMessage(presence->getStatus()); + switch (presence->getShow()) { + case StatusShow::Online: + case StatusShow::None: + case StatusShow::FFC: + info.setStatus(LinkLocalServiceInfo::Available); + break; + case StatusShow::Away: + case StatusShow::XA: + info.setStatus(LinkLocalServiceInfo::Away); + break; + case StatusShow::DND: + info.setStatus(LinkLocalServiceInfo::DND); + break; + } + info.setPort(linkLocalConnectionPort); + return info; +} + +} diff --git a/Slimber/Server.h b/Slimber/Server.h new file mode 100644 index 0000000..3587c50 --- /dev/null +++ b/Slimber/Server.h @@ -0,0 +1,112 @@ +#pragma once + +#include <boost/shared_ptr.hpp> +#include <boost/optional.hpp> +#include <vector> + +#include "Swiften/Network/BoostIOServiceThread.h" +#include "Swiften/Network/BoostConnectionServer.h" +#include "Swiften/Server/UserRegistry.h" +#include "Swiften/Base/IDGenerator.h" +#include "Swiften/Server/ServerFromClientSession.h" +#include "Swiften/JID/JID.h" +#include "Swiften/Parser/PayloadParsers/FullPayloadParserFactoryCollection.h" +#include "Swiften/Serializer/PayloadSerializers/FullPayloadSerializerCollection.h" +#include "Swiften/LinkLocal/LinkLocalServiceInfo.h" +#include "Slimber/ServerError.h" + +namespace Swift { + class DNSSDServiceID; + class String; + class VCardCollection; + class LinkLocalConnector; + class LinkLocalServiceBrowser; + class LinkLocalPresenceManager; + class BoostConnectionServer; + class SessionTracer; + class RosterPayload; + class Presence; + + class Server { + public: + Server( + int clientConnectionPort, + int linkLocalConnectionPort, + LinkLocalServiceBrowser* browser, + VCardCollection* vCardCollection); + ~Server(); + + void start(); + void stop(); + + int getLinkLocalPort() const { + return linkLocalConnectionPort; + } + + int getClientToServerPort() const { + return clientConnectionPort; + } + + boost::signal<void (bool)> onSelfConnected; + boost::signal<void (boost::optional<ServerError>)> onStopped; + + private: + void stop(boost::optional<ServerError>); + + void handleNewClientConnection(boost::shared_ptr<Connection> c); + void handleSessionStarted(); + void handleSessionFinished(boost::shared_ptr<ServerFromClientSession>); + void handleElementReceived(boost::shared_ptr<Element> element, boost::shared_ptr<ServerFromClientSession> session); + void handleRosterChanged(boost::shared_ptr<RosterPayload> roster); + void handlePresenceChanged(boost::shared_ptr<Presence> presence); + void handleServiceRegistered(const DNSSDServiceID& service); + void handleNewLinkLocalConnection(boost::shared_ptr<Connection> connection); + void handleLinkLocalSessionFinished(boost::shared_ptr<Session> session); + void handleLinkLocalElementReceived(boost::shared_ptr<Element> element, boost::shared_ptr<Session> session); + void handleConnectFinished(boost::shared_ptr<LinkLocalConnector> connector, bool error); + void handleClientConnectionServerStopped( + boost::optional<BoostConnectionServer::Error>); + void handleLinkLocalConnectionServerStopped( + boost::optional<BoostConnectionServer::Error>); + boost::shared_ptr<Session> getLinkLocalSessionForJID(const JID& jid); + boost::shared_ptr<LinkLocalConnector> getLinkLocalConnectorForJID(const JID& jid); + void registerLinkLocalSession(boost::shared_ptr<Session> session); + void unregisterService(); + LinkLocalServiceInfo getLinkLocalServiceInfo(boost::shared_ptr<Presence> presence); + + private: + class DummyUserRegistry : public UserRegistry { + public: + DummyUserRegistry() {} + + virtual bool isValidUserPassword(const JID&, const String&) const { + return true; + } + }; + + private: + IDGenerator idGenerator; + FullPayloadParserFactoryCollection payloadParserFactories; + FullPayloadSerializerCollection payloadSerializers; + BoostIOServiceThread boostIOServiceThread; + DummyUserRegistry userRegistry; + bool linkLocalServiceRegistered; + bool rosterRequested; + int clientConnectionPort; + int linkLocalConnectionPort; + LinkLocalServiceBrowser* linkLocalServiceBrowser; + VCardCollection* vCardCollection; + LinkLocalPresenceManager* presenceManager; + bool stopping; + boost::shared_ptr<BoostConnectionServer> serverFromClientConnectionServer; + std::vector<boost::bsignals::connection> serverFromClientConnectionServerSignalConnections; + boost::shared_ptr<ServerFromClientSession> serverFromClientSession; + boost::shared_ptr<Presence> lastPresence; + JID selfJID; + boost::shared_ptr<BoostConnectionServer> serverFromNetworkConnectionServer; + std::vector<boost::bsignals::connection> serverFromNetworkConnectionServerSignalConnections; + std::vector< boost::shared_ptr<Session> > linkLocalSessions; + std::vector< boost::shared_ptr<LinkLocalConnector> > connectors; + std::vector< boost::shared_ptr<SessionTracer> > tracers; + }; +} diff --git a/Slimber/ServerError.h b/Slimber/ServerError.h new file mode 100644 index 0000000..ce293c2 --- /dev/null +++ b/Slimber/ServerError.h @@ -0,0 +1,31 @@ +#pragma once + +#include "Swiften/Base/String.h" + +namespace Swift { + class ServerError { + public: + enum Type { + C2SPortConflict, + C2SError, + LinkLocalPortConflict, + LinkLocalError + }; + + ServerError(Type type, const String& message = String()) : + type(type), message(message) { + } + + Type getType() const { + return type; + } + + const String& getMessage() const { + return message; + } + + private: + Type type; + String message; + }; +} diff --git a/Slimber/UnitTest/LinkLocalPresenceManagerTest.cpp b/Slimber/UnitTest/LinkLocalPresenceManagerTest.cpp new file mode 100644 index 0000000..f77a8cb --- /dev/null +++ b/Slimber/UnitTest/LinkLocalPresenceManagerTest.cpp @@ -0,0 +1,255 @@ +#include <cppunit/extensions/HelperMacros.h> +#include <cppunit/extensions/TestFactoryRegistry.h> +#include <boost/bind.hpp> +#include <map> + +#include "Swiften/Elements/Presence.h" +#include "Swiften/Elements/RosterPayload.h" +#include "Swiften/Elements/RosterItemPayload.h" +#include "Slimber/LinkLocalPresenceManager.h" +#include "Swiften/LinkLocal/LinkLocalServiceInfo.h" +#include "Swiften/LinkLocal/LinkLocalServiceBrowser.h" +#include "Swiften/LinkLocal/DNSSD/DNSSDServiceID.h" +#include "Swiften/LinkLocal/DNSSD/DNSSDResolveServiceQuery.h" +#include "Swiften/LinkLocal/DNSSD/Fake/FakeDNSSDQuerier.h" +#include "Swiften/EventLoop/DummyEventLoop.h" + +using namespace Swift; + +class LinkLocalPresenceManagerTest : public CppUnit::TestFixture { + CPPUNIT_TEST_SUITE(LinkLocalPresenceManagerTest); + CPPUNIT_TEST(testConstructor); + CPPUNIT_TEST(testServiceAdded); + CPPUNIT_TEST(testServiceRemoved); + CPPUNIT_TEST(testServiceChanged); + CPPUNIT_TEST(testGetRoster); + CPPUNIT_TEST(testGetAllPresence); + CPPUNIT_TEST(testGetRoster_InfoWithNick); + CPPUNIT_TEST(testGetRoster_InfoWithFirstName); + CPPUNIT_TEST(testGetRoster_InfoWithLastName); + CPPUNIT_TEST(testGetRoster_InfoWithFirstAndLastName); + CPPUNIT_TEST(testGetRoster_NoInfo); + CPPUNIT_TEST(testGetServiceForJID); + CPPUNIT_TEST(testGetServiceForJID_NoMatch); + CPPUNIT_TEST_SUITE_END(); + + public: + void setUp() { + eventLoop = new DummyEventLoop(); + querier = boost::shared_ptr<FakeDNSSDQuerier>(new FakeDNSSDQuerier("wonderland.lit")); + browser = new LinkLocalServiceBrowser(querier); + browser->start(); + } + + void tearDown() { + browser->stop(); + delete browser; + delete eventLoop; + } + + void testConstructor() { + addService("alice@wonderland"); + addService("rabbit@teaparty"); + std::auto_ptr<LinkLocalPresenceManager> testling(createTestling()); + + CPPUNIT_ASSERT_EQUAL(2, static_cast<int>(testling->getRoster()->getItems().size())); + CPPUNIT_ASSERT_EQUAL(2, static_cast<int>(testling->getAllPresence().size())); + } + + void testServiceAdded() { + std::auto_ptr<LinkLocalPresenceManager> testling(createTestling()); + + addService("alice@wonderland", "Alice"); + + CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(rosterChanges.size())); + CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(rosterChanges[0]->getItems().size())); + boost::optional<RosterItemPayload> item = rosterChanges[0]->getItem(JID("alice@wonderland")); + CPPUNIT_ASSERT(item); + CPPUNIT_ASSERT_EQUAL(String("Alice"), item->getName()); + CPPUNIT_ASSERT_EQUAL(RosterItemPayload::Both, item->getSubscription()); + CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(presenceChanges.size())); + CPPUNIT_ASSERT(StatusShow::Online == presenceChanges[0]->getShow()); + CPPUNIT_ASSERT(JID("alice@wonderland") == presenceChanges[0]->getFrom()); + } + + void testServiceRemoved() { + std::auto_ptr<LinkLocalPresenceManager> testling(createTestling()); + + addService("alice@wonderland"); + removeService("alice@wonderland"); + + CPPUNIT_ASSERT_EQUAL(2, static_cast<int>(rosterChanges.size())); + CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(rosterChanges[1]->getItems().size())); + boost::optional<RosterItemPayload> item = rosterChanges[1]->getItem(JID("alice@wonderland")); + CPPUNIT_ASSERT(item); + CPPUNIT_ASSERT_EQUAL(RosterItemPayload::Remove, item->getSubscription()); + } + + void testServiceChanged() { + std::auto_ptr<LinkLocalPresenceManager> testling(createTestling()); + + addService("alice@wonderland"); + updateServicePresence("alice@wonderland", LinkLocalServiceInfo::Away, "I'm Away"); + + CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(rosterChanges.size())); + CPPUNIT_ASSERT_EQUAL(2, static_cast<int>(presenceChanges.size())); + CPPUNIT_ASSERT(StatusShow::Away == presenceChanges[1]->getShow()); + CPPUNIT_ASSERT(JID("alice@wonderland") == presenceChanges[1]->getFrom()); + CPPUNIT_ASSERT_EQUAL(String("I'm Away"), presenceChanges[1]->getStatus()); + } + + void testGetAllPresence() { + std::auto_ptr<LinkLocalPresenceManager> testling(createTestling()); + + addService("alice@wonderland"); + addService("rabbit@teaparty"); + updateServicePresence("rabbit@teaparty", LinkLocalServiceInfo::Away, "Partying"); + + std::vector<boost::shared_ptr<Presence> > presences = testling->getAllPresence(); + CPPUNIT_ASSERT_EQUAL(2, static_cast<int>(presences.size())); + // The order doesn't matter + CPPUNIT_ASSERT(JID("rabbit@teaparty") == presences[0]->getFrom()); + CPPUNIT_ASSERT(StatusShow::Away == presences[0]->getShow()); + CPPUNIT_ASSERT(JID("alice@wonderland") == presences[1]->getFrom()); + CPPUNIT_ASSERT(StatusShow::Online == presences[1]->getShow()); + } + + void testGetRoster() { + std::auto_ptr<LinkLocalPresenceManager> testling(createTestling()); + + addService("alice@wonderland", "Alice"); + addService("rabbit@teaparty", "Rabbit"); + + boost::shared_ptr<RosterPayload> roster = testling->getRoster(); + CPPUNIT_ASSERT_EQUAL(2, static_cast<int>(roster->getItems().size())); + boost::optional<RosterItemPayload> item; + item = roster->getItem(JID("alice@wonderland")); + CPPUNIT_ASSERT(item); + CPPUNIT_ASSERT_EQUAL(String("Alice"), item->getName()); + CPPUNIT_ASSERT_EQUAL(RosterItemPayload::Both, item->getSubscription()); + item = roster->getItem(JID("rabbit@teaparty")); + CPPUNIT_ASSERT(item); + CPPUNIT_ASSERT_EQUAL(String("Rabbit"), item->getName()); + CPPUNIT_ASSERT_EQUAL(RosterItemPayload::Both, item->getSubscription()); + } + + void testGetRoster_InfoWithNick() { + std::auto_ptr<LinkLocalPresenceManager> testling(createTestling()); + + addService("alice@wonderland", "Alice", "Alice In", "Wonderland"); + + boost::optional<RosterItemPayload> item = testling->getRoster()->getItem(JID("alice@wonderland")); + CPPUNIT_ASSERT_EQUAL(String("Alice"), item->getName()); + } + + void testGetRoster_InfoWithFirstName() { + std::auto_ptr<LinkLocalPresenceManager> testling(createTestling()); + + addService("alice@wonderland", "", "Alice In", ""); + + boost::optional<RosterItemPayload> item = testling->getRoster()->getItem(JID("alice@wonderland")); + CPPUNIT_ASSERT_EQUAL(String("Alice In"), item->getName()); + } + + void testGetRoster_InfoWithLastName() { + std::auto_ptr<LinkLocalPresenceManager> testling(createTestling()); + + addService("alice@wonderland", "", "", "Wonderland"); + + boost::optional<RosterItemPayload> item = testling->getRoster()->getItem(JID("alice@wonderland")); + CPPUNIT_ASSERT_EQUAL(String("Wonderland"), item->getName()); + } + + void testGetRoster_InfoWithFirstAndLastName() { + std::auto_ptr<LinkLocalPresenceManager> testling(createTestling()); + + addService("alice@wonderland", "", "Alice In", "Wonderland"); + + boost::optional<RosterItemPayload> item = testling->getRoster()->getItem(JID("alice@wonderland")); + CPPUNIT_ASSERT_EQUAL(String("Alice In Wonderland"), item->getName()); + } + + void testGetRoster_NoInfo() { + std::auto_ptr<LinkLocalPresenceManager> testling(createTestling()); + + addService("alice@wonderland"); + + boost::optional<RosterItemPayload> item = testling->getRoster()->getItem(JID("alice@wonderland")); + CPPUNIT_ASSERT_EQUAL(String(""), item->getName()); + } + + void testGetServiceForJID() { + std::auto_ptr<LinkLocalPresenceManager> testling(createTestling()); + + addService("alice@wonderland"); + addService("rabbit@teaparty"); + addService("queen@garden"); + + boost::optional<LinkLocalService> service = testling->getServiceForJID(JID("rabbit@teaparty")); + CPPUNIT_ASSERT(service); + CPPUNIT_ASSERT_EQUAL(String("rabbit@teaparty"), service->getID().getName()); + } + + void testGetServiceForJID_NoMatch() { + std::auto_ptr<LinkLocalPresenceManager> testling(createTestling()); + + addService("alice@wonderland"); + addService("queen@garden"); + + CPPUNIT_ASSERT(!testling->getServiceForJID(JID("rabbit@teaparty"))); + } + + private: + std::auto_ptr<LinkLocalPresenceManager> createTestling() { + std::auto_ptr<LinkLocalPresenceManager> testling( + new LinkLocalPresenceManager(browser)); + testling->onRosterChanged.connect(boost::bind( + &LinkLocalPresenceManagerTest::handleRosterChanged, this, _1)); + testling->onPresenceChanged.connect(boost::bind( + &LinkLocalPresenceManagerTest::handlePresenceChanged, this, _1)); + return testling; + } + + void addService(const String& name, const String& nickName = String(), const String& firstName = String(), const String& lastName = String()) { + DNSSDServiceID service(name, "local."); + LinkLocalServiceInfo info; + info.setFirstName(firstName); + info.setLastName(lastName); + info.setNick(nickName); + querier->setServiceInfo(service, DNSSDResolveServiceQuery::Result(name + "._presence._tcp.local", "rabbithole.local", 1234, info.toTXTRecord())); + querier->addService(service); + eventLoop->processEvents(); + } + + void removeService(const String& name) { + DNSSDServiceID service(name, "local."); + querier->removeService(DNSSDServiceID(name, "local.")); + eventLoop->processEvents(); + } + + void updateServicePresence(const String& name, LinkLocalServiceInfo::Status status, const String& message) { + DNSSDServiceID service(name, "local."); + LinkLocalServiceInfo info; + info.setStatus(status); + info.setMessage(message); + querier->setServiceInfo(service, DNSSDResolveServiceQuery::Result(name + "._presence._tcp.local", "rabbithole.local", 1234, info.toTXTRecord())); + eventLoop->processEvents(); + } + + void handleRosterChanged(boost::shared_ptr<RosterPayload> payload) { + rosterChanges.push_back(payload); + } + + void handlePresenceChanged(boost::shared_ptr<Presence> presence) { + presenceChanges.push_back(presence); + } + + private: + DummyEventLoop* eventLoop; + boost::shared_ptr<FakeDNSSDQuerier> querier; + LinkLocalServiceBrowser* browser; + std::vector< boost::shared_ptr<RosterPayload> > rosterChanges; + std::vector< boost::shared_ptr<Presence> > presenceChanges; +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(LinkLocalPresenceManagerTest); diff --git a/Slimber/UnitTest/MenuletControllerTest.cpp b/Slimber/UnitTest/MenuletControllerTest.cpp new file mode 100644 index 0000000..c666679 --- /dev/null +++ b/Slimber/UnitTest/MenuletControllerTest.cpp @@ -0,0 +1,151 @@ +#include <cppunit/extensions/HelperMacros.h> +#include <cppunit/extensions/TestFactoryRegistry.h> + +#include "Slimber/Menulet.h" +#include "Slimber/MenuletController.h" + +using namespace Swift; + +class MenuletControllerTest : public CppUnit::TestFixture { + CPPUNIT_TEST_SUITE(MenuletControllerTest); + CPPUNIT_TEST(testConstructor); + CPPUNIT_TEST(testUpdate); + CPPUNIT_TEST(testSetXMPPStatus_Online); + CPPUNIT_TEST(testSetXMPPStatus_Offline); + CPPUNIT_TEST(testSetUserNames); + CPPUNIT_TEST(testSetUserNames_NoUsers); + CPPUNIT_TEST_SUITE_END(); + + public: + void setUp() { + menulet = new FakeMenulet(); + } + + void tearDown() { + delete menulet; + } + + void testConstructor() { + MenuletController testling(menulet); + + CPPUNIT_ASSERT_EQUAL(8, static_cast<int>(menulet->items.size())); + int i = 0; + CPPUNIT_ASSERT_EQUAL(String("No online users"), menulet->items[i++]); + CPPUNIT_ASSERT_EQUAL(String("-"), menulet->items[i++]); + CPPUNIT_ASSERT_EQUAL(String("[Offline] "), menulet->items[i++]); + CPPUNIT_ASSERT_EQUAL(String("-"), menulet->items[i++]); + CPPUNIT_ASSERT_EQUAL(String("*About*"), menulet->items[i++]); + CPPUNIT_ASSERT_EQUAL(String("-"), menulet->items[i++]); + CPPUNIT_ASSERT_EQUAL(String("*Restart*"), menulet->items[i++]); + CPPUNIT_ASSERT_EQUAL(String("*Exit*"), menulet->items[i++]); + } + + void testUpdate() { + MenuletController testling(menulet); + + testling.setXMPPStatus("You are connected", MenuletController::Online); + + CPPUNIT_ASSERT_EQUAL(8, static_cast<int>(menulet->items.size())); + int i = 0; + CPPUNIT_ASSERT_EQUAL(String("No online users"), menulet->items[i++]); + CPPUNIT_ASSERT_EQUAL(String("-"), menulet->items[i++]); + CPPUNIT_ASSERT_EQUAL(String("[Online] You are connected"), menulet->items[i++]); + CPPUNIT_ASSERT_EQUAL(String("-"), menulet->items[i++]); + CPPUNIT_ASSERT_EQUAL(String("*About*"), menulet->items[i++]); + CPPUNIT_ASSERT_EQUAL(String("-"), menulet->items[i++]); + CPPUNIT_ASSERT_EQUAL(String("*Restart*"), menulet->items[i++]); + CPPUNIT_ASSERT_EQUAL(String("*Exit*"), menulet->items[i++]); + } + + void testSetXMPPStatus_Online() { + MenuletController testling(menulet); + + testling.setXMPPStatus("You are connected", MenuletController::Online); + + int i = 0; + CPPUNIT_ASSERT_EQUAL(String("No online users"), menulet->items[i++]); + CPPUNIT_ASSERT_EQUAL(String("-"), menulet->items[i++]); + CPPUNIT_ASSERT_EQUAL(String("[Online] You are connected"), menulet->items[i++]); + } + + + void testSetXMPPStatus_Offline() { + MenuletController testling(menulet); + + testling.setXMPPStatus("You are not connected", MenuletController::Offline); + + int i = 0; + CPPUNIT_ASSERT_EQUAL(String("No online users"), menulet->items[i++]); + CPPUNIT_ASSERT_EQUAL(String("-"), menulet->items[i++]); + CPPUNIT_ASSERT_EQUAL(String("[Offline] You are not connected"), menulet->items[i++]); + } + + void testSetUserNames() { + MenuletController testling(menulet); + + std::vector<String> users; + users.push_back("Alice In Wonderland"); + users.push_back("The Mad Hatter"); + testling.setUserNames(users); + + int i = 0; + CPPUNIT_ASSERT_EQUAL(String("Online users:"), menulet->items[i++]); + CPPUNIT_ASSERT_EQUAL(String(" Alice In Wonderland"), menulet->items[i++]); + CPPUNIT_ASSERT_EQUAL(String(" The Mad Hatter"), menulet->items[i++]); + CPPUNIT_ASSERT_EQUAL(String("-"), menulet->items[i++]); + } + + void testSetUserNames_NoUsers() { + MenuletController testling(menulet); + + std::vector<String> users; + testling.setUserNames(users); + + int i = 0; + CPPUNIT_ASSERT_EQUAL(String("No online users"), menulet->items[i++]); + CPPUNIT_ASSERT_EQUAL(String("-"), menulet->items[i++]); + } + + private: + struct FakeMenulet : public Menulet { + virtual void clear() { + items.clear(); + } + + virtual void addItem(const String& name, const String& icon = String()) { + String result; + if (!icon.isEmpty()) { + result += "[" + icon + "] "; + } + result += name; + items.push_back(result); + } + + virtual void addAboutItem() { + items.push_back("*About*"); + } + + virtual void addRestartItem() { + items.push_back("*Restart*"); + } + + virtual void addExitItem() { + items.push_back("*Exit*"); + } + + virtual void addSeparator() { + items.push_back("-"); + } + + virtual void setIcon(const String& i) { + icon = i; + } + + std::vector<String> items; + String icon; + }; + + FakeMenulet* menulet; +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(MenuletControllerTest); diff --git a/Slimber/VCardCollection.cpp b/Slimber/VCardCollection.cpp new file mode 100644 index 0000000..1477429 --- /dev/null +++ b/Slimber/VCardCollection.cpp @@ -0,0 +1,8 @@ +#include "Slimber/VCardCollection.h" + +namespace Swift { + +VCardCollection::~VCardCollection() { +} + +} diff --git a/Slimber/VCardCollection.h b/Slimber/VCardCollection.h new file mode 100644 index 0000000..42f7df7 --- /dev/null +++ b/Slimber/VCardCollection.h @@ -0,0 +1,15 @@ +#pragma once + +#include <boost/shared_ptr.hpp> + +#include "Swiften/Elements/VCard.h" + +namespace Swift { + class VCardCollection { + public: + virtual ~VCardCollection(); + + virtual boost::shared_ptr<VCard> getOwnVCard() const = 0; + virtual void setOwnVCard(boost::shared_ptr<VCard> vcard) = 0; + }; +} |