Merge ~morphis/aethercast/+git/aethercast:feature/cli-rework into aethercast:master

Proposed by Simon Fels
Status: Needs review
Proposed branch: ~morphis/aethercast/+git/aethercast:feature/cli-rework
Merge into: aethercast:master
Prerequisite: ~morphis/aethercast/+git/aethercast:feature/snap-support
Diff against target: 2434 lines (+1472/-58)
18 files modified
dev/null (+0/-25)
snapcraft.yaml (+9/-0)
src/CMakeLists.txt (+11/-2)
src/ac/cli.cpp (+246/-0)
src/ac/cli.h (+343/-0)
src/ac/client/application.cpp (+541/-0)
src/ac/client/application.h (+112/-0)
src/ac/cmds/service.cpp (+28/-0)
src/ac/cmds/service.h (+32/-0)
src/ac/cmds/shell.cpp (+28/-0)
src/ac/cmds/shell.h (+32/-0)
src/ac/do_not_copy_or_move.h (+36/-0)
src/ac/main.cpp (+15/-2)
src/ac/optional.h (+31/-0)
src/ac/service.cpp (+0/-27)
src/ac/service.h (+0/-2)
src/ac/utils.cpp (+6/-0)
src/ac/utils.h (+2/-0)
Reviewer Review Type Date Requested Status
Ubuntu Phablet Team Pending
Review via email: mp+315439@code.launchpad.net

Commit message

Rework command line frontend and move everything into a single binary

Description of the change

Rework command line frontend and move everything into a single binary

Both shell and service are now covered by a single aethercast binary
which can be invoked via

 $ aethercast service

to start the service component or as

 $ aethercast shell

to get into the interactive shell. More utility things like
mirscreencast_to_stream will be converted into a subcommand as well.

To post a comment you must log in.
856701f... by Simon Fels

Rework configuration to be interchangable

Unmerged commits

dce5ad2... by Simon Fels

Extend snapcraft configuration to cover changed cli

83e7725... by Simon Fels

Rework command line frontend and move everything into a single binary

Both shell and service are now covered by a single aethercast binary
which can be invoked via

 $ aethercast service

to start the service component or as

 $ aethercast shell

to get into the interactive shell. More utility things like
mirscreencast_to_stream will be converted into a subcommand as well.

856701f... by Simon Fels

Rework configuration to be interchangable

630a5da... by Simon Fels

Add snap build configuration

Currently builds aethercast without Mir and Android support. Those
things can be added back at a later point.

18c722d... by Simon Fels

Refactor necessary code bits to allow execute inside a snap

98f844b... by Simon Fels

Enable support for Mir and Android only optional

Both are not yet mutally exclusive and still depend on each other but
Mir support will become independent of the Android bits over time so
we put both decoupled already from the beginning.

294eee4... by Simon Fels

Add maintainer script to get rid of unwanted configuration file

5aa83f2... by Simon Fels

Allow dhcpd to write files which just include .lease in the name

dhcpd seems to write some temp files when its creating the lease file
and therefor needs wider access and just *.lease

cf472e7... by Simon Fels

Correctly install hook for dhclient

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/snapcraft.yaml b/snapcraft.yaml
2index 4b4bea5..017bcdf 100644
3--- a/snapcraft.yaml
4+++ b/snapcraft.yaml
5@@ -13,10 +13,19 @@ slots:
6 interface: dbus
7 bus: system
8 name: org.aethercast
9+plugs:
10+ dbus-client:
11+ interface: dbus
12+ bus: system
13+ name: org.aethercast
14
15 apps:
16 aethercast:
17 command: sbin/aethercast
18+ plugs:
19+ - dbus-client
20+ service:
21+ command: sbin/aethercast service
22 daemon: simple
23 slots:
24 - dbus-service
25diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
26index 8522a89..c55d44c 100644
27--- a/src/CMakeLists.txt
28+++ b/src/CMakeLists.txt
29@@ -21,6 +21,9 @@ set(HEADERS
30 ac/config.cpp
31 ac/config.h
32
33+ ac/optional.h
34+ ac/do_not_copy_or_move.h
35+
36 ac/common/executable.h
37 ac/common/executor.h
38 ac/common/executorfactory.h
39@@ -53,6 +56,7 @@ set(ANDROID_SOURCES
40 ac/android/h264encoder.cpp)
41
42 set(SOURCES
43+ ac/cli.cpp
44 ac/types.cpp
45 ac/utils.cpp
46 ac/networkutils.cpp
47@@ -69,6 +73,11 @@ set(SOURCES
48 ac/networkmanagerfactory.cpp
49 ac/networkdevice.cpp
50
51+ ac/cmds/shell.cpp
52+ ac/cmds/service.cpp
53+
54+ ac/client/application.cpp
55+
56 ac/dbus/helpers.cpp
57 ac/dbus/errors.cpp
58 ac/dbus/controllerskeleton.cpp
59@@ -189,6 +198,8 @@ target_link_libraries(aethercast-core
60 ${EGL_LIBRARIES}
61 ${GLESV2_LDFLAGS}
62 ${GLESV2_LIBRARIES}
63+ ${READLINE_LDFLAGS}
64+ ${READLINE_LIBRARIES}
65 -ldl
66 )
67
68@@ -210,5 +221,3 @@ install(
69 LIBRARY DESTINATION lib
70 ARCHIVE DESTINATION lib/static
71 )
72-
73-add_subdirectory(client)
74diff --git a/src/ac/cli.cpp b/src/ac/cli.cpp
75new file mode 100644
76index 0000000..1129244
77--- /dev/null
78+++ b/src/ac/cli.cpp
79@@ -0,0 +1,246 @@
80+/*
81+ * Copyright (C) 2016 Canonical, Ltd.
82+ *
83+ * This program is free software; you can redistribute it and/or modify
84+ * it under the terms of the GNU Lesser General Public License as published by
85+ * the Free Software Foundation; version 3.
86+ *
87+ * This program is distributed in the hope that it will be useful,
88+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
89+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
90+ * GNU Lesser General Public License for more details.
91+ *
92+ * You should have received a copy of the GNU Lesser General Public License
93+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
94+ *
95+ * Authored by: Thomas Voß <thomas.voss@canonical.com>
96+ *
97+ */
98+
99+#include <boost/format.hpp>
100+#include <boost/program_options.hpp>
101+
102+#include "ac/cli.h"
103+
104+namespace cli = ac::cli;
105+namespace po = boost::program_options;
106+
107+namespace {
108+namespace pattern {
109+static constexpr const char* help_for_command_with_subcommands =
110+ "NAME:\n"
111+ " %1% - %2%\n"
112+ "\n"
113+ "USAGE:\n"
114+ " %3% [command options] [arguments...]";
115+
116+static constexpr const char* commands = "COMMANDS:";
117+static constexpr const char* command = " %1% %2%";
118+
119+static constexpr const char* options = "OPTIONS:";
120+static constexpr const char* option = " --%1% %2%";
121+}
122+
123+void add_to_desc_for_flags(po::options_description& desc,
124+ const std::set<cli::Flag::Ptr>& flags) {
125+ for (auto flag : flags) {
126+ auto v = po::value<std::string>()->notifier(
127+ [flag](const std::string& s) { flag->notify(s); });
128+ desc.add_options()(flag->name().as_string().c_str(), v,
129+ flag->description().as_string().c_str());
130+ }
131+}
132+}
133+
134+std::vector<std::string> cli::args(int argc, char** argv) {
135+ std::vector<std::string> result;
136+ for (int i = 1; i < argc; i++) result.push_back(argv[i]);
137+ return result;
138+}
139+
140+const cli::Name& cli::Flag::name() const { return name_; }
141+
142+const cli::Description& cli::Flag::description() const { return description_; }
143+
144+cli::Flag::Flag(const Name& name, const Description& description)
145+ : name_{name}, description_{description} {}
146+
147+cli::Command::FlagsWithInvalidValue::FlagsWithInvalidValue()
148+ : std::runtime_error{"Flags with invalid value"} {}
149+
150+cli::Command::FlagsMissing::FlagsMissing()
151+ : std::runtime_error{"Flags are missing in command invocation"} {}
152+
153+cli::Name cli::Command::name() const { return name_; }
154+
155+cli::Usage cli::Command::usage() const { return usage_; }
156+
157+cli::Description cli::Command::description() const { return description_; }
158+
159+cli::Command::Command(const cli::Name& name, const cli::Usage& usage,
160+ const cli::Description& description)
161+ : name_(name), usage_(usage), description_(description) {}
162+
163+cli::CommandWithSubcommands::CommandWithSubcommands(
164+ const Name& name, const Usage& usage, const Description& description)
165+ : Command{name, usage, description} {
166+ command(std::make_shared<cmd::Help>(*this));
167+}
168+
169+cli::CommandWithSubcommands& cli::CommandWithSubcommands::command(
170+ const Command::Ptr& command) {
171+ commands_[command->name().as_string()] = command;
172+ return *this;
173+}
174+
175+cli::CommandWithSubcommands& cli::CommandWithSubcommands::flag(
176+ const Flag::Ptr& flag) {
177+ flags_.insert(flag);
178+ return *this;
179+}
180+
181+void cli::CommandWithSubcommands::help(std::ostream& out) {
182+ out << boost::format(pattern::help_for_command_with_subcommands) %
183+ name().as_string() % usage().as_string() % name().as_string()
184+ << std::endl;
185+
186+ if (flags_.size() > 0) {
187+ out << std::endl
188+ << pattern::options << std::endl;
189+ for (const auto& flag : flags_)
190+ out << boost::format(pattern::option) % flag->name() % flag->description()
191+ << std::endl;
192+ }
193+
194+ if (commands_.size() > 0) {
195+ out << std::endl
196+ << pattern::commands << std::endl;
197+ for (const auto& cmd : commands_) {
198+ if (cmd.second)
199+ out << boost::format(pattern::command) % cmd.second->name() %
200+ cmd.second->description()
201+ << std::endl;
202+ }
203+ }
204+}
205+
206+int cli::CommandWithSubcommands::run(const cli::Command::Context& ctxt) {
207+ po::positional_options_description pdesc;
208+ pdesc.add("command", 1);
209+
210+ po::options_description desc("Options");
211+ desc.add_options()("command", po::value<std::string>()->required(),
212+ "the command to be executed");
213+
214+ add_to_desc_for_flags(desc, flags_);
215+
216+ try {
217+ po::variables_map vm;
218+ auto parsed = po::command_line_parser(ctxt.args)
219+ .options(desc)
220+ .positional(pdesc)
221+ .style(po::command_line_style::unix_style)
222+ .allow_unregistered()
223+ .run();
224+
225+ po::store(parsed, vm);
226+ po::notify(vm);
227+
228+ auto cmd = commands_[vm["command"].as<std::string>()];
229+ if (!cmd) {
230+ ctxt.cout << "Unknown command '" << vm["command"].as<std::string>() << "'"
231+ << std::endl;
232+ help(ctxt.cout);
233+ return EXIT_FAILURE;
234+ }
235+
236+ return cmd->run(cli::Command::Context{
237+ ctxt.cin, ctxt.cout,
238+ po::collect_unrecognized(parsed.options, po::include_positional)});
239+ } catch (const po::error& e) {
240+ ctxt.cout << e.what() << std::endl;
241+ help(ctxt.cout);
242+ return EXIT_FAILURE;
243+ }
244+
245+ return EXIT_FAILURE;
246+}
247+
248+cli::CommandWithFlagsAndAction::CommandWithFlagsAndAction(
249+ const Name& name, const Usage& usage, const Description& description)
250+ : Command{name, usage, description} {}
251+
252+cli::CommandWithFlagsAndAction& cli::CommandWithFlagsAndAction::flag(
253+ const Flag::Ptr& flag) {
254+ flags_.insert(flag);
255+ return *this;
256+}
257+
258+cli::CommandWithFlagsAndAction& cli::CommandWithFlagsAndAction::action(
259+ const Action& action) {
260+ action_ = action;
261+ return *this;
262+}
263+
264+int cli::CommandWithFlagsAndAction::run(const Context& ctxt) {
265+ po::options_description cd(name().as_string());
266+
267+ bool help_requested{false};
268+ cd.add_options()("help", po::bool_switch(&help_requested),
269+ "produces a help message");
270+
271+ add_to_desc_for_flags(cd, flags_);
272+
273+ try {
274+ po::variables_map vm;
275+ auto parsed = po::command_line_parser(ctxt.args)
276+ .options(cd)
277+ .style(po::command_line_style::unix_style)
278+ .allow_unregistered()
279+ .run();
280+ po::store(parsed, vm);
281+ po::notify(vm);
282+
283+ if (help_requested) {
284+ help(ctxt.cout);
285+ return EXIT_SUCCESS;
286+ }
287+
288+ return action_(cli::Command::Context{
289+ ctxt.cin, ctxt.cout,
290+ po::collect_unrecognized(parsed.options, po::include_positional)});
291+ } catch (const po::error& e) {
292+ ctxt.cout << e.what() << std::endl;
293+ help(ctxt.cout);
294+ return EXIT_FAILURE;
295+ }
296+
297+ return EXIT_FAILURE;
298+}
299+
300+void cli::CommandWithFlagsAndAction::help(std::ostream& out) {
301+ out << boost::format(pattern::help_for_command_with_subcommands) %
302+ name().as_string() % description().as_string() % name().as_string()
303+ << std::endl;
304+
305+ if (flags_.size() > 0) {
306+ out << std::endl
307+ << boost::format(pattern::options) << std::endl;
308+ for (const auto& flag : flags_)
309+ out << boost::format(pattern::option) % flag->name() % flag->description()
310+ << std::endl;
311+ }
312+}
313+
314+cli::cmd::Help::Help(Command& cmd)
315+ : Command{cli::Name{"help"}, cli::Usage{"prints a short help message"},
316+ cli::Description{"prints a short help message"}},
317+ command{cmd} {}
318+
319+// From Command
320+int cli::cmd::Help::run(const Context& context) {
321+ command.help(context.cout);
322+ return EXIT_FAILURE;
323+}
324+
325+void cli::cmd::Help::help(std::ostream& out) { command.help(out); }
326diff --git a/src/ac/cli.h b/src/ac/cli.h
327new file mode 100644
328index 0000000..131965f
329--- /dev/null
330+++ b/src/ac/cli.h
331@@ -0,0 +1,343 @@
332+/*
333+ * Copyright (C) 2016 Canonical, Ltd.
334+ *
335+ * This program is free software; you can redistribute it and/or modify
336+ * it under the terms of the GNU Lesser General Public License as published by
337+ * the Free Software Foundation; version 3.
338+ *
339+ * This program is distributed in the hope that it will be useful,
340+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
341+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
342+ * GNU Lesser General Public License for more details.
343+ *
344+ * You should have received a copy of the GNU Lesser General Public License
345+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
346+ *
347+ * Authored by: Thomas Voß <thomas.voss@canonical.com>
348+ *
349+ */
350+#ifndef AETHERCAST_CLI_H_
351+#define AETHERCAST_CLI_H_
352+
353+#include <iomanip>
354+#include <iostream>
355+#include <memory>
356+#include <set>
357+#include <sstream>
358+#include <stdexcept>
359+#include <string>
360+#include <unordered_map>
361+
362+#include "ac/do_not_copy_or_move.h"
363+#include "ac/optional.h"
364+
365+namespace ac {
366+namespace cli {
367+
368+template <std::size_t max>
369+class SizeConstrainedString {
370+ public:
371+ SizeConstrainedString(const std::string& s) : s{s} {
372+ if (s.size() > max)
373+ throw std::logic_error{"Max size exceeded " + std::to_string(max)};
374+ }
375+
376+ const std::string& as_string() const { return s; }
377+
378+ operator std::string() const { return s; }
379+
380+ private:
381+ std::string s;
382+};
383+
384+template <std::size_t max>
385+bool operator<(const SizeConstrainedString<max>& lhs,
386+ const SizeConstrainedString<max>& rhs) {
387+ return lhs.as_string() < rhs.as_string();
388+}
389+
390+template <std::size_t max>
391+bool operator==(const SizeConstrainedString<max>& lhs,
392+ const SizeConstrainedString<max>& rhs) {
393+ return lhs.as_string() == rhs.as_string();
394+}
395+
396+template <std::size_t max>
397+std::ostream& operator<<(std::ostream& out,
398+ const SizeConstrainedString<max>& scs) {
399+ return out << std::setw(max) << std::left << scs.as_string();
400+}
401+
402+// We are imposing size constraints to ensure a consistent CLI layout.
403+typedef SizeConstrainedString<20> Name;
404+typedef SizeConstrainedString<60> Usage;
405+typedef SizeConstrainedString<60> Description;
406+
407+/// @brief Flag models an input parameter to a command.
408+class Flag : public DoNotCopyOrMove {
409+ public:
410+ // Safe us some typing.
411+ typedef std::shared_ptr<Flag> Ptr;
412+
413+ /// @brief notify announces a new value to the flag.
414+ virtual void notify(const std::string& value) = 0;
415+ /// @brief name returns the name of the Flag.
416+ const Name& name() const;
417+ /// @brief description returns a human-readable description of the flag.
418+ const Description& description() const;
419+
420+ protected:
421+ /// @brief Flag creates a new instance, initializing name and description
422+ /// from the given values.
423+ Flag(const Name& name, const Description& description);
424+
425+ private:
426+ Name name_;
427+ Description description_;
428+};
429+
430+/// @brief TypedFlag implements Flag relying on operator<< and operator>> to
431+/// read/write values to/from strings.
432+template <typename T>
433+class TypedFlag : public Flag {
434+ public:
435+ typedef std::shared_ptr<TypedFlag<T>> Ptr;
436+
437+ TypedFlag(const Name& name, const Description& description)
438+ : Flag{name, description} {}
439+
440+ /// @brief value installs the given value in the flag.
441+ TypedFlag& value(const T& value) {
442+ value_ = value;
443+ return *this;
444+ }
445+
446+ /// @brief value returns the optional value associated with the flag.
447+ const Optional<T>& value() const { return value_; }
448+
449+ /// @brief notify tries to unwrap a value of type T from value.
450+ void notify(const std::string& s) override {
451+ std::stringstream ss{s};
452+ T value;
453+ ss >> value;
454+ value_ = value;
455+ }
456+
457+ private:
458+ Optional<T> value_;
459+};
460+
461+/// @brief TypedReferenceFlag implements Flag, relying on operator<</>> to
462+/// convert to/from string representations,
463+/// updating the given mutable reference to a value of type T.
464+template <typename T>
465+class TypedReferenceFlag : public Flag {
466+ public:
467+ // Safe us some typing.
468+ typedef std::shared_ptr<TypedReferenceFlag<T>> Ptr;
469+
470+ /// @brief TypedReferenceFlag initializes a new instance with name,
471+ /// description and value.
472+ TypedReferenceFlag(const Name& name, const Description& description, T& value)
473+ : Flag{name, description}, value_{value} {}
474+
475+ /// @brief notify tries to unwrap a value of type T from value,
476+ /// relying on operator>> to read from given string s.
477+ void notify(const std::string& s) override {
478+ std::stringstream ss{s};
479+ ss >> value_.get();
480+ }
481+
482+ private:
483+ std::reference_wrapper<T> value_;
484+};
485+
486+/// @brief OptionalTypedReferenceFlag handles Optional<T> references, making
487+/// sure that
488+/// a value is always read on notify, even if the Optional<T> wasn't initialized
489+/// previously.
490+template <typename T>
491+class OptionalTypedReferenceFlag : public Flag {
492+ public:
493+ typedef std::shared_ptr<OptionalTypedReferenceFlag<T>> Ptr;
494+
495+ OptionalTypedReferenceFlag(const Name& name, const Description& description,
496+ Optional<T>& value)
497+ : Flag{name, description}, value_{value} {}
498+
499+ /// @brief notify tries to unwrap a value of type T from value.
500+ void notify(const std::string& s) override {
501+ std::stringstream ss{s};
502+ T value;
503+ ss >> value;
504+ value_.get() = value;
505+ }
506+
507+ private:
508+ std::reference_wrapper<Optional<T>> value_;
509+};
510+
511+/// @brief Command abstracts an individual command available from the daemon.
512+class Command : public DoNotCopyOrMove {
513+ public:
514+ // Safe us some typing
515+ typedef std::shared_ptr<Command> Ptr;
516+
517+ /// @brief FlagsMissing is thrown if at least one required flag is missing.
518+ struct FlagsMissing : public std::runtime_error {
519+ /// @brief FlagsMissing initializes a new instance.
520+ FlagsMissing();
521+ };
522+
523+ /// @brief FlagsWithWrongValue is thrown if a value passed on the command line
524+ /// is invalid.
525+ struct FlagsWithInvalidValue : public std::runtime_error {
526+ /// @brief FlagsWithInvalidValue initializes a new instance.
527+ FlagsWithInvalidValue();
528+ };
529+
530+ /// @brief Context bundles information passed to Command::run invocations.
531+ struct Context {
532+ std::istream& cin; ///< The std::istream that should be used for reading.
533+ std::ostream& cout; ///< The std::ostream that should be used for writing.
534+ std::vector<std::string> args; ///< The command line args.
535+ };
536+
537+ /// @brief name returns the Name of the command.
538+ virtual Name name() const;
539+
540+ /// @brief usage returns a short usage string for the command.
541+ virtual Usage usage() const;
542+
543+ /// @brief description returns a longer string explaining the command.
544+ virtual Description description() const;
545+
546+ /// @brief run puts the command to execution.
547+ virtual int run(const Context& context) = 0;
548+
549+ /// @brief help prints information about a command to out.
550+ virtual void help(std::ostream& out) = 0;
551+
552+ protected:
553+ /// @brief Command initializes a new instance with the given name, usage and
554+ /// description.
555+ Command(const Name& name, const Usage& usage, const Description& description);
556+
557+ /// @brief name adjusts the name of the command to n.
558+ // virtual void name(const Name& n);
559+ /// @brief usage adjusts the usage string of the comand to u.
560+ // virtual void usage(const Usage& u);
561+ /// @brief description adjusts the description string of the command to d.
562+ // virtual void description(const Description& d);
563+
564+ private:
565+ Name name_;
566+ Usage usage_;
567+ Description description_;
568+};
569+
570+/// @brief CommandWithSubcommands implements Command, selecting one of a set of
571+/// actions.
572+class CommandWithSubcommands : public Command {
573+ public:
574+ typedef std::shared_ptr<CommandWithSubcommands> Ptr;
575+ typedef std::function<int(const Context&)> Action;
576+
577+ /// @brief CommandWithSubcommands initializes a new instance with the given
578+ /// name, usage and description
579+ CommandWithSubcommands(const Name& name, const Usage& usage,
580+ const Description& description);
581+
582+ /// @brief command adds the given command to the set of known commands.
583+ CommandWithSubcommands& command(const Command::Ptr& command);
584+
585+ /// @brief flag adds the given flag to the set of known flags.
586+ CommandWithSubcommands& flag(const Flag::Ptr& flag);
587+
588+ // From Command
589+ int run(const Context& context) override;
590+ void help(std::ostream& out) override;
591+
592+ private:
593+ std::unordered_map<std::string, Command::Ptr> commands_;
594+ std::set<Flag::Ptr> flags_;
595+};
596+
597+/// @brief CommandWithFlagsAction implements Command, executing an Action after
598+/// handling
599+class CommandWithFlagsAndAction : public Command {
600+ public:
601+ typedef std::shared_ptr<CommandWithFlagsAndAction> Ptr;
602+ typedef std::function<int(const Context&)> Action;
603+
604+ /// @brief CommandWithFlagsAndAction initializes a new instance with the given
605+ /// name, usage and description
606+ CommandWithFlagsAndAction(const Name& name, const Usage& usage,
607+ const Description& description);
608+
609+ /// @brief flag adds the given flag to the set of known flags.
610+ CommandWithFlagsAndAction& flag(const Flag::Ptr& flag);
611+
612+ /// @brief action installs the given action.
613+ CommandWithFlagsAndAction& action(const Action& action);
614+
615+ // From Command
616+ int run(const Context& context) override;
617+ void help(std::ostream& out) override;
618+
619+ private:
620+ std::set<Flag::Ptr> flags_;
621+ Action action_;
622+};
623+
624+namespace cmd {
625+/// @brief HelpFor prints a help message for the given command on execution.
626+class Help : public Command {
627+ public:
628+ /// @brief HelpFor initializes a new instance with the given reference to a
629+ /// cmd.
630+ explicit Help(Command& cmd);
631+
632+ // From Command
633+ int run(const Context& context) override;
634+ void help(std::ostream& out) override;
635+
636+ private:
637+ /// @cond
638+ Command& command;
639+ /// @endcond
640+};
641+}
642+
643+/// @brief args returns a vector of strings assembled from argc and argv.
644+std::vector<std::string> args(int argc, char** argv);
645+
646+/// @brief make_flag returns a flag with the given name and description.
647+template <typename T>
648+typename TypedFlag<T>::Ptr make_flag(const Name& name,
649+ const Description& description) {
650+ return std::make_shared<TypedFlag<T>>(name, description);
651+}
652+
653+/// @brief make_flag returns a flag with the given name and description,
654+/// notifying updates to value.
655+template <typename T>
656+typename TypedReferenceFlag<T>::Ptr make_flag(const Name& name,
657+ const Description& desc,
658+ T& value) {
659+ return std::make_shared<TypedReferenceFlag<T>>(name, desc, value);
660+}
661+
662+/// @brief make_flag returns a flag with the given name and description,
663+/// updating the given optional value.
664+template <typename T>
665+typename OptionalTypedReferenceFlag<T>::Ptr make_flag(const Name& name,
666+ const Description& desc,
667+ Optional<T>& value) {
668+ return std::make_shared<OptionalTypedReferenceFlag<T>>(name, desc, value);
669+}
670+
671+} // namespace cli
672+} // namespace ac
673+
674+#endif
675diff --git a/src/ac/client/application.cpp b/src/ac/client/application.cpp
676new file mode 100644
677index 0000000..9fef06b
678--- /dev/null
679+++ b/src/ac/client/application.cpp
680@@ -0,0 +1,541 @@
681+/*
682+ * Copyright (C) 2015 Canonical, Ltd.
683+ *
684+ * This program is free software; you can redistribute it and/or modify
685+ * it under the terms of the GNU General Public License as published by
686+ * the Free Software Foundation; either version 2 of the License, or
687+ * (at your option) any later version.
688+ *
689+ * This program is distributed in the hope that it will be useful,
690+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
691+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
692+ * GNU General Public License for more details.
693+ *
694+ * You should have received a copy of the GNU General Public License
695+ * along with this program; if not, write to the Free Software
696+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
697+ *
698+ */
699+
700+#include <memory>
701+#include <functional>
702+#include <iostream>
703+#include <string>
704+#include <sstream>
705+
706+#include <readline/readline.h>
707+#include <readline/history.h>
708+
709+#include "ac/glib_wrapper.h"
710+
711+#include "application.h"
712+
713+using namespace std::placeholders;
714+
715+namespace {
716+const unsigned int kDBusTimeout = 120;
717+
718+class PromptSaver {
719+public:
720+ PromptSaver() {
721+ save_input_ = !RL_ISSTATE(RL_STATE_DONE);
722+ if (save_input_) {
723+ saved_point_ = rl_point;
724+ saved_line_ = rl_copy_text(0, rl_end);
725+ rl_save_prompt();
726+ rl_replace_line("", 0);
727+ rl_redisplay();
728+ }
729+ }
730+
731+ ~PromptSaver() {
732+ if (save_input_) {
733+ rl_restore_prompt();
734+ rl_replace_line(saved_line_, 0);
735+ rl_point = saved_point_;
736+ rl_redisplay();
737+ g_free(saved_line_);
738+ }
739+ }
740+
741+private:
742+ bool save_input_;
743+ int saved_point_;
744+ char *saved_line_;
745+};
746+}
747+
748+namespace ac {
749+namespace client {
750+
751+GMainLoop *Application::main_loop_ = nullptr;
752+std::map<std::string,Application::Command> Application::available_commands_ = { };
753+
754+int Application::Main(const MainOptions &options) {
755+ Application app;
756+ return app.Run();
757+}
758+
759+Application::Application() :
760+ bus_connection_(nullptr),
761+ manager_(nullptr),
762+ input_source_(0),
763+ object_manager_(nullptr) {
764+
765+ rl_callback_handler_install("aethercastctl> ", &Application::OnReadlineMessage);
766+
767+ main_loop_ = g_main_loop_new(nullptr, FALSE);
768+
769+ GError *error = nullptr;
770+ bus_connection_ = g_bus_get_sync(G_BUS_TYPE_SYSTEM, NULL, &error);
771+ if (!bus_connection_) {
772+ std::cerr << "Failed to connect to system bus: " << error->message << std::endl;
773+ g_error_free(error);
774+ return;
775+ }
776+
777+ g_bus_watch_name_on_connection(bus_connection_, "org.aethercast",
778+ G_BUS_NAME_WATCHER_FLAGS_NONE,
779+ &Application::OnServiceFound,
780+ &Application::OnServiceLost,
781+ this, nullptr);
782+
783+ g_dbus_connection_signal_subscribe(bus_connection_, "org.aethercast", "org.freedesktop.DBus.Properties", "PropertiesChanged", "/org/aethercast", nullptr,
784+ G_DBUS_SIGNAL_FLAGS_NONE, &Application::OnManagerPropertiesChanged, this, nullptr);
785+
786+ RegisterCommand(Command { "enable", "", "Enable manager", std::bind(&Application::HandleEnableCommand, this, _1) });
787+ RegisterCommand(Command { "disable", "", "Disable manager", std::bind(&Application::HandleDisableCommand, this, _1) });
788+ RegisterCommand(Command { "show", "", "Show manager properties", std::bind(&Application::HandleShowCommand, this, _1) });
789+ RegisterCommand(Command { "scan", "", "Scan for devices", std::bind(&Application::HandleScanCommand, this, _1) });
790+ RegisterCommand(Command { "devices", "", "List available devices", std::bind(&Application::HandleDevicesCommand, this, _1) });
791+ RegisterCommand(Command { "info", "<address>", "Show device information", std::bind(&Application::HandleInfoCommand, this, _1) });
792+ RegisterCommand(Command { "connect", "<address>", "Connect a device", std::bind(&Application::HandleConnectCommand, this, _1) });
793+ RegisterCommand(Command { "disconnect", "<address>", "Disconnect a device", std::bind(&Application::HandleDisconnectCommand, this, _1) });
794+}
795+
796+Application::~Application() {
797+ rl_message();
798+ rl_callback_handler_remove();
799+
800+ g_object_unref(bus_connection_);
801+ g_main_loop_unref(main_loop_);
802+
803+ if (manager_)
804+ g_object_unref(manager_);
805+
806+ if (input_source_ > 0)
807+ g_source_remove(input_source_);
808+}
809+
810+int Application::Run() {
811+ g_main_loop_run(main_loop_);
812+ return 0;
813+}
814+
815+void Application::RegisterCommand(const Command &command) {
816+ available_commands_[command.name] = command;
817+}
818+
819+void Application::HandleEnableCommand(const std::string &arguments) {
820+ if (!manager_)
821+ return;
822+
823+ aethercast_interface_manager_set_enabled(manager_, true);
824+}
825+
826+void Application::HandleDisableCommand(const std::string &arguments) {
827+ if (!manager_)
828+ return;
829+
830+ aethercast_interface_manager_set_enabled(manager_, false);
831+}
832+
833+void Application::HandleShowCommand(const std::string &arguments) {
834+ if (!manager_)
835+ return;
836+
837+ auto enabled = aethercast_interface_manager_get_enabled(manager_);
838+ std::cout << "Enabled: " << std::boolalpha << (bool) enabled << std::endl;
839+
840+ auto state = aethercast_interface_manager_get_state(manager_);
841+ std::cout << "State: " << state << std::endl;
842+
843+ auto scanning = aethercast_interface_manager_get_scanning(manager_);
844+ std::cout << "Scanning: " << std::boolalpha << (bool) scanning << std::endl;
845+
846+ auto capabilities = aethercast_interface_manager_get_capabilities(manager_);
847+ std::cout << "Capabilities:" << std::endl;
848+ for (int n = 0; capabilities[n] != nullptr; n++)
849+ std::cout << " " << capabilities[n] << std::endl;
850+}
851+
852+void Application::OnScanDone(GObject *object, GAsyncResult *res, gpointer user_data) {
853+ auto inst = static_cast<Application*>(user_data);
854+
855+ GError *error = nullptr;
856+ if (!aethercast_interface_manager_call_scan_finish(inst->manager_, res, &error)) {
857+ std::cerr << "Failed to scan:" << error->message << std::endl;
858+ g_error_free(error);
859+ return;
860+ }
861+}
862+
863+void Application::HandleScanCommand(const std::string &arguments) {
864+ if (!manager_)
865+ return;
866+
867+ aethercast_interface_manager_call_scan(manager_, nullptr, &Application::OnScanDone, this);
868+}
869+
870+void Application::ForeachDevice(std::function<void(AethercastInterfaceDevice*)> callback, const std::string &address_filter) {
871+ if (!callback)
872+ return;
873+
874+ auto objects = g_dbus_object_manager_get_objects(object_manager_);
875+
876+ for (auto obj = objects; obj != nullptr; obj = obj->next) {
877+ auto device = AETHERCAST_INTERFACE_OBJECT_PROXY(obj->data);
878+ if (!device)
879+ continue;
880+
881+ auto iface = g_dbus_object_get_interface(G_DBUS_OBJECT(device), "org.aethercast.Device");
882+ if (!iface)
883+ continue;
884+
885+ auto device_obj = AETHERCAST_INTERFACE_DEVICE(iface);
886+
887+ if (address_filter.length() > 0) {
888+ auto device_address = aethercast_interface_device_get_address(device_obj);
889+ if (address_filter != device_address)
890+ continue;
891+ }
892+
893+ callback(device_obj);
894+ }
895+}
896+
897+void Application::HandleDevicesCommand(const std::string &arguments) {
898+ ForeachDevice([=](AethercastInterfaceDevice *device) {
899+ auto address = aethercast_interface_device_get_address(device);
900+ auto name = aethercast_interface_device_get_name(device);
901+ std::cout << "Device " << address << " " << name << std::endl;
902+ });
903+}
904+
905+void Application::HandleInfoCommand(const std::string &arguments) {
906+ if (arguments.length() == 0) {
907+ std::cerr << "No device address supplied" << std::endl;
908+ return;
909+ }
910+
911+ bool found = false;
912+
913+ ForeachDevice([&](AethercastInterfaceDevice *device) {
914+ auto address = aethercast_interface_device_get_address(device);
915+ std::cout << "Address: " << address << std::endl;
916+
917+ auto name = aethercast_interface_device_get_name(device);
918+ std::cout << "Name: " << name << std::endl;
919+
920+ auto state = aethercast_interface_device_get_state(device);
921+ std::cout << "State: " << state << std::endl;
922+
923+ auto capabilities = aethercast_interface_device_get_capabilities(device);
924+ std::cout << "Capabilities: " << std::endl;
925+ for (int n = 0; capabilities[n] != nullptr; n++)
926+ std::cout << " " << capabilities[n] << std::endl;
927+
928+ found = true;
929+ }, arguments);
930+
931+ if (!found)
932+ std::cerr << "Unknown or invalid device address" << std::endl;
933+}
934+
935+void Application::OnDeviceConnected(GObject *object, GAsyncResult *res, gpointer user_data) {
936+ PromptSaver ps;
937+
938+ GError *error = nullptr;
939+ if (!aethercast_interface_device_call_connect_finish(AETHERCAST_INTERFACE_DEVICE(object), res, &error)) {
940+ std::cerr << "Failed to connect with device: " << error->message << std::endl;
941+ g_error_free(error);
942+ return;
943+ }
944+}
945+
946+void Application::HandleConnectCommand(const std::string &arguments) {
947+ PromptSaver ps;
948+
949+ if (arguments.length() == 0) {
950+ std::cerr << "No device address supplied" << std::endl;
951+ return;
952+ }
953+
954+ bool found = false;
955+
956+ ForeachDevice([&](AethercastInterfaceDevice *device) {
957+
958+ aethercast_interface_device_call_connect(device, "sink", nullptr,
959+ &Application::OnDeviceConnected, nullptr);
960+
961+ found = true;
962+ }, arguments);
963+
964+ if (!found)
965+ std::cerr << "Unknown or invalid device address" << std::endl;
966+}
967+
968+void Application::OnDeviceDisconnected(GObject *object, GAsyncResult *res, gpointer user_data) {
969+ PromptSaver ps;
970+
971+ GError *error = nullptr;
972+ if (!aethercast_interface_device_call_disconnect_finish(AETHERCAST_INTERFACE_DEVICE(object), res, &error)) {
973+ std::cerr << "Failed to disconnect from device: " << error->message << std::endl;
974+ g_error_free(error);
975+ return;
976+ }
977+}
978+
979+void Application::HandleDisconnectCommand(const std::string &arguments) {
980+ PromptSaver ps;
981+
982+ if (arguments.length() == 0) {
983+ std::cerr << "No device address supplied" << std::endl;
984+ return;
985+ }
986+
987+ bool found = false;
988+
989+ ForeachDevice([&](AethercastInterfaceDevice *device) {
990+
991+ aethercast_interface_device_call_disconnect(device, nullptr,
992+ &Application::OnDeviceDisconnected, nullptr);
993+
994+ found = true;
995+ }, arguments);
996+
997+ if (!found)
998+ std::cerr << "Unknown or invalid device address" << std::endl;
999+}
1000+
1001+void Application::SetupStandardInput() {
1002+ GIOChannel *channel;
1003+
1004+ channel = g_io_channel_unix_new(fileno(stdin));
1005+
1006+ input_source_ = g_io_add_watch(channel,
1007+ (GIOCondition) (G_IO_IN | G_IO_HUP | G_IO_ERR | G_IO_NVAL),
1008+ &Application::OnUserInput, this);
1009+
1010+ g_io_channel_unref(channel);
1011+
1012+}
1013+
1014+gboolean Application::OnUserInput(GIOChannel *channel, GIOCondition condition, gpointer user_data) {
1015+ auto inst = static_cast<Application*>(user_data);
1016+
1017+ if (condition & G_IO_IN) {
1018+ rl_callback_read_char();
1019+ return TRUE;
1020+ }
1021+
1022+ if (condition & (G_IO_HUP | G_IO_ERR | G_IO_NVAL)) {
1023+ g_main_loop_quit(inst->main_loop_);
1024+ return FALSE;
1025+ }
1026+
1027+ return TRUE;
1028+}
1029+
1030+void Application::OnReadlineMessage(char *input) {
1031+ PromptSaver ps;
1032+
1033+ char *arg;
1034+ int i;
1035+
1036+ if (!input) {
1037+ rl_newline(1, '\n');
1038+ g_main_loop_quit(main_loop_);
1039+ return;
1040+ }
1041+
1042+ std::string cmd = strtok_r(input, " ", &arg) ? : "";
1043+ if (cmd.size() == 0)
1044+ return;
1045+
1046+ if (arg) {
1047+ int len = strlen(arg);
1048+ if (len > 0 && arg[len - 1] == ' ')
1049+ arg[len - 1] = '\0';
1050+ }
1051+
1052+ auto iter = available_commands_.find(cmd);
1053+ if (iter != available_commands_.end()) {
1054+ if (iter->second.func)
1055+ iter->second.func(arg);
1056+ }
1057+ else if (cmd == "help") {
1058+ std::cout << "Available commands:" << std::endl;
1059+
1060+ for (auto cmd : available_commands_) {
1061+ if (cmd.second.description.size() == 0)
1062+ continue;
1063+
1064+ fprintf(stdout, " %s %-*s %s\n", cmd.second.name.c_str(),
1065+ (int)(25 - cmd.second.name.length()),
1066+ cmd.second.arguments.c_str(),
1067+ cmd.second.description.c_str());
1068+ }
1069+ }
1070+ else {
1071+ std::cerr << "Invalid command" << std::endl;
1072+ }
1073+
1074+ g_free(input);
1075+}
1076+
1077+void Application::OnServiceLost(GDBusConnection *connection, const gchar *name, gpointer user_data) {
1078+ auto inst = static_cast<Application*>(user_data);
1079+
1080+ if (inst->input_source_ > 0) {
1081+ g_source_remove(inst->input_source_);
1082+ inst->input_source_ = 0;
1083+ }
1084+
1085+ if (inst->manager_) {
1086+ g_object_unref(inst->manager_);
1087+ inst->manager_ = nullptr;
1088+ }
1089+
1090+ if (inst->object_manager_) {
1091+ g_object_unref(inst->object_manager_);
1092+ inst->object_manager_ = nullptr;
1093+ }
1094+}
1095+
1096+void Application::OnServiceFound(GDBusConnection *connection, const gchar *name, const gchar *name_owner, gpointer user_data) {
1097+ auto inst = static_cast<Application*>(user_data);
1098+
1099+ aethercast_interface_manager_proxy_new(inst->bus_connection_, G_DBUS_PROXY_FLAGS_NONE,
1100+ "org.aethercast", "/org/aethercast", nullptr,
1101+ &Application::OnManagerConnected, inst);
1102+}
1103+
1104+void Application::OnManagerConnected(GObject *object, GAsyncResult *res, gpointer user_data) {
1105+ PromptSaver ps;
1106+
1107+ auto inst = static_cast<Application*>(user_data);
1108+
1109+ GError *error = nullptr;
1110+ inst->manager_ = aethercast_interface_manager_proxy_new_finish(res, &error);
1111+ if (!inst->manager_) {
1112+ std::cerr << "Could not connect with manager: " << error->message << std::endl;
1113+ g_error_free(error);
1114+ g_main_loop_quit(inst->main_loop_);
1115+ return;
1116+ }
1117+
1118+ // Use a high enough timeout to make sure we wait enough for the service to
1119+ // respond as the connection process can take some time depending on different
1120+ // factors.
1121+ g_dbus_proxy_set_default_timeout(G_DBUS_PROXY(inst->manager_), kDBusTimeout * 1000);
1122+
1123+ aethercast_interface_object_manager_client_new(inst->bus_connection_,
1124+ G_DBUS_OBJECT_MANAGER_CLIENT_FLAGS_NONE,
1125+ "org.aethercast", "/org/aethercast",
1126+ nullptr, &Application::OnObjectManagerCreated, inst);
1127+}
1128+
1129+void Application::OnManagerPropertiesChanged(GDBusConnection *connection, const gchar *sender_name, const gchar *object_path,
1130+ const gchar *interface_name, const gchar *signal_name, GVariant *parameters,
1131+ gpointer user_data) {
1132+ PromptSaver ps;
1133+
1134+ if (g_variant_n_children(parameters) != 3)
1135+ return;
1136+
1137+ GVariant *changed_props = g_variant_get_child_value(parameters, 1);
1138+ for (int n = 0; n < g_variant_n_children(changed_props); n++) {
1139+ auto element = g_variant_get_child_value(changed_props, n);
1140+ auto key_v = g_variant_get_child_value(element, 0);
1141+ auto value_v = g_variant_get_child_value(element, 1);
1142+
1143+ auto key = std::string(g_variant_get_string(key_v, nullptr));
1144+
1145+ std::cout << "[CHG] Manager " << key << " changed: ";
1146+
1147+ if (key == "Enabled")
1148+ std::cout << std::boolalpha << (bool) g_variant_get_boolean(g_variant_get_variant(value_v)) << std::endl;
1149+ else if (key == "State")
1150+ std::cout << g_variant_get_string(g_variant_get_variant(value_v), nullptr) << std::endl;
1151+ else if (key == "Scanning")
1152+ std::cout << std::boolalpha << (bool) g_variant_get_boolean(g_variant_get_variant(value_v)) << std::endl;
1153+ else if (key == "Capabilities") {
1154+ std::stringstream capabilities;
1155+
1156+ for (int m = 0; m < g_variant_n_children(value_v); m++) {
1157+ auto capability = g_variant_get_child_value(value_v, m);
1158+ if (!g_variant_is_of_type(capability, G_VARIANT_TYPE_STRING))
1159+ continue;
1160+
1161+ capabilities << g_variant_get_string(capability, nullptr) << " ";
1162+ }
1163+
1164+ std::cout << capabilities.str() << std::endl;
1165+ }
1166+ else
1167+ std::cout << "unknown" << std::endl;
1168+
1169+ g_variant_unref(key_v);
1170+ g_variant_unref(value_v);
1171+ }
1172+}
1173+
1174+void Application::OnDeviceAdded(GDBusObjectManager *manager, GDBusObject *object, gpointer user_data) {
1175+ PromptSaver ps;
1176+
1177+ auto iface = g_dbus_object_get_interface(G_DBUS_OBJECT(object), "org.aethercast.Device");
1178+ if (!iface)
1179+ return;
1180+
1181+ auto device = AETHERCAST_INTERFACE_DEVICE(iface);
1182+
1183+ auto address = aethercast_interface_device_get_address(device);
1184+ auto name = aethercast_interface_device_get_name(device);
1185+
1186+ std::cout << "Device " << address << " " << name << " added" << std::endl;
1187+}
1188+
1189+void Application::OnDeviceRemoved(GDBusObjectManager *manager, GDBusObject *object, gpointer user_data) {
1190+ PromptSaver ps;
1191+
1192+ auto iface = g_dbus_object_get_interface(G_DBUS_OBJECT(object), "org.aethercast.Device");
1193+ if (!iface)
1194+ return;
1195+
1196+ auto device = AETHERCAST_INTERFACE_DEVICE(iface);
1197+
1198+ auto address = aethercast_interface_device_get_address(device);
1199+ auto name = aethercast_interface_device_get_name(device);
1200+
1201+ std::cout << "Device " << address << " " << name <<" removed" << std::endl;
1202+}
1203+
1204+void Application::OnObjectManagerCreated(GObject *object, GAsyncResult *res, gpointer user_data) {
1205+ auto inst = static_cast<Application*>(user_data);
1206+
1207+ GError *error = nullptr;
1208+ inst->object_manager_ = aethercast_interface_object_manager_client_new_finish(res, &error);
1209+ if (!inst->object_manager_) {
1210+ g_error_free(error);
1211+ return;
1212+ }
1213+
1214+ g_signal_connect(inst->object_manager_, "object-added", G_CALLBACK(&Application::OnDeviceAdded), inst);
1215+ g_signal_connect(inst->object_manager_, "object-removed", G_CALLBACK(&Application::OnDeviceRemoved), inst);
1216+
1217+ inst->SetupStandardInput();
1218+}
1219+
1220+} // namespace client
1221+} // namespace ac
1222diff --git a/src/ac/client/application.h b/src/ac/client/application.h
1223new file mode 100644
1224index 0000000..a7b3d5a
1225--- /dev/null
1226+++ b/src/ac/client/application.h
1227@@ -0,0 +1,112 @@
1228+/*
1229+ * Copyright (C) 2015 Canonical, Ltd.
1230+ *
1231+ * This program is free software; you can redistribute it and/or modify
1232+ * it under the terms of the GNU General Public License as published by
1233+ * the Free Software Foundation; either version 2 of the License, or
1234+ * (at your option) any later version.
1235+ *
1236+ * This program is distributed in the hope that it will be useful,
1237+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1238+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1239+ * GNU General Public License for more details.
1240+ *
1241+ * You should have received a copy of the GNU General Public License
1242+ * along with this program; if not, write to the Free Software
1243+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
1244+ *
1245+ */
1246+
1247+#ifndef APPLICATION_H_
1248+#define APPLICATION_H_
1249+
1250+#include <map>
1251+#include <functional>
1252+
1253+#include "ac/glib_wrapper.h"
1254+
1255+extern "C" {
1256+// Ignore all warnings coming from the external headers as we don't
1257+// control them and also don't want to get any warnings from them
1258+// which will only pollute our build output.
1259+#pragma GCC diagnostic push
1260+#pragma GCC diagnostic warning "-w"
1261+#include "aethercastinterface.h"
1262+#pragma GCC diagnostic pop
1263+}
1264+
1265+namespace ac {
1266+namespace client {
1267+
1268+class Application {
1269+public:
1270+ struct MainOptions {};
1271+
1272+ static int Main(const MainOptions &options);
1273+
1274+ Application();
1275+ ~Application();
1276+
1277+ int Run();
1278+
1279+private:
1280+ struct Command {
1281+ std::string name;
1282+ std::string arguments;
1283+ std::string description;
1284+ std::function<void(std::string)> func;
1285+ };
1286+
1287+ void HandleEnableCommand(const std::string &arguments);
1288+ void HandleDisableCommand(const std::string &arguments);
1289+ void HandleShowCommand(const std::string &arguments);
1290+ void HandleScanCommand(const std::string &arguments);
1291+ void HandleDevicesCommand(const std::string &arguments);
1292+ void HandleInfoCommand(const std::string &arguments);
1293+ void HandleConnectCommand(const std::string &arguments);
1294+ void HandleDisconnectCommand(const std::string &arguments);
1295+
1296+ void RegisterCommand(const Command &command);
1297+
1298+private:
1299+ static gboolean OnUserInput(GIOChannel *channel, GIOCondition condition, gpointer user_data);
1300+ static void OnReadlineMessage(char *input);
1301+ static void OnServiceLost(GDBusConnection *connection, const gchar *name, gpointer user_data);
1302+ static void OnServiceFound(GDBusConnection *connection, const gchar *name, const gchar *name_owner, gpointer user_data);
1303+ static void OnManagerConnected(GObject *object, GAsyncResult *res, gpointer user_data);
1304+ static void OnDeviceAdded(GDBusObjectManager *manager, GDBusObject *object, gpointer user_data);
1305+ static void OnDeviceRemoved(GDBusObjectManager *manager, GDBusObject *object, gpointer user_data);
1306+ static void OnObjectManagerCreated(GObject *object, GAsyncResult *res, gpointer user_data);
1307+
1308+ static void OnManagerPropertiesChanged(GDBusConnection *connection,
1309+ const gchar *sender_name,
1310+ const gchar *object_path,
1311+ const gchar *interface_name,
1312+ const gchar *signal_name,
1313+ GVariant *parameters,
1314+ gpointer user_data);
1315+
1316+ static void OnScanDone(GObject *object, GAsyncResult *res, gpointer user_data);
1317+ static void OnDeviceConnected(GObject *object, GAsyncResult *res, gpointer user_data);
1318+ static void OnDeviceDisconnected(GObject *object, GAsyncResult *res, gpointer user_data);
1319+private:
1320+ void SetupStandardInput();
1321+ void ForeachDevice(std::function<void(AethercastInterfaceDevice*)> callback, const std::string &address_filter = "");
1322+
1323+private:
1324+ GDBusConnection *bus_connection_;
1325+ AethercastInterfaceManager *manager_;
1326+ guint input_source_;
1327+ GDBusObjectManager *object_manager_;
1328+
1329+private:
1330+ // Needs to be static otherwise we can't access it from the readline
1331+ // callback function (which sadly doesn't allow passing any user data)
1332+ static GMainLoop *main_loop_;
1333+ static std::map<std::string,Command> available_commands_;
1334+};
1335+
1336+} // namespace client
1337+} // namespace ac
1338+
1339+#endif
1340diff --git a/src/ac/cmds/service.cpp b/src/ac/cmds/service.cpp
1341new file mode 100644
1342index 0000000..36c146e
1343--- /dev/null
1344+++ b/src/ac/cmds/service.cpp
1345@@ -0,0 +1,28 @@
1346+/*
1347+ * Copyright (C) 2017 Simon Fels <morphis@gravedo.de>
1348+ *
1349+ * This program is free software; you can redistribute it and/or modify
1350+ * it under the terms of the GNU Lesser General Public License as published by
1351+ * the Free Software Foundation; version 3.
1352+ *
1353+ * This program is distributed in the hope that it will be useful,
1354+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1355+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1356+ * GNU Lesser General Public License for more details.
1357+ *
1358+ * You should have received a copy of the GNU Lesser General Public License
1359+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1360+ *
1361+ */
1362+
1363+#include "ac/cmds/service.h"
1364+#include "ac/service.h"
1365+
1366+ac::cmds::Service::Service()
1367+ : CommandWithFlagsAndAction{
1368+ cli::Name{"service"}, cli::Usage{"service"},
1369+ cli::Description{"Start the background service"}} {
1370+ action([](const cli::Command::Context&) {
1371+ return ac::Service::Main(ac::Service::MainOptions{});
1372+ });
1373+}
1374diff --git a/src/ac/cmds/service.h b/src/ac/cmds/service.h
1375new file mode 100644
1376index 0000000..541a868
1377--- /dev/null
1378+++ b/src/ac/cmds/service.h
1379@@ -0,0 +1,32 @@
1380+/*
1381+ * Copyright (C) 2017 Canonical, Ltd.
1382+ *
1383+ * This program is free software; you can redistribute it and/or modify
1384+ * it under the terms of the GNU Lesser General Public License as published by
1385+ * the Free Software Foundation; version 3.
1386+ *
1387+ * This program is distributed in the hope that it will be useful,
1388+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1389+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1390+ * GNU Lesser General Public License for more details.
1391+ *
1392+ * You should have received a copy of the GNU Lesser General Public License
1393+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1394+ *
1395+ */
1396+
1397+#ifndef AETHERCAST_CMDS_SERVICE_H_
1398+#define AETHERCAST_CMDS_SERVICE_H_
1399+
1400+#include "ac/cli.h"
1401+
1402+namespace ac {
1403+namespace cmds {
1404+class Service : public cli::CommandWithFlagsAndAction {
1405+public:
1406+ Service();
1407+};
1408+} // namespace cmds
1409+} // namespace ac
1410+
1411+#endif
1412diff --git a/src/ac/cmds/shell.cpp b/src/ac/cmds/shell.cpp
1413new file mode 100644
1414index 0000000..b3e1127
1415--- /dev/null
1416+++ b/src/ac/cmds/shell.cpp
1417@@ -0,0 +1,28 @@
1418+/*
1419+ * Copyright (C) 2017 Canonical, Ltd.
1420+ *
1421+ * This program is free software; you can redistribute it and/or modify
1422+ * it under the terms of the GNU Lesser General Public License as published by
1423+ * the Free Software Foundation; version 3.
1424+ *
1425+ * This program is distributed in the hope that it will be useful,
1426+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1427+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1428+ * GNU Lesser General Public License for more details.
1429+ *
1430+ * You should have received a copy of the GNU Lesser General Public License
1431+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1432+ *
1433+ */
1434+
1435+#include "ac/cmds/shell.h"
1436+#include "ac/client/application.h"
1437+
1438+ac::cmds::Shell::Shell()
1439+ : CommandWithFlagsAndAction{
1440+ cli::Name{"shell"}, cli::Usage{"shell"},
1441+ cli::Description{"Start the interactive shell"}} {
1442+ action([](const cli::Command::Context&) {
1443+ return ac::client::Application::Main(ac::client::Application::MainOptions{});
1444+ });
1445+}
1446diff --git a/src/ac/cmds/shell.h b/src/ac/cmds/shell.h
1447new file mode 100644
1448index 0000000..a4d905b
1449--- /dev/null
1450+++ b/src/ac/cmds/shell.h
1451@@ -0,0 +1,32 @@
1452+/*
1453+ * Copyright (C) 2017 Canonical, Ltd.
1454+ *
1455+ * This program is free software; you can redistribute it and/or modify
1456+ * it under the terms of the GNU Lesser General Public License as published by
1457+ * the Free Software Foundation; version 3.
1458+ *
1459+ * This program is distributed in the hope that it will be useful,
1460+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1461+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1462+ * GNU Lesser General Public License for more details.
1463+ *
1464+ * You should have received a copy of the GNU Lesser General Public License
1465+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1466+ *
1467+ */
1468+
1469+#ifndef AETHERCAST_CMDS_SHELL_H_
1470+#define AETHERCAST_CMDS_SHELL_H_
1471+
1472+#include "ac/cli.h"
1473+
1474+namespace ac {
1475+namespace cmds {
1476+class Shell : public cli::CommandWithFlagsAndAction {
1477+public:
1478+ Shell();
1479+};
1480+} // namespace cmds
1481+} // namespace ac
1482+
1483+#endif
1484diff --git a/src/ac/do_not_copy_or_move.h b/src/ac/do_not_copy_or_move.h
1485new file mode 100644
1486index 0000000..944c649
1487--- /dev/null
1488+++ b/src/ac/do_not_copy_or_move.h
1489@@ -0,0 +1,36 @@
1490+/*
1491+ * Copyright (C) 2017 Canonical, Ltd.
1492+ *
1493+ * This program is free software: you can redistribute it and/or modify it
1494+ * under the terms of the GNU General Public License version 3, as published
1495+ * by the Free Software Foundation.
1496+ *
1497+ * This program is distributed in the hope that it will be useful, but
1498+ * WITHOUT ANY WARRANTY; without even the implied warranties of
1499+ * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
1500+ * PURPOSE. See the GNU General Public License for more details.
1501+ *
1502+ * You should have received a copy of the GNU General Public License along
1503+ * with this program. If not, see <http://www.gnu.org/licenses/>.
1504+ *
1505+ */
1506+
1507+#ifndef AETHERCAST_DO_NOT_COPY_OR_MOVE_H_
1508+#define AETHERCAST_DO_NOT_COPY_OR_MOVE_H_
1509+
1510+namespace ac {
1511+
1512+class DoNotCopyOrMove {
1513+ public:
1514+ DoNotCopyOrMove(const DoNotCopyOrMove&) = delete;
1515+ DoNotCopyOrMove(DoNotCopyOrMove&&) = delete;
1516+ virtual ~DoNotCopyOrMove() = default;
1517+ DoNotCopyOrMove& operator=(const DoNotCopyOrMove&) = delete;
1518+ DoNotCopyOrMove& operator=(DoNotCopyOrMove&&) = delete;
1519+
1520+ protected:
1521+ DoNotCopyOrMove() = default;
1522+};
1523+}
1524+
1525+#endif
1526diff --git a/src/ac/main.cpp b/src/ac/main.cpp
1527index 8fa7842..7fa1990 100644
1528--- a/src/ac/main.cpp
1529+++ b/src/ac/main.cpp
1530@@ -15,8 +15,21 @@
1531 *
1532 */
1533
1534-#include "service.h"
1535+#include "ac/cli.h"
1536+#include "ac/utils.h"
1537+#include "ac/cmds/shell.h"
1538+#include "ac/cmds/service.h"
1539
1540 int main(int argc, char **argv) {
1541- return ac::Service::Main(ac::Service::MainOptions::FromCommandLine(argc, argv));
1542+ ac::cli::CommandWithSubcommands cmd{ac::cli::Name{"aethercast"},
1543+ ac::cli::Usage{"aethercast"},
1544+ ac::cli::Description{"The Display Casting Service"}};
1545+ cmd.command(std::make_shared<ac::cmds::Shell>());
1546+ cmd.command(std::make_shared<ac::cmds::Service>());
1547+
1548+ auto arguments = ac::Utils::CollectArguments(argc, argv);
1549+ if (arguments.size() == 0)
1550+ arguments = {"shell"};
1551+
1552+ return cmd.run({std::cin, std::cout, arguments});
1553 }
1554diff --git a/src/ac/optional.h b/src/ac/optional.h
1555new file mode 100644
1556index 0000000..56b0e55
1557--- /dev/null
1558+++ b/src/ac/optional.h
1559@@ -0,0 +1,31 @@
1560+/*
1561+ * Copyright (C) 2016 Canonical, Ltd.
1562+ *
1563+ * This program is free software; you can redistribute it and/or modify
1564+ * it under the terms of the GNU Lesser General Public License as published by
1565+ * the Free Software Foundation; version 3.
1566+ *
1567+ * This program is distributed in the hope that it will be useful,
1568+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1569+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1570+ * GNU Lesser General Public License for more details.
1571+ *
1572+ * You should have received a copy of the GNU Lesser General Public License
1573+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1574+ *
1575+ * Authored by: Thomas Voß <thomas.voss@canonical.com>
1576+ *
1577+ */
1578+
1579+#ifndef AETHERCAST_OPTIONAL_H_
1580+#define AETHERCAST_OPTIONAL_H_
1581+
1582+#include <boost/optional.hpp>
1583+#include <boost/optional/optional_io.hpp>
1584+
1585+namespace ac {
1586+template <typename T>
1587+using Optional = boost::optional<T>;
1588+}
1589+
1590+#endif
1591diff --git a/src/ac/service.cpp b/src/ac/service.cpp
1592index 2293560..76b5494 100644
1593--- a/src/ac/service.cpp
1594+++ b/src/ac/service.cpp
1595@@ -62,33 +62,6 @@ void SafeLog (const char *format, ...)
1596 }
1597 }
1598 namespace ac {
1599-Service::MainOptions Service::MainOptions::FromCommandLine(int argc, char** argv) {
1600- static gboolean option_debug{FALSE};
1601- static gboolean option_version{FALSE};
1602-
1603- static GOptionEntry options[] = {
1604- { "debug", 'd', 0, G_OPTION_ARG_NONE, &option_debug, "Enable debugging mode", nullptr },
1605- { "version", 'v', 0, G_OPTION_ARG_NONE, &option_version, "Show version information and exit", nullptr },
1606- { NULL },
1607- };
1608-
1609- std::shared_ptr<GOptionContext> context{g_option_context_new(nullptr), [](GOptionContext *ctxt) { g_option_context_free(ctxt); }};
1610- GError *error = nullptr;
1611-
1612- g_option_context_add_main_entries(context.get(), options, NULL);
1613-
1614- if (!g_option_context_parse(context.get(), &argc, &argv, &error)) {
1615- if (error) {
1616- g_printerr("%s\n", error->message);
1617- g_error_free(error);
1618- } else
1619- g_printerr("An unknown error occurred\n");
1620- exit(1);
1621- }
1622-
1623- return MainOptions{option_debug == TRUE, option_version == TRUE};
1624-}
1625-
1626 int Service::Main(const Service::MainOptions &options) {
1627 if (options.print_version) {
1628 std::printf("%d.%d\n", Service::kVersionMajor, Service::kVersionMinor);
1629diff --git a/src/ac/service.h b/src/ac/service.h
1630index 94ecb4c..fd23d16 100644
1631--- a/src/ac/service.h
1632+++ b/src/ac/service.h
1633@@ -46,8 +46,6 @@ public:
1634 typedef std::shared_ptr<Service> Ptr;
1635
1636 struct MainOptions {
1637- static MainOptions FromCommandLine(int argc, char** argv);
1638-
1639 bool debug;
1640 bool print_version;
1641 };
1642diff --git a/src/ac/utils.cpp b/src/ac/utils.cpp
1643index c89c3a9..0bf497c 100644
1644--- a/src/ac/utils.cpp
1645+++ b/src/ac/utils.cpp
1646@@ -30,6 +30,12 @@
1647 #include "utils.h"
1648
1649 namespace ac {
1650+std::vector<std::string> Utils::CollectArguments(int argc, char **argv) {
1651+ std::vector<std::string> result;
1652+ for (int i = 1; i < argc; i++) result.push_back(argv[i]);
1653+ return result;
1654+}
1655+
1656 bool Utils::StringStartsWith(const std::string &text, const std::string &prefix) {
1657 return text.compare(0, prefix.size(), prefix) == 0;
1658 }
1659diff --git a/src/ac/utils.h b/src/ac/utils.h
1660index 847d7d2..d649e8f 100644
1661--- a/src/ac/utils.h
1662+++ b/src/ac/utils.h
1663@@ -33,6 +33,8 @@ struct Utils
1664 // Merely used as a namespace.
1665 Utils() = delete;
1666
1667+ // Convert plain array into std::vector with strings
1668+ static std::vector<std::string> CollectArguments(int argc, char **argv);
1669 // StringStartsWith returns true iff text[0:prefix.size()-1] == prefix.
1670 static bool StringStartsWith(const std::string &text, const std::string &prefix);
1671 // ParseHex parses an integer with base 16 from str.
1672diff --git a/src/client/CMakeLists.txt b/src/client/CMakeLists.txt
1673deleted file mode 100644
1674index 2d8588e..0000000
1675--- a/src/client/CMakeLists.txt
1676+++ /dev/null
1677@@ -1,36 +0,0 @@
1678-set(SOURCES
1679- main.cpp
1680- application.cpp
1681-)
1682-
1683-link_directories(
1684- ${GLIB_LIBRARY_DIRS}
1685- ${GIO_LIBRARY_DIRS}
1686- ${GIO-UNIX_LIBRARY_DIRS}
1687-)
1688-
1689-include_directories(
1690- ${Boost_INCLUDE_DIRS}
1691- ${GLIB_INCLUDE_DIRS}
1692- ${GIO_INCLUDE_DIRS}
1693- ${GIO-UNIX_INCLUDE_DIRS}
1694- ${READLINE_INCLUDE_DIRS}
1695- ${CMAKE_CURRENT_SOURCE_DIR}/../
1696- ${CMAKE_CURRENT_BINARY_DIR}/src
1697-)
1698-
1699-add_executable(aethercastctl ${SOURCES})
1700-target_link_libraries(aethercastctl
1701- aethercast-gdbus-wrapper
1702- ${Boost_LIBRARIES}
1703- ${GLIB_LIBRARIES}
1704- ${GIO_LIBRARIES}
1705- ${GIO-UNIX_LIBRARIES}
1706- ${READLINE_LIBRARIES}
1707- -ldl
1708-)
1709-
1710-install(
1711- TARGETS aethercastctl
1712- RUNTIME DESTINATION bin
1713-)
1714diff --git a/src/client/application.cpp b/src/client/application.cpp
1715deleted file mode 100644
1716index 5471586..0000000
1717--- a/src/client/application.cpp
1718+++ /dev/null
1719@@ -1,564 +0,0 @@
1720-/*
1721- * Copyright (C) 2015 Canonical, Ltd.
1722- *
1723- * This program is free software; you can redistribute it and/or modify
1724- * it under the terms of the GNU General Public License as published by
1725- * the Free Software Foundation; either version 2 of the License, or
1726- * (at your option) any later version.
1727- *
1728- * This program is distributed in the hope that it will be useful,
1729- * but WITHOUT ANY WARRANTY; without even the implied warranty of
1730- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1731- * GNU General Public License for more details.
1732- *
1733- * You should have received a copy of the GNU General Public License
1734- * along with this program; if not, write to the Free Software
1735- * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
1736- *
1737- */
1738-
1739-#include <memory>
1740-#include <functional>
1741-#include <iostream>
1742-#include <string>
1743-#include <sstream>
1744-
1745-#include <readline/readline.h>
1746-#include <readline/history.h>
1747-
1748-#include "ac/glib_wrapper.h"
1749-
1750-#include "application.h"
1751-
1752-using namespace std::placeholders;
1753-
1754-namespace {
1755-const unsigned int kDBusTimeout = 120;
1756-
1757-class PromptSaver {
1758-public:
1759- PromptSaver() {
1760- save_input_ = !RL_ISSTATE(RL_STATE_DONE);
1761- if (save_input_) {
1762- saved_point_ = rl_point;
1763- saved_line_ = rl_copy_text(0, rl_end);
1764- rl_save_prompt();
1765- rl_replace_line("", 0);
1766- rl_redisplay();
1767- }
1768- }
1769-
1770- ~PromptSaver() {
1771- if (save_input_) {
1772- rl_restore_prompt();
1773- rl_replace_line(saved_line_, 0);
1774- rl_point = saved_point_;
1775- rl_redisplay();
1776- g_free(saved_line_);
1777- }
1778- }
1779-
1780-private:
1781- bool save_input_;
1782- int saved_point_;
1783- char *saved_line_;
1784-};
1785-}
1786-
1787-namespace ac {
1788-namespace client {
1789-
1790-GMainLoop *Application::main_loop_ = nullptr;
1791-std::map<std::string,Application::Command> Application::available_commands_ = { };
1792-
1793-Application::MainOptions Application::MainOptions::FromCommandLine(int argc, char** argv) {
1794-
1795- static GOptionEntry options[] = {
1796- { NULL },
1797- };
1798-
1799- std::shared_ptr<GOptionContext> context{g_option_context_new(nullptr), [](GOptionContext *ctxt) { g_option_context_free(ctxt); }};
1800- GError *error = nullptr;
1801-
1802- g_option_context_add_main_entries(context.get(), options, NULL);
1803-
1804- if (!g_option_context_parse(context.get(), &argc, &argv, &error)) {
1805- if (error) {
1806- std::cerr << error->message << std::endl;
1807- g_error_free(error);
1808- } else
1809- std::cerr << "An unknown error occured" << std::endl;
1810- exit(1);
1811- }
1812-
1813- return MainOptions{};
1814-}
1815-
1816-int Application::Main(const MainOptions &options) {
1817- Application app;
1818- return app.Run();
1819-}
1820-
1821-Application::Application() :
1822- bus_connection_(nullptr),
1823- manager_(nullptr),
1824- input_source_(0),
1825- object_manager_(nullptr) {
1826-
1827- rl_callback_handler_install("aethercastctl> ", &Application::OnReadlineMessage);
1828-
1829- main_loop_ = g_main_loop_new(nullptr, FALSE);
1830-
1831- GError *error = nullptr;
1832- bus_connection_ = g_bus_get_sync(G_BUS_TYPE_SYSTEM, NULL, &error);
1833- if (!bus_connection_) {
1834- std::cerr << "Failed to connect to system bus: " << error->message << std::endl;
1835- g_error_free(error);
1836- return;
1837- }
1838-
1839- g_bus_watch_name_on_connection(bus_connection_, "org.aethercast",
1840- G_BUS_NAME_WATCHER_FLAGS_NONE,
1841- &Application::OnServiceFound,
1842- &Application::OnServiceLost,
1843- this, nullptr);
1844-
1845- g_dbus_connection_signal_subscribe(bus_connection_, "org.aethercast", "org.freedesktop.DBus.Properties", "PropertiesChanged", "/org/aethercast", nullptr,
1846- G_DBUS_SIGNAL_FLAGS_NONE, &Application::OnManagerPropertiesChanged, this, nullptr);
1847-
1848- RegisterCommand(Command { "enable", "", "Enable manager", std::bind(&Application::HandleEnableCommand, this, _1) });
1849- RegisterCommand(Command { "disable", "", "Disable manager", std::bind(&Application::HandleDisableCommand, this, _1) });
1850- RegisterCommand(Command { "show", "", "Show manager properties", std::bind(&Application::HandleShowCommand, this, _1) });
1851- RegisterCommand(Command { "scan", "", "Scan for devices", std::bind(&Application::HandleScanCommand, this, _1) });
1852- RegisterCommand(Command { "devices", "", "List available devices", std::bind(&Application::HandleDevicesCommand, this, _1) });
1853- RegisterCommand(Command { "info", "<address>", "Show device information", std::bind(&Application::HandleInfoCommand, this, _1) });
1854- RegisterCommand(Command { "connect", "<address>", "Connect a device", std::bind(&Application::HandleConnectCommand, this, _1) });
1855- RegisterCommand(Command { "disconnect", "<address>", "Disconnect a device", std::bind(&Application::HandleDisconnectCommand, this, _1) });
1856-}
1857-
1858-Application::~Application() {
1859- rl_message();
1860- rl_callback_handler_remove();
1861-
1862- g_object_unref(bus_connection_);
1863- g_main_loop_unref(main_loop_);
1864-
1865- if (manager_)
1866- g_object_unref(manager_);
1867-
1868- if (input_source_ > 0)
1869- g_source_remove(input_source_);
1870-}
1871-
1872-int Application::Run() {
1873- g_main_loop_run(main_loop_);
1874- return 0;
1875-}
1876-
1877-void Application::RegisterCommand(const Command &command) {
1878- available_commands_[command.name] = command;
1879-}
1880-
1881-void Application::HandleEnableCommand(const std::string &arguments) {
1882- if (!manager_)
1883- return;
1884-
1885- aethercast_interface_manager_set_enabled(manager_, true);
1886-}
1887-
1888-void Application::HandleDisableCommand(const std::string &arguments) {
1889- if (!manager_)
1890- return;
1891-
1892- aethercast_interface_manager_set_enabled(manager_, false);
1893-}
1894-
1895-void Application::HandleShowCommand(const std::string &arguments) {
1896- if (!manager_)
1897- return;
1898-
1899- auto enabled = aethercast_interface_manager_get_enabled(manager_);
1900- std::cout << "Enabled: " << std::boolalpha << (bool) enabled << std::endl;
1901-
1902- auto state = aethercast_interface_manager_get_state(manager_);
1903- std::cout << "State: " << state << std::endl;
1904-
1905- auto scanning = aethercast_interface_manager_get_scanning(manager_);
1906- std::cout << "Scanning: " << std::boolalpha << (bool) scanning << std::endl;
1907-
1908- auto capabilities = aethercast_interface_manager_get_capabilities(manager_);
1909- std::cout << "Capabilities:" << std::endl;
1910- for (int n = 0; capabilities[n] != nullptr; n++)
1911- std::cout << " " << capabilities[n] << std::endl;
1912-}
1913-
1914-void Application::OnScanDone(GObject *object, GAsyncResult *res, gpointer user_data) {
1915- auto inst = static_cast<Application*>(user_data);
1916-
1917- GError *error = nullptr;
1918- if (!aethercast_interface_manager_call_scan_finish(inst->manager_, res, &error)) {
1919- std::cerr << "Failed to scan:" << error->message << std::endl;
1920- g_error_free(error);
1921- return;
1922- }
1923-}
1924-
1925-void Application::HandleScanCommand(const std::string &arguments) {
1926- if (!manager_)
1927- return;
1928-
1929- aethercast_interface_manager_call_scan(manager_, nullptr, &Application::OnScanDone, this);
1930-}
1931-
1932-void Application::ForeachDevice(std::function<void(AethercastInterfaceDevice*)> callback, const std::string &address_filter) {
1933- if (!callback)
1934- return;
1935-
1936- auto objects = g_dbus_object_manager_get_objects(object_manager_);
1937-
1938- for (auto obj = objects; obj != nullptr; obj = obj->next) {
1939- auto device = AETHERCAST_INTERFACE_OBJECT_PROXY(obj->data);
1940- if (!device)
1941- continue;
1942-
1943- auto iface = g_dbus_object_get_interface(G_DBUS_OBJECT(device), "org.aethercast.Device");
1944- if (!iface)
1945- continue;
1946-
1947- auto device_obj = AETHERCAST_INTERFACE_DEVICE(iface);
1948-
1949- if (address_filter.length() > 0) {
1950- auto device_address = aethercast_interface_device_get_address(device_obj);
1951- if (address_filter != device_address)
1952- continue;
1953- }
1954-
1955- callback(device_obj);
1956- }
1957-}
1958-
1959-void Application::HandleDevicesCommand(const std::string &arguments) {
1960- ForeachDevice([=](AethercastInterfaceDevice *device) {
1961- auto address = aethercast_interface_device_get_address(device);
1962- auto name = aethercast_interface_device_get_name(device);
1963- std::cout << "Device " << address << " " << name << std::endl;
1964- });
1965-}
1966-
1967-void Application::HandleInfoCommand(const std::string &arguments) {
1968- if (arguments.length() == 0) {
1969- std::cerr << "No device address supplied" << std::endl;
1970- return;
1971- }
1972-
1973- bool found = false;
1974-
1975- ForeachDevice([&](AethercastInterfaceDevice *device) {
1976- auto address = aethercast_interface_device_get_address(device);
1977- std::cout << "Address: " << address << std::endl;
1978-
1979- auto name = aethercast_interface_device_get_name(device);
1980- std::cout << "Name: " << name << std::endl;
1981-
1982- auto state = aethercast_interface_device_get_state(device);
1983- std::cout << "State: " << state << std::endl;
1984-
1985- auto capabilities = aethercast_interface_device_get_capabilities(device);
1986- std::cout << "Capabilities: " << std::endl;
1987- for (int n = 0; capabilities[n] != nullptr; n++)
1988- std::cout << " " << capabilities[n] << std::endl;
1989-
1990- found = true;
1991- }, arguments);
1992-
1993- if (!found)
1994- std::cerr << "Unknown or invalid device address" << std::endl;
1995-}
1996-
1997-void Application::OnDeviceConnected(GObject *object, GAsyncResult *res, gpointer user_data) {
1998- PromptSaver ps;
1999-
2000- GError *error = nullptr;
2001- if (!aethercast_interface_device_call_connect_finish(AETHERCAST_INTERFACE_DEVICE(object), res, &error)) {
2002- std::cerr << "Failed to connect with device: " << error->message << std::endl;
2003- g_error_free(error);
2004- return;
2005- }
2006-}
2007-
2008-void Application::HandleConnectCommand(const std::string &arguments) {
2009- PromptSaver ps;
2010-
2011- if (arguments.length() == 0) {
2012- std::cerr << "No device address supplied" << std::endl;
2013- return;
2014- }
2015-
2016- bool found = false;
2017-
2018- ForeachDevice([&](AethercastInterfaceDevice *device) {
2019-
2020- aethercast_interface_device_call_connect(device, "sink", nullptr,
2021- &Application::OnDeviceConnected, nullptr);
2022-
2023- found = true;
2024- }, arguments);
2025-
2026- if (!found)
2027- std::cerr << "Unknown or invalid device address" << std::endl;
2028-}
2029-
2030-void Application::OnDeviceDisconnected(GObject *object, GAsyncResult *res, gpointer user_data) {
2031- PromptSaver ps;
2032-
2033- GError *error = nullptr;
2034- if (!aethercast_interface_device_call_disconnect_finish(AETHERCAST_INTERFACE_DEVICE(object), res, &error)) {
2035- std::cerr << "Failed to disconnect from device: " << error->message << std::endl;
2036- g_error_free(error);
2037- return;
2038- }
2039-}
2040-
2041-void Application::HandleDisconnectCommand(const std::string &arguments) {
2042- PromptSaver ps;
2043-
2044- if (arguments.length() == 0) {
2045- std::cerr << "No device address supplied" << std::endl;
2046- return;
2047- }
2048-
2049- bool found = false;
2050-
2051- ForeachDevice([&](AethercastInterfaceDevice *device) {
2052-
2053- aethercast_interface_device_call_disconnect(device, nullptr,
2054- &Application::OnDeviceDisconnected, nullptr);
2055-
2056- found = true;
2057- }, arguments);
2058-
2059- if (!found)
2060- std::cerr << "Unknown or invalid device address" << std::endl;
2061-}
2062-
2063-void Application::SetupStandardInput() {
2064- GIOChannel *channel;
2065-
2066- channel = g_io_channel_unix_new(fileno(stdin));
2067-
2068- input_source_ = g_io_add_watch(channel,
2069- (GIOCondition) (G_IO_IN | G_IO_HUP | G_IO_ERR | G_IO_NVAL),
2070- &Application::OnUserInput, this);
2071-
2072- g_io_channel_unref(channel);
2073-
2074-}
2075-
2076-gboolean Application::OnUserInput(GIOChannel *channel, GIOCondition condition, gpointer user_data) {
2077- auto inst = static_cast<Application*>(user_data);
2078-
2079- if (condition & G_IO_IN) {
2080- rl_callback_read_char();
2081- return TRUE;
2082- }
2083-
2084- if (condition & (G_IO_HUP | G_IO_ERR | G_IO_NVAL)) {
2085- g_main_loop_quit(inst->main_loop_);
2086- return FALSE;
2087- }
2088-
2089- return TRUE;
2090-}
2091-
2092-void Application::OnReadlineMessage(char *input) {
2093- PromptSaver ps;
2094-
2095- char *arg;
2096- int i;
2097-
2098- if (!input) {
2099- rl_newline(1, '\n');
2100- g_main_loop_quit(main_loop_);
2101- return;
2102- }
2103-
2104- std::string cmd = strtok_r(input, " ", &arg) ? : "";
2105- if (cmd.size() == 0)
2106- return;
2107-
2108- if (arg) {
2109- int len = strlen(arg);
2110- if (len > 0 && arg[len - 1] == ' ')
2111- arg[len - 1] = '\0';
2112- }
2113-
2114- auto iter = available_commands_.find(cmd);
2115- if (iter != available_commands_.end()) {
2116- if (iter->second.func)
2117- iter->second.func(arg);
2118- }
2119- else if (cmd == "help") {
2120- std::cout << "Available commands:" << std::endl;
2121-
2122- for (auto cmd : available_commands_) {
2123- if (cmd.second.description.size() == 0)
2124- continue;
2125-
2126- fprintf(stdout, " %s %-*s %s\n", cmd.second.name.c_str(),
2127- (int)(25 - cmd.second.name.length()),
2128- cmd.second.arguments.c_str(),
2129- cmd.second.description.c_str());
2130- }
2131- }
2132- else {
2133- std::cerr << "Invalid command" << std::endl;
2134- }
2135-
2136- g_free(input);
2137-}
2138-
2139-void Application::OnServiceLost(GDBusConnection *connection, const gchar *name, gpointer user_data) {
2140- auto inst = static_cast<Application*>(user_data);
2141-
2142- if (inst->input_source_ > 0) {
2143- g_source_remove(inst->input_source_);
2144- inst->input_source_ = 0;
2145- }
2146-
2147- if (inst->manager_) {
2148- g_object_unref(inst->manager_);
2149- inst->manager_ = nullptr;
2150- }
2151-
2152- if (inst->object_manager_) {
2153- g_object_unref(inst->object_manager_);
2154- inst->object_manager_ = nullptr;
2155- }
2156-}
2157-
2158-void Application::OnServiceFound(GDBusConnection *connection, const gchar *name, const gchar *name_owner, gpointer user_data) {
2159- auto inst = static_cast<Application*>(user_data);
2160-
2161- aethercast_interface_manager_proxy_new(inst->bus_connection_, G_DBUS_PROXY_FLAGS_NONE,
2162- "org.aethercast", "/org/aethercast", nullptr,
2163- &Application::OnManagerConnected, inst);
2164-}
2165-
2166-void Application::OnManagerConnected(GObject *object, GAsyncResult *res, gpointer user_data) {
2167- PromptSaver ps;
2168-
2169- auto inst = static_cast<Application*>(user_data);
2170-
2171- GError *error = nullptr;
2172- inst->manager_ = aethercast_interface_manager_proxy_new_finish(res, &error);
2173- if (!inst->manager_) {
2174- std::cerr << "Could not connect with manager: " << error->message << std::endl;
2175- g_error_free(error);
2176- g_main_loop_quit(inst->main_loop_);
2177- return;
2178- }
2179-
2180- // Use a high enough timeout to make sure we wait enough for the service to
2181- // respond as the connection process can take some time depending on different
2182- // factors.
2183- g_dbus_proxy_set_default_timeout(G_DBUS_PROXY(inst->manager_), kDBusTimeout * 1000);
2184-
2185- aethercast_interface_object_manager_client_new(inst->bus_connection_,
2186- G_DBUS_OBJECT_MANAGER_CLIENT_FLAGS_NONE,
2187- "org.aethercast", "/org/aethercast",
2188- nullptr, &Application::OnObjectManagerCreated, inst);
2189-}
2190-
2191-void Application::OnManagerPropertiesChanged(GDBusConnection *connection, const gchar *sender_name, const gchar *object_path,
2192- const gchar *interface_name, const gchar *signal_name, GVariant *parameters,
2193- gpointer user_data) {
2194- PromptSaver ps;
2195-
2196- if (g_variant_n_children(parameters) != 3)
2197- return;
2198-
2199- GVariant *changed_props = g_variant_get_child_value(parameters, 1);
2200- for (int n = 0; n < g_variant_n_children(changed_props); n++) {
2201- auto element = g_variant_get_child_value(changed_props, n);
2202- auto key_v = g_variant_get_child_value(element, 0);
2203- auto value_v = g_variant_get_child_value(element, 1);
2204-
2205- auto key = std::string(g_variant_get_string(key_v, nullptr));
2206-
2207- std::cout << "[CHG] Manager " << key << " changed: ";
2208-
2209- if (key == "Enabled")
2210- std::cout << std::boolalpha << (bool) g_variant_get_boolean(g_variant_get_variant(value_v)) << std::endl;
2211- else if (key == "State")
2212- std::cout << g_variant_get_string(g_variant_get_variant(value_v), nullptr) << std::endl;
2213- else if (key == "Scanning")
2214- std::cout << std::boolalpha << (bool) g_variant_get_boolean(g_variant_get_variant(value_v)) << std::endl;
2215- else if (key == "Capabilities") {
2216- std::stringstream capabilities;
2217-
2218- for (int m = 0; m < g_variant_n_children(value_v); m++) {
2219- auto capability = g_variant_get_child_value(value_v, m);
2220- if (!g_variant_is_of_type(capability, G_VARIANT_TYPE_STRING))
2221- continue;
2222-
2223- capabilities << g_variant_get_string(capability, nullptr) << " ";
2224- }
2225-
2226- std::cout << capabilities.str() << std::endl;
2227- }
2228- else
2229- std::cout << "unknown" << std::endl;
2230-
2231- g_variant_unref(key_v);
2232- g_variant_unref(value_v);
2233- }
2234-}
2235-
2236-void Application::OnDeviceAdded(GDBusObjectManager *manager, GDBusObject *object, gpointer user_data) {
2237- PromptSaver ps;
2238-
2239- auto iface = g_dbus_object_get_interface(G_DBUS_OBJECT(object), "org.aethercast.Device");
2240- if (!iface)
2241- return;
2242-
2243- auto device = AETHERCAST_INTERFACE_DEVICE(iface);
2244-
2245- auto address = aethercast_interface_device_get_address(device);
2246- auto name = aethercast_interface_device_get_name(device);
2247-
2248- std::cout << "Device " << address << " " << name << " added" << std::endl;
2249-}
2250-
2251-void Application::OnDeviceRemoved(GDBusObjectManager *manager, GDBusObject *object, gpointer user_data) {
2252- PromptSaver ps;
2253-
2254- auto iface = g_dbus_object_get_interface(G_DBUS_OBJECT(object), "org.aethercast.Device");
2255- if (!iface)
2256- return;
2257-
2258- auto device = AETHERCAST_INTERFACE_DEVICE(iface);
2259-
2260- auto address = aethercast_interface_device_get_address(device);
2261- auto name = aethercast_interface_device_get_name(device);
2262-
2263- std::cout << "Device " << address << " " << name <<" removed" << std::endl;
2264-}
2265-
2266-void Application::OnObjectManagerCreated(GObject *object, GAsyncResult *res, gpointer user_data) {
2267- auto inst = static_cast<Application*>(user_data);
2268-
2269- GError *error = nullptr;
2270- inst->object_manager_ = aethercast_interface_object_manager_client_new_finish(res, &error);
2271- if (!inst->object_manager_) {
2272- g_error_free(error);
2273- return;
2274- }
2275-
2276- g_signal_connect(inst->object_manager_, "object-added", G_CALLBACK(&Application::OnDeviceAdded), inst);
2277- g_signal_connect(inst->object_manager_, "object-removed", G_CALLBACK(&Application::OnDeviceRemoved), inst);
2278-
2279- inst->SetupStandardInput();
2280-}
2281-
2282-} // namespace client
2283-} // namespace ac
2284diff --git a/src/client/application.h b/src/client/application.h
2285deleted file mode 100644
2286index 9596110..0000000
2287--- a/src/client/application.h
2288+++ /dev/null
2289@@ -1,114 +0,0 @@
2290-/*
2291- * Copyright (C) 2015 Canonical, Ltd.
2292- *
2293- * This program is free software; you can redistribute it and/or modify
2294- * it under the terms of the GNU General Public License as published by
2295- * the Free Software Foundation; either version 2 of the License, or
2296- * (at your option) any later version.
2297- *
2298- * This program is distributed in the hope that it will be useful,
2299- * but WITHOUT ANY WARRANTY; without even the implied warranty of
2300- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2301- * GNU General Public License for more details.
2302- *
2303- * You should have received a copy of the GNU General Public License
2304- * along with this program; if not, write to the Free Software
2305- * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
2306- *
2307- */
2308-
2309-#ifndef APPLICATION_H_
2310-#define APPLICATION_H_
2311-
2312-#include <map>
2313-#include <functional>
2314-
2315-#include "ac/glib_wrapper.h"
2316-
2317-extern "C" {
2318-// Ignore all warnings coming from the external headers as we don't
2319-// control them and also don't want to get any warnings from them
2320-// which will only pollute our build output.
2321-#pragma GCC diagnostic push
2322-#pragma GCC diagnostic warning "-w"
2323-#include "aethercastinterface.h"
2324-#pragma GCC diagnostic pop
2325-}
2326-
2327-namespace ac {
2328-namespace client {
2329-
2330-class Application {
2331-public:
2332- struct MainOptions {
2333- static MainOptions FromCommandLine(int argc, char** argv);
2334- };
2335-
2336- static int Main(const MainOptions &options);
2337-
2338- Application();
2339- ~Application();
2340-
2341- int Run();
2342-
2343-private:
2344- struct Command {
2345- std::string name;
2346- std::string arguments;
2347- std::string description;
2348- std::function<void(std::string)> func;
2349- };
2350-
2351- void HandleEnableCommand(const std::string &arguments);
2352- void HandleDisableCommand(const std::string &arguments);
2353- void HandleShowCommand(const std::string &arguments);
2354- void HandleScanCommand(const std::string &arguments);
2355- void HandleDevicesCommand(const std::string &arguments);
2356- void HandleInfoCommand(const std::string &arguments);
2357- void HandleConnectCommand(const std::string &arguments);
2358- void HandleDisconnectCommand(const std::string &arguments);
2359-
2360- void RegisterCommand(const Command &command);
2361-
2362-private:
2363- static gboolean OnUserInput(GIOChannel *channel, GIOCondition condition, gpointer user_data);
2364- static void OnReadlineMessage(char *input);
2365- static void OnServiceLost(GDBusConnection *connection, const gchar *name, gpointer user_data);
2366- static void OnServiceFound(GDBusConnection *connection, const gchar *name, const gchar *name_owner, gpointer user_data);
2367- static void OnManagerConnected(GObject *object, GAsyncResult *res, gpointer user_data);
2368- static void OnDeviceAdded(GDBusObjectManager *manager, GDBusObject *object, gpointer user_data);
2369- static void OnDeviceRemoved(GDBusObjectManager *manager, GDBusObject *object, gpointer user_data);
2370- static void OnObjectManagerCreated(GObject *object, GAsyncResult *res, gpointer user_data);
2371-
2372- static void OnManagerPropertiesChanged(GDBusConnection *connection,
2373- const gchar *sender_name,
2374- const gchar *object_path,
2375- const gchar *interface_name,
2376- const gchar *signal_name,
2377- GVariant *parameters,
2378- gpointer user_data);
2379-
2380- static void OnScanDone(GObject *object, GAsyncResult *res, gpointer user_data);
2381- static void OnDeviceConnected(GObject *object, GAsyncResult *res, gpointer user_data);
2382- static void OnDeviceDisconnected(GObject *object, GAsyncResult *res, gpointer user_data);
2383-private:
2384- void SetupStandardInput();
2385- void ForeachDevice(std::function<void(AethercastInterfaceDevice*)> callback, const std::string &address_filter = "");
2386-
2387-private:
2388- GDBusConnection *bus_connection_;
2389- AethercastInterfaceManager *manager_;
2390- guint input_source_;
2391- GDBusObjectManager *object_manager_;
2392-
2393-private:
2394- // Needs to be static otherwise we can't access it from the readline
2395- // callback function (which sadly doesn't allow passing any user data)
2396- static GMainLoop *main_loop_;
2397- static std::map<std::string,Command> available_commands_;
2398-};
2399-
2400-} // namespace client
2401-} // namespace ac
2402-
2403-#endif
2404diff --git a/src/client/main.cpp b/src/client/main.cpp
2405deleted file mode 100644
2406index 0663e9f..0000000
2407--- a/src/client/main.cpp
2408+++ /dev/null
2409@@ -1,25 +0,0 @@
2410-/*
2411- * Copyright (C) 2012 Intel Corporation. All rights reserved.
2412- * 2015 Canonical, Ltd.
2413- *
2414- * This program is free software; you can redistribute it and/or modify
2415- * it under the terms of the GNU General Public License as published by
2416- * the Free Software Foundation; either version 2 of the License, or
2417- * (at your option) any later version.
2418- *
2419- * This program is distributed in the hope that it will be useful,
2420- * but WITHOUT ANY WARRANTY; without even the implied warranty of
2421- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2422- * GNU General Public License for more details.
2423- *
2424- * You should have received a copy of the GNU General Public License
2425- * along with this program; if not, write to the Free Software
2426- * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
2427- *
2428- */
2429-
2430-#include "application.h"
2431-
2432-int main(int argc, char **argv) {
2433- return ac::client::Application::Main(ac::client::Application::MainOptions::FromCommandLine(argc, argv));
2434-}

Subscribers

People subscribed via source and target branches