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
diff --git a/snapcraft.yaml b/snapcraft.yaml
index 4b4bea5..017bcdf 100644
--- a/snapcraft.yaml
+++ b/snapcraft.yaml
@@ -13,10 +13,19 @@ slots:
13 interface: dbus13 interface: dbus
14 bus: system14 bus: system
15 name: org.aethercast15 name: org.aethercast
16plugs:
17 dbus-client:
18 interface: dbus
19 bus: system
20 name: org.aethercast
1621
17apps:22apps:
18 aethercast:23 aethercast:
19 command: sbin/aethercast24 command: sbin/aethercast
25 plugs:
26 - dbus-client
27 service:
28 command: sbin/aethercast service
20 daemon: simple29 daemon: simple
21 slots:30 slots:
22 - dbus-service31 - dbus-service
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 8522a89..c55d44c 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -21,6 +21,9 @@ set(HEADERS
21 ac/config.cpp21 ac/config.cpp
22 ac/config.h22 ac/config.h
2323
24 ac/optional.h
25 ac/do_not_copy_or_move.h
26
24 ac/common/executable.h27 ac/common/executable.h
25 ac/common/executor.h28 ac/common/executor.h
26 ac/common/executorfactory.h29 ac/common/executorfactory.h
@@ -53,6 +56,7 @@ set(ANDROID_SOURCES
53 ac/android/h264encoder.cpp)56 ac/android/h264encoder.cpp)
5457
55set(SOURCES58set(SOURCES
59 ac/cli.cpp
56 ac/types.cpp60 ac/types.cpp
57 ac/utils.cpp61 ac/utils.cpp
58 ac/networkutils.cpp62 ac/networkutils.cpp
@@ -69,6 +73,11 @@ set(SOURCES
69 ac/networkmanagerfactory.cpp73 ac/networkmanagerfactory.cpp
70 ac/networkdevice.cpp74 ac/networkdevice.cpp
7175
76 ac/cmds/shell.cpp
77 ac/cmds/service.cpp
78
79 ac/client/application.cpp
80
72 ac/dbus/helpers.cpp81 ac/dbus/helpers.cpp
73 ac/dbus/errors.cpp82 ac/dbus/errors.cpp
74 ac/dbus/controllerskeleton.cpp83 ac/dbus/controllerskeleton.cpp
@@ -189,6 +198,8 @@ target_link_libraries(aethercast-core
189 ${EGL_LIBRARIES}198 ${EGL_LIBRARIES}
190 ${GLESV2_LDFLAGS}199 ${GLESV2_LDFLAGS}
191 ${GLESV2_LIBRARIES}200 ${GLESV2_LIBRARIES}
201 ${READLINE_LDFLAGS}
202 ${READLINE_LIBRARIES}
192 -ldl203 -ldl
193)204)
194205
@@ -210,5 +221,3 @@ install(
210 LIBRARY DESTINATION lib221 LIBRARY DESTINATION lib
211 ARCHIVE DESTINATION lib/static222 ARCHIVE DESTINATION lib/static
212)223)
213
214add_subdirectory(client)
diff --git a/src/ac/cli.cpp b/src/ac/cli.cpp
215new file mode 100644224new file mode 100644
index 0000000..1129244
--- /dev/null
+++ b/src/ac/cli.cpp
@@ -0,0 +1,246 @@
1/*
2 * Copyright (C) 2016 Canonical, Ltd.
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU Lesser General Public License as published by
6 * the Free Software Foundation; version 3.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU Lesser General Public License for more details.
12 *
13 * You should have received a copy of the GNU Lesser General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 *
16 * Authored by: Thomas Voß <thomas.voss@canonical.com>
17 *
18 */
19
20#include <boost/format.hpp>
21#include <boost/program_options.hpp>
22
23#include "ac/cli.h"
24
25namespace cli = ac::cli;
26namespace po = boost::program_options;
27
28namespace {
29namespace pattern {
30static constexpr const char* help_for_command_with_subcommands =
31 "NAME:\n"
32 " %1% - %2%\n"
33 "\n"
34 "USAGE:\n"
35 " %3% [command options] [arguments...]";
36
37static constexpr const char* commands = "COMMANDS:";
38static constexpr const char* command = " %1% %2%";
39
40static constexpr const char* options = "OPTIONS:";
41static constexpr const char* option = " --%1% %2%";
42}
43
44void add_to_desc_for_flags(po::options_description& desc,
45 const std::set<cli::Flag::Ptr>& flags) {
46 for (auto flag : flags) {
47 auto v = po::value<std::string>()->notifier(
48 [flag](const std::string& s) { flag->notify(s); });
49 desc.add_options()(flag->name().as_string().c_str(), v,
50 flag->description().as_string().c_str());
51 }
52}
53}
54
55std::vector<std::string> cli::args(int argc, char** argv) {
56 std::vector<std::string> result;
57 for (int i = 1; i < argc; i++) result.push_back(argv[i]);
58 return result;
59}
60
61const cli::Name& cli::Flag::name() const { return name_; }
62
63const cli::Description& cli::Flag::description() const { return description_; }
64
65cli::Flag::Flag(const Name& name, const Description& description)
66 : name_{name}, description_{description} {}
67
68cli::Command::FlagsWithInvalidValue::FlagsWithInvalidValue()
69 : std::runtime_error{"Flags with invalid value"} {}
70
71cli::Command::FlagsMissing::FlagsMissing()
72 : std::runtime_error{"Flags are missing in command invocation"} {}
73
74cli::Name cli::Command::name() const { return name_; }
75
76cli::Usage cli::Command::usage() const { return usage_; }
77
78cli::Description cli::Command::description() const { return description_; }
79
80cli::Command::Command(const cli::Name& name, const cli::Usage& usage,
81 const cli::Description& description)
82 : name_(name), usage_(usage), description_(description) {}
83
84cli::CommandWithSubcommands::CommandWithSubcommands(
85 const Name& name, const Usage& usage, const Description& description)
86 : Command{name, usage, description} {
87 command(std::make_shared<cmd::Help>(*this));
88}
89
90cli::CommandWithSubcommands& cli::CommandWithSubcommands::command(
91 const Command::Ptr& command) {
92 commands_[command->name().as_string()] = command;
93 return *this;
94}
95
96cli::CommandWithSubcommands& cli::CommandWithSubcommands::flag(
97 const Flag::Ptr& flag) {
98 flags_.insert(flag);
99 return *this;
100}
101
102void cli::CommandWithSubcommands::help(std::ostream& out) {
103 out << boost::format(pattern::help_for_command_with_subcommands) %
104 name().as_string() % usage().as_string() % name().as_string()
105 << std::endl;
106
107 if (flags_.size() > 0) {
108 out << std::endl
109 << pattern::options << std::endl;
110 for (const auto& flag : flags_)
111 out << boost::format(pattern::option) % flag->name() % flag->description()
112 << std::endl;
113 }
114
115 if (commands_.size() > 0) {
116 out << std::endl
117 << pattern::commands << std::endl;
118 for (const auto& cmd : commands_) {
119 if (cmd.second)
120 out << boost::format(pattern::command) % cmd.second->name() %
121 cmd.second->description()
122 << std::endl;
123 }
124 }
125}
126
127int cli::CommandWithSubcommands::run(const cli::Command::Context& ctxt) {
128 po::positional_options_description pdesc;
129 pdesc.add("command", 1);
130
131 po::options_description desc("Options");
132 desc.add_options()("command", po::value<std::string>()->required(),
133 "the command to be executed");
134
135 add_to_desc_for_flags(desc, flags_);
136
137 try {
138 po::variables_map vm;
139 auto parsed = po::command_line_parser(ctxt.args)
140 .options(desc)
141 .positional(pdesc)
142 .style(po::command_line_style::unix_style)
143 .allow_unregistered()
144 .run();
145
146 po::store(parsed, vm);
147 po::notify(vm);
148
149 auto cmd = commands_[vm["command"].as<std::string>()];
150 if (!cmd) {
151 ctxt.cout << "Unknown command '" << vm["command"].as<std::string>() << "'"
152 << std::endl;
153 help(ctxt.cout);
154 return EXIT_FAILURE;
155 }
156
157 return cmd->run(cli::Command::Context{
158 ctxt.cin, ctxt.cout,
159 po::collect_unrecognized(parsed.options, po::include_positional)});
160 } catch (const po::error& e) {
161 ctxt.cout << e.what() << std::endl;
162 help(ctxt.cout);
163 return EXIT_FAILURE;
164 }
165
166 return EXIT_FAILURE;
167}
168
169cli::CommandWithFlagsAndAction::CommandWithFlagsAndAction(
170 const Name& name, const Usage& usage, const Description& description)
171 : Command{name, usage, description} {}
172
173cli::CommandWithFlagsAndAction& cli::CommandWithFlagsAndAction::flag(
174 const Flag::Ptr& flag) {
175 flags_.insert(flag);
176 return *this;
177}
178
179cli::CommandWithFlagsAndAction& cli::CommandWithFlagsAndAction::action(
180 const Action& action) {
181 action_ = action;
182 return *this;
183}
184
185int cli::CommandWithFlagsAndAction::run(const Context& ctxt) {
186 po::options_description cd(name().as_string());
187
188 bool help_requested{false};
189 cd.add_options()("help", po::bool_switch(&help_requested),
190 "produces a help message");
191
192 add_to_desc_for_flags(cd, flags_);
193
194 try {
195 po::variables_map vm;
196 auto parsed = po::command_line_parser(ctxt.args)
197 .options(cd)
198 .style(po::command_line_style::unix_style)
199 .allow_unregistered()
200 .run();
201 po::store(parsed, vm);
202 po::notify(vm);
203
204 if (help_requested) {
205 help(ctxt.cout);
206 return EXIT_SUCCESS;
207 }
208
209 return action_(cli::Command::Context{
210 ctxt.cin, ctxt.cout,
211 po::collect_unrecognized(parsed.options, po::include_positional)});
212 } catch (const po::error& e) {
213 ctxt.cout << e.what() << std::endl;
214 help(ctxt.cout);
215 return EXIT_FAILURE;
216 }
217
218 return EXIT_FAILURE;
219}
220
221void cli::CommandWithFlagsAndAction::help(std::ostream& out) {
222 out << boost::format(pattern::help_for_command_with_subcommands) %
223 name().as_string() % description().as_string() % name().as_string()
224 << std::endl;
225
226 if (flags_.size() > 0) {
227 out << std::endl
228 << boost::format(pattern::options) << std::endl;
229 for (const auto& flag : flags_)
230 out << boost::format(pattern::option) % flag->name() % flag->description()
231 << std::endl;
232 }
233}
234
235cli::cmd::Help::Help(Command& cmd)
236 : Command{cli::Name{"help"}, cli::Usage{"prints a short help message"},
237 cli::Description{"prints a short help message"}},
238 command{cmd} {}
239
240// From Command
241int cli::cmd::Help::run(const Context& context) {
242 command.help(context.cout);
243 return EXIT_FAILURE;
244}
245
246void cli::cmd::Help::help(std::ostream& out) { command.help(out); }
diff --git a/src/ac/cli.h b/src/ac/cli.h
0new file mode 100644247new file mode 100644
index 0000000..131965f
--- /dev/null
+++ b/src/ac/cli.h
@@ -0,0 +1,343 @@
1/*
2 * Copyright (C) 2016 Canonical, Ltd.
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU Lesser General Public License as published by
6 * the Free Software Foundation; version 3.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU Lesser General Public License for more details.
12 *
13 * You should have received a copy of the GNU Lesser General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 *
16 * Authored by: Thomas Voß <thomas.voss@canonical.com>
17 *
18 */
19#ifndef AETHERCAST_CLI_H_
20#define AETHERCAST_CLI_H_
21
22#include <iomanip>
23#include <iostream>
24#include <memory>
25#include <set>
26#include <sstream>
27#include <stdexcept>
28#include <string>
29#include <unordered_map>
30
31#include "ac/do_not_copy_or_move.h"
32#include "ac/optional.h"
33
34namespace ac {
35namespace cli {
36
37template <std::size_t max>
38class SizeConstrainedString {
39 public:
40 SizeConstrainedString(const std::string& s) : s{s} {
41 if (s.size() > max)
42 throw std::logic_error{"Max size exceeded " + std::to_string(max)};
43 }
44
45 const std::string& as_string() const { return s; }
46
47 operator std::string() const { return s; }
48
49 private:
50 std::string s;
51};
52
53template <std::size_t max>
54bool operator<(const SizeConstrainedString<max>& lhs,
55 const SizeConstrainedString<max>& rhs) {
56 return lhs.as_string() < rhs.as_string();
57}
58
59template <std::size_t max>
60bool operator==(const SizeConstrainedString<max>& lhs,
61 const SizeConstrainedString<max>& rhs) {
62 return lhs.as_string() == rhs.as_string();
63}
64
65template <std::size_t max>
66std::ostream& operator<<(std::ostream& out,
67 const SizeConstrainedString<max>& scs) {
68 return out << std::setw(max) << std::left << scs.as_string();
69}
70
71// We are imposing size constraints to ensure a consistent CLI layout.
72typedef SizeConstrainedString<20> Name;
73typedef SizeConstrainedString<60> Usage;
74typedef SizeConstrainedString<60> Description;
75
76/// @brief Flag models an input parameter to a command.
77class Flag : public DoNotCopyOrMove {
78 public:
79 // Safe us some typing.
80 typedef std::shared_ptr<Flag> Ptr;
81
82 /// @brief notify announces a new value to the flag.
83 virtual void notify(const std::string& value) = 0;
84 /// @brief name returns the name of the Flag.
85 const Name& name() const;
86 /// @brief description returns a human-readable description of the flag.
87 const Description& description() const;
88
89 protected:
90 /// @brief Flag creates a new instance, initializing name and description
91 /// from the given values.
92 Flag(const Name& name, const Description& description);
93
94 private:
95 Name name_;
96 Description description_;
97};
98
99/// @brief TypedFlag implements Flag relying on operator<< and operator>> to
100/// read/write values to/from strings.
101template <typename T>
102class TypedFlag : public Flag {
103 public:
104 typedef std::shared_ptr<TypedFlag<T>> Ptr;
105
106 TypedFlag(const Name& name, const Description& description)
107 : Flag{name, description} {}
108
109 /// @brief value installs the given value in the flag.
110 TypedFlag& value(const T& value) {
111 value_ = value;
112 return *this;
113 }
114
115 /// @brief value returns the optional value associated with the flag.
116 const Optional<T>& value() const { return value_; }
117
118 /// @brief notify tries to unwrap a value of type T from value.
119 void notify(const std::string& s) override {
120 std::stringstream ss{s};
121 T value;
122 ss >> value;
123 value_ = value;
124 }
125
126 private:
127 Optional<T> value_;
128};
129
130/// @brief TypedReferenceFlag implements Flag, relying on operator<</>> to
131/// convert to/from string representations,
132/// updating the given mutable reference to a value of type T.
133template <typename T>
134class TypedReferenceFlag : public Flag {
135 public:
136 // Safe us some typing.
137 typedef std::shared_ptr<TypedReferenceFlag<T>> Ptr;
138
139 /// @brief TypedReferenceFlag initializes a new instance with name,
140 /// description and value.
141 TypedReferenceFlag(const Name& name, const Description& description, T& value)
142 : Flag{name, description}, value_{value} {}
143
144 /// @brief notify tries to unwrap a value of type T from value,
145 /// relying on operator>> to read from given string s.
146 void notify(const std::string& s) override {
147 std::stringstream ss{s};
148 ss >> value_.get();
149 }
150
151 private:
152 std::reference_wrapper<T> value_;
153};
154
155/// @brief OptionalTypedReferenceFlag handles Optional<T> references, making
156/// sure that
157/// a value is always read on notify, even if the Optional<T> wasn't initialized
158/// previously.
159template <typename T>
160class OptionalTypedReferenceFlag : public Flag {
161 public:
162 typedef std::shared_ptr<OptionalTypedReferenceFlag<T>> Ptr;
163
164 OptionalTypedReferenceFlag(const Name& name, const Description& description,
165 Optional<T>& value)
166 : Flag{name, description}, value_{value} {}
167
168 /// @brief notify tries to unwrap a value of type T from value.
169 void notify(const std::string& s) override {
170 std::stringstream ss{s};
171 T value;
172 ss >> value;
173 value_.get() = value;
174 }
175
176 private:
177 std::reference_wrapper<Optional<T>> value_;
178};
179
180/// @brief Command abstracts an individual command available from the daemon.
181class Command : public DoNotCopyOrMove {
182 public:
183 // Safe us some typing
184 typedef std::shared_ptr<Command> Ptr;
185
186 /// @brief FlagsMissing is thrown if at least one required flag is missing.
187 struct FlagsMissing : public std::runtime_error {
188 /// @brief FlagsMissing initializes a new instance.
189 FlagsMissing();
190 };
191
192 /// @brief FlagsWithWrongValue is thrown if a value passed on the command line
193 /// is invalid.
194 struct FlagsWithInvalidValue : public std::runtime_error {
195 /// @brief FlagsWithInvalidValue initializes a new instance.
196 FlagsWithInvalidValue();
197 };
198
199 /// @brief Context bundles information passed to Command::run invocations.
200 struct Context {
201 std::istream& cin; ///< The std::istream that should be used for reading.
202 std::ostream& cout; ///< The std::ostream that should be used for writing.
203 std::vector<std::string> args; ///< The command line args.
204 };
205
206 /// @brief name returns the Name of the command.
207 virtual Name name() const;
208
209 /// @brief usage returns a short usage string for the command.
210 virtual Usage usage() const;
211
212 /// @brief description returns a longer string explaining the command.
213 virtual Description description() const;
214
215 /// @brief run puts the command to execution.
216 virtual int run(const Context& context) = 0;
217
218 /// @brief help prints information about a command to out.
219 virtual void help(std::ostream& out) = 0;
220
221 protected:
222 /// @brief Command initializes a new instance with the given name, usage and
223 /// description.
224 Command(const Name& name, const Usage& usage, const Description& description);
225
226 /// @brief name adjusts the name of the command to n.
227 // virtual void name(const Name& n);
228 /// @brief usage adjusts the usage string of the comand to u.
229 // virtual void usage(const Usage& u);
230 /// @brief description adjusts the description string of the command to d.
231 // virtual void description(const Description& d);
232
233 private:
234 Name name_;
235 Usage usage_;
236 Description description_;
237};
238
239/// @brief CommandWithSubcommands implements Command, selecting one of a set of
240/// actions.
241class CommandWithSubcommands : public Command {
242 public:
243 typedef std::shared_ptr<CommandWithSubcommands> Ptr;
244 typedef std::function<int(const Context&)> Action;
245
246 /// @brief CommandWithSubcommands initializes a new instance with the given
247 /// name, usage and description
248 CommandWithSubcommands(const Name& name, const Usage& usage,
249 const Description& description);
250
251 /// @brief command adds the given command to the set of known commands.
252 CommandWithSubcommands& command(const Command::Ptr& command);
253
254 /// @brief flag adds the given flag to the set of known flags.
255 CommandWithSubcommands& flag(const Flag::Ptr& flag);
256
257 // From Command
258 int run(const Context& context) override;
259 void help(std::ostream& out) override;
260
261 private:
262 std::unordered_map<std::string, Command::Ptr> commands_;
263 std::set<Flag::Ptr> flags_;
264};
265
266/// @brief CommandWithFlagsAction implements Command, executing an Action after
267/// handling
268class CommandWithFlagsAndAction : public Command {
269 public:
270 typedef std::shared_ptr<CommandWithFlagsAndAction> Ptr;
271 typedef std::function<int(const Context&)> Action;
272
273 /// @brief CommandWithFlagsAndAction initializes a new instance with the given
274 /// name, usage and description
275 CommandWithFlagsAndAction(const Name& name, const Usage& usage,
276 const Description& description);
277
278 /// @brief flag adds the given flag to the set of known flags.
279 CommandWithFlagsAndAction& flag(const Flag::Ptr& flag);
280
281 /// @brief action installs the given action.
282 CommandWithFlagsAndAction& action(const Action& action);
283
284 // From Command
285 int run(const Context& context) override;
286 void help(std::ostream& out) override;
287
288 private:
289 std::set<Flag::Ptr> flags_;
290 Action action_;
291};
292
293namespace cmd {
294/// @brief HelpFor prints a help message for the given command on execution.
295class Help : public Command {
296 public:
297 /// @brief HelpFor initializes a new instance with the given reference to a
298 /// cmd.
299 explicit Help(Command& cmd);
300
301 // From Command
302 int run(const Context& context) override;
303 void help(std::ostream& out) override;
304
305 private:
306 /// @cond
307 Command& command;
308 /// @endcond
309};
310}
311
312/// @brief args returns a vector of strings assembled from argc and argv.
313std::vector<std::string> args(int argc, char** argv);
314
315/// @brief make_flag returns a flag with the given name and description.
316template <typename T>
317typename TypedFlag<T>::Ptr make_flag(const Name& name,
318 const Description& description) {
319 return std::make_shared<TypedFlag<T>>(name, description);
320}
321
322/// @brief make_flag returns a flag with the given name and description,
323/// notifying updates to value.
324template <typename T>
325typename TypedReferenceFlag<T>::Ptr make_flag(const Name& name,
326 const Description& desc,
327 T& value) {
328 return std::make_shared<TypedReferenceFlag<T>>(name, desc, value);
329}
330
331/// @brief make_flag returns a flag with the given name and description,
332/// updating the given optional value.
333template <typename T>
334typename OptionalTypedReferenceFlag<T>::Ptr make_flag(const Name& name,
335 const Description& desc,
336 Optional<T>& value) {
337 return std::make_shared<OptionalTypedReferenceFlag<T>>(name, desc, value);
338}
339
340} // namespace cli
341} // namespace ac
342
343#endif
diff --git a/src/ac/client/application.cpp b/src/ac/client/application.cpp
0new file mode 100644344new file mode 100644
index 0000000..9fef06b
--- /dev/null
+++ b/src/ac/client/application.cpp
@@ -0,0 +1,541 @@
1/*
2 * Copyright (C) 2015 Canonical, Ltd.
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
17 *
18 */
19
20#include <memory>
21#include <functional>
22#include <iostream>
23#include <string>
24#include <sstream>
25
26#include <readline/readline.h>
27#include <readline/history.h>
28
29#include "ac/glib_wrapper.h"
30
31#include "application.h"
32
33using namespace std::placeholders;
34
35namespace {
36const unsigned int kDBusTimeout = 120;
37
38class PromptSaver {
39public:
40 PromptSaver() {
41 save_input_ = !RL_ISSTATE(RL_STATE_DONE);
42 if (save_input_) {
43 saved_point_ = rl_point;
44 saved_line_ = rl_copy_text(0, rl_end);
45 rl_save_prompt();
46 rl_replace_line("", 0);
47 rl_redisplay();
48 }
49 }
50
51 ~PromptSaver() {
52 if (save_input_) {
53 rl_restore_prompt();
54 rl_replace_line(saved_line_, 0);
55 rl_point = saved_point_;
56 rl_redisplay();
57 g_free(saved_line_);
58 }
59 }
60
61private:
62 bool save_input_;
63 int saved_point_;
64 char *saved_line_;
65};
66}
67
68namespace ac {
69namespace client {
70
71GMainLoop *Application::main_loop_ = nullptr;
72std::map<std::string,Application::Command> Application::available_commands_ = { };
73
74int Application::Main(const MainOptions &options) {
75 Application app;
76 return app.Run();
77}
78
79Application::Application() :
80 bus_connection_(nullptr),
81 manager_(nullptr),
82 input_source_(0),
83 object_manager_(nullptr) {
84
85 rl_callback_handler_install("aethercastctl> ", &Application::OnReadlineMessage);
86
87 main_loop_ = g_main_loop_new(nullptr, FALSE);
88
89 GError *error = nullptr;
90 bus_connection_ = g_bus_get_sync(G_BUS_TYPE_SYSTEM, NULL, &error);
91 if (!bus_connection_) {
92 std::cerr << "Failed to connect to system bus: " << error->message << std::endl;
93 g_error_free(error);
94 return;
95 }
96
97 g_bus_watch_name_on_connection(bus_connection_, "org.aethercast",
98 G_BUS_NAME_WATCHER_FLAGS_NONE,
99 &Application::OnServiceFound,
100 &Application::OnServiceLost,
101 this, nullptr);
102
103 g_dbus_connection_signal_subscribe(bus_connection_, "org.aethercast", "org.freedesktop.DBus.Properties", "PropertiesChanged", "/org/aethercast", nullptr,
104 G_DBUS_SIGNAL_FLAGS_NONE, &Application::OnManagerPropertiesChanged, this, nullptr);
105
106 RegisterCommand(Command { "enable", "", "Enable manager", std::bind(&Application::HandleEnableCommand, this, _1) });
107 RegisterCommand(Command { "disable", "", "Disable manager", std::bind(&Application::HandleDisableCommand, this, _1) });
108 RegisterCommand(Command { "show", "", "Show manager properties", std::bind(&Application::HandleShowCommand, this, _1) });
109 RegisterCommand(Command { "scan", "", "Scan for devices", std::bind(&Application::HandleScanCommand, this, _1) });
110 RegisterCommand(Command { "devices", "", "List available devices", std::bind(&Application::HandleDevicesCommand, this, _1) });
111 RegisterCommand(Command { "info", "<address>", "Show device information", std::bind(&Application::HandleInfoCommand, this, _1) });
112 RegisterCommand(Command { "connect", "<address>", "Connect a device", std::bind(&Application::HandleConnectCommand, this, _1) });
113 RegisterCommand(Command { "disconnect", "<address>", "Disconnect a device", std::bind(&Application::HandleDisconnectCommand, this, _1) });
114}
115
116Application::~Application() {
117 rl_message();
118 rl_callback_handler_remove();
119
120 g_object_unref(bus_connection_);
121 g_main_loop_unref(main_loop_);
122
123 if (manager_)
124 g_object_unref(manager_);
125
126 if (input_source_ > 0)
127 g_source_remove(input_source_);
128}
129
130int Application::Run() {
131 g_main_loop_run(main_loop_);
132 return 0;
133}
134
135void Application::RegisterCommand(const Command &command) {
136 available_commands_[command.name] = command;
137}
138
139void Application::HandleEnableCommand(const std::string &arguments) {
140 if (!manager_)
141 return;
142
143 aethercast_interface_manager_set_enabled(manager_, true);
144}
145
146void Application::HandleDisableCommand(const std::string &arguments) {
147 if (!manager_)
148 return;
149
150 aethercast_interface_manager_set_enabled(manager_, false);
151}
152
153void Application::HandleShowCommand(const std::string &arguments) {
154 if (!manager_)
155 return;
156
157 auto enabled = aethercast_interface_manager_get_enabled(manager_);
158 std::cout << "Enabled: " << std::boolalpha << (bool) enabled << std::endl;
159
160 auto state = aethercast_interface_manager_get_state(manager_);
161 std::cout << "State: " << state << std::endl;
162
163 auto scanning = aethercast_interface_manager_get_scanning(manager_);
164 std::cout << "Scanning: " << std::boolalpha << (bool) scanning << std::endl;
165
166 auto capabilities = aethercast_interface_manager_get_capabilities(manager_);
167 std::cout << "Capabilities:" << std::endl;
168 for (int n = 0; capabilities[n] != nullptr; n++)
169 std::cout << " " << capabilities[n] << std::endl;
170}
171
172void Application::OnScanDone(GObject *object, GAsyncResult *res, gpointer user_data) {
173 auto inst = static_cast<Application*>(user_data);
174
175 GError *error = nullptr;
176 if (!aethercast_interface_manager_call_scan_finish(inst->manager_, res, &error)) {
177 std::cerr << "Failed to scan:" << error->message << std::endl;
178 g_error_free(error);
179 return;
180 }
181}
182
183void Application::HandleScanCommand(const std::string &arguments) {
184 if (!manager_)
185 return;
186
187 aethercast_interface_manager_call_scan(manager_, nullptr, &Application::OnScanDone, this);
188}
189
190void Application::ForeachDevice(std::function<void(AethercastInterfaceDevice*)> callback, const std::string &address_filter) {
191 if (!callback)
192 return;
193
194 auto objects = g_dbus_object_manager_get_objects(object_manager_);
195
196 for (auto obj = objects; obj != nullptr; obj = obj->next) {
197 auto device = AETHERCAST_INTERFACE_OBJECT_PROXY(obj->data);
198 if (!device)
199 continue;
200
201 auto iface = g_dbus_object_get_interface(G_DBUS_OBJECT(device), "org.aethercast.Device");
202 if (!iface)
203 continue;
204
205 auto device_obj = AETHERCAST_INTERFACE_DEVICE(iface);
206
207 if (address_filter.length() > 0) {
208 auto device_address = aethercast_interface_device_get_address(device_obj);
209 if (address_filter != device_address)
210 continue;
211 }
212
213 callback(device_obj);
214 }
215}
216
217void Application::HandleDevicesCommand(const std::string &arguments) {
218 ForeachDevice([=](AethercastInterfaceDevice *device) {
219 auto address = aethercast_interface_device_get_address(device);
220 auto name = aethercast_interface_device_get_name(device);
221 std::cout << "Device " << address << " " << name << std::endl;
222 });
223}
224
225void Application::HandleInfoCommand(const std::string &arguments) {
226 if (arguments.length() == 0) {
227 std::cerr << "No device address supplied" << std::endl;
228 return;
229 }
230
231 bool found = false;
232
233 ForeachDevice([&](AethercastInterfaceDevice *device) {
234 auto address = aethercast_interface_device_get_address(device);
235 std::cout << "Address: " << address << std::endl;
236
237 auto name = aethercast_interface_device_get_name(device);
238 std::cout << "Name: " << name << std::endl;
239
240 auto state = aethercast_interface_device_get_state(device);
241 std::cout << "State: " << state << std::endl;
242
243 auto capabilities = aethercast_interface_device_get_capabilities(device);
244 std::cout << "Capabilities: " << std::endl;
245 for (int n = 0; capabilities[n] != nullptr; n++)
246 std::cout << " " << capabilities[n] << std::endl;
247
248 found = true;
249 }, arguments);
250
251 if (!found)
252 std::cerr << "Unknown or invalid device address" << std::endl;
253}
254
255void Application::OnDeviceConnected(GObject *object, GAsyncResult *res, gpointer user_data) {
256 PromptSaver ps;
257
258 GError *error = nullptr;
259 if (!aethercast_interface_device_call_connect_finish(AETHERCAST_INTERFACE_DEVICE(object), res, &error)) {
260 std::cerr << "Failed to connect with device: " << error->message << std::endl;
261 g_error_free(error);
262 return;
263 }
264}
265
266void Application::HandleConnectCommand(const std::string &arguments) {
267 PromptSaver ps;
268
269 if (arguments.length() == 0) {
270 std::cerr << "No device address supplied" << std::endl;
271 return;
272 }
273
274 bool found = false;
275
276 ForeachDevice([&](AethercastInterfaceDevice *device) {
277
278 aethercast_interface_device_call_connect(device, "sink", nullptr,
279 &Application::OnDeviceConnected, nullptr);
280
281 found = true;
282 }, arguments);
283
284 if (!found)
285 std::cerr << "Unknown or invalid device address" << std::endl;
286}
287
288void Application::OnDeviceDisconnected(GObject *object, GAsyncResult *res, gpointer user_data) {
289 PromptSaver ps;
290
291 GError *error = nullptr;
292 if (!aethercast_interface_device_call_disconnect_finish(AETHERCAST_INTERFACE_DEVICE(object), res, &error)) {
293 std::cerr << "Failed to disconnect from device: " << error->message << std::endl;
294 g_error_free(error);
295 return;
296 }
297}
298
299void Application::HandleDisconnectCommand(const std::string &arguments) {
300 PromptSaver ps;
301
302 if (arguments.length() == 0) {
303 std::cerr << "No device address supplied" << std::endl;
304 return;
305 }
306
307 bool found = false;
308
309 ForeachDevice([&](AethercastInterfaceDevice *device) {
310
311 aethercast_interface_device_call_disconnect(device, nullptr,
312 &Application::OnDeviceDisconnected, nullptr);
313
314 found = true;
315 }, arguments);
316
317 if (!found)
318 std::cerr << "Unknown or invalid device address" << std::endl;
319}
320
321void Application::SetupStandardInput() {
322 GIOChannel *channel;
323
324 channel = g_io_channel_unix_new(fileno(stdin));
325
326 input_source_ = g_io_add_watch(channel,
327 (GIOCondition) (G_IO_IN | G_IO_HUP | G_IO_ERR | G_IO_NVAL),
328 &Application::OnUserInput, this);
329
330 g_io_channel_unref(channel);
331
332}
333
334gboolean Application::OnUserInput(GIOChannel *channel, GIOCondition condition, gpointer user_data) {
335 auto inst = static_cast<Application*>(user_data);
336
337 if (condition & G_IO_IN) {
338 rl_callback_read_char();
339 return TRUE;
340 }
341
342 if (condition & (G_IO_HUP | G_IO_ERR | G_IO_NVAL)) {
343 g_main_loop_quit(inst->main_loop_);
344 return FALSE;
345 }
346
347 return TRUE;
348}
349
350void Application::OnReadlineMessage(char *input) {
351 PromptSaver ps;
352
353 char *arg;
354 int i;
355
356 if (!input) {
357 rl_newline(1, '\n');
358 g_main_loop_quit(main_loop_);
359 return;
360 }
361
362 std::string cmd = strtok_r(input, " ", &arg) ? : "";
363 if (cmd.size() == 0)
364 return;
365
366 if (arg) {
367 int len = strlen(arg);
368 if (len > 0 && arg[len - 1] == ' ')
369 arg[len - 1] = '\0';
370 }
371
372 auto iter = available_commands_.find(cmd);
373 if (iter != available_commands_.end()) {
374 if (iter->second.func)
375 iter->second.func(arg);
376 }
377 else if (cmd == "help") {
378 std::cout << "Available commands:" << std::endl;
379
380 for (auto cmd : available_commands_) {
381 if (cmd.second.description.size() == 0)
382 continue;
383
384 fprintf(stdout, " %s %-*s %s\n", cmd.second.name.c_str(),
385 (int)(25 - cmd.second.name.length()),
386 cmd.second.arguments.c_str(),
387 cmd.second.description.c_str());
388 }
389 }
390 else {
391 std::cerr << "Invalid command" << std::endl;
392 }
393
394 g_free(input);
395}
396
397void Application::OnServiceLost(GDBusConnection *connection, const gchar *name, gpointer user_data) {
398 auto inst = static_cast<Application*>(user_data);
399
400 if (inst->input_source_ > 0) {
401 g_source_remove(inst->input_source_);
402 inst->input_source_ = 0;
403 }
404
405 if (inst->manager_) {
406 g_object_unref(inst->manager_);
407 inst->manager_ = nullptr;
408 }
409
410 if (inst->object_manager_) {
411 g_object_unref(inst->object_manager_);
412 inst->object_manager_ = nullptr;
413 }
414}
415
416void Application::OnServiceFound(GDBusConnection *connection, const gchar *name, const gchar *name_owner, gpointer user_data) {
417 auto inst = static_cast<Application*>(user_data);
418
419 aethercast_interface_manager_proxy_new(inst->bus_connection_, G_DBUS_PROXY_FLAGS_NONE,
420 "org.aethercast", "/org/aethercast", nullptr,
421 &Application::OnManagerConnected, inst);
422}
423
424void Application::OnManagerConnected(GObject *object, GAsyncResult *res, gpointer user_data) {
425 PromptSaver ps;
426
427 auto inst = static_cast<Application*>(user_data);
428
429 GError *error = nullptr;
430 inst->manager_ = aethercast_interface_manager_proxy_new_finish(res, &error);
431 if (!inst->manager_) {
432 std::cerr << "Could not connect with manager: " << error->message << std::endl;
433 g_error_free(error);
434 g_main_loop_quit(inst->main_loop_);
435 return;
436 }
437
438 // Use a high enough timeout to make sure we wait enough for the service to
439 // respond as the connection process can take some time depending on different
440 // factors.
441 g_dbus_proxy_set_default_timeout(G_DBUS_PROXY(inst->manager_), kDBusTimeout * 1000);
442
443 aethercast_interface_object_manager_client_new(inst->bus_connection_,
444 G_DBUS_OBJECT_MANAGER_CLIENT_FLAGS_NONE,
445 "org.aethercast", "/org/aethercast",
446 nullptr, &Application::OnObjectManagerCreated, inst);
447}
448
449void Application::OnManagerPropertiesChanged(GDBusConnection *connection, const gchar *sender_name, const gchar *object_path,
450 const gchar *interface_name, const gchar *signal_name, GVariant *parameters,
451 gpointer user_data) {
452 PromptSaver ps;
453
454 if (g_variant_n_children(parameters) != 3)
455 return;
456
457 GVariant *changed_props = g_variant_get_child_value(parameters, 1);
458 for (int n = 0; n < g_variant_n_children(changed_props); n++) {
459 auto element = g_variant_get_child_value(changed_props, n);
460 auto key_v = g_variant_get_child_value(element, 0);
461 auto value_v = g_variant_get_child_value(element, 1);
462
463 auto key = std::string(g_variant_get_string(key_v, nullptr));
464
465 std::cout << "[CHG] Manager " << key << " changed: ";
466
467 if (key == "Enabled")
468 std::cout << std::boolalpha << (bool) g_variant_get_boolean(g_variant_get_variant(value_v)) << std::endl;
469 else if (key == "State")
470 std::cout << g_variant_get_string(g_variant_get_variant(value_v), nullptr) << std::endl;
471 else if (key == "Scanning")
472 std::cout << std::boolalpha << (bool) g_variant_get_boolean(g_variant_get_variant(value_v)) << std::endl;
473 else if (key == "Capabilities") {
474 std::stringstream capabilities;
475
476 for (int m = 0; m < g_variant_n_children(value_v); m++) {
477 auto capability = g_variant_get_child_value(value_v, m);
478 if (!g_variant_is_of_type(capability, G_VARIANT_TYPE_STRING))
479 continue;
480
481 capabilities << g_variant_get_string(capability, nullptr) << " ";
482 }
483
484 std::cout << capabilities.str() << std::endl;
485 }
486 else
487 std::cout << "unknown" << std::endl;
488
489 g_variant_unref(key_v);
490 g_variant_unref(value_v);
491 }
492}
493
494void Application::OnDeviceAdded(GDBusObjectManager *manager, GDBusObject *object, gpointer user_data) {
495 PromptSaver ps;
496
497 auto iface = g_dbus_object_get_interface(G_DBUS_OBJECT(object), "org.aethercast.Device");
498 if (!iface)
499 return;
500
501 auto device = AETHERCAST_INTERFACE_DEVICE(iface);
502
503 auto address = aethercast_interface_device_get_address(device);
504 auto name = aethercast_interface_device_get_name(device);
505
506 std::cout << "Device " << address << " " << name << " added" << std::endl;
507}
508
509void Application::OnDeviceRemoved(GDBusObjectManager *manager, GDBusObject *object, gpointer user_data) {
510 PromptSaver ps;
511
512 auto iface = g_dbus_object_get_interface(G_DBUS_OBJECT(object), "org.aethercast.Device");
513 if (!iface)
514 return;
515
516 auto device = AETHERCAST_INTERFACE_DEVICE(iface);
517
518 auto address = aethercast_interface_device_get_address(device);
519 auto name = aethercast_interface_device_get_name(device);
520
521 std::cout << "Device " << address << " " << name <<" removed" << std::endl;
522}
523
524void Application::OnObjectManagerCreated(GObject *object, GAsyncResult *res, gpointer user_data) {
525 auto inst = static_cast<Application*>(user_data);
526
527 GError *error = nullptr;
528 inst->object_manager_ = aethercast_interface_object_manager_client_new_finish(res, &error);
529 if (!inst->object_manager_) {
530 g_error_free(error);
531 return;
532 }
533
534 g_signal_connect(inst->object_manager_, "object-added", G_CALLBACK(&Application::OnDeviceAdded), inst);
535 g_signal_connect(inst->object_manager_, "object-removed", G_CALLBACK(&Application::OnDeviceRemoved), inst);
536
537 inst->SetupStandardInput();
538}
539
540} // namespace client
541} // namespace ac
diff --git a/src/ac/client/application.h b/src/ac/client/application.h
0new file mode 100644542new file mode 100644
index 0000000..a7b3d5a
--- /dev/null
+++ b/src/ac/client/application.h
@@ -0,0 +1,112 @@
1/*
2 * Copyright (C) 2015 Canonical, Ltd.
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
17 *
18 */
19
20#ifndef APPLICATION_H_
21#define APPLICATION_H_
22
23#include <map>
24#include <functional>
25
26#include "ac/glib_wrapper.h"
27
28extern "C" {
29// Ignore all warnings coming from the external headers as we don't
30// control them and also don't want to get any warnings from them
31// which will only pollute our build output.
32#pragma GCC diagnostic push
33#pragma GCC diagnostic warning "-w"
34#include "aethercastinterface.h"
35#pragma GCC diagnostic pop
36}
37
38namespace ac {
39namespace client {
40
41class Application {
42public:
43 struct MainOptions {};
44
45 static int Main(const MainOptions &options);
46
47 Application();
48 ~Application();
49
50 int Run();
51
52private:
53 struct Command {
54 std::string name;
55 std::string arguments;
56 std::string description;
57 std::function<void(std::string)> func;
58 };
59
60 void HandleEnableCommand(const std::string &arguments);
61 void HandleDisableCommand(const std::string &arguments);
62 void HandleShowCommand(const std::string &arguments);
63 void HandleScanCommand(const std::string &arguments);
64 void HandleDevicesCommand(const std::string &arguments);
65 void HandleInfoCommand(const std::string &arguments);
66 void HandleConnectCommand(const std::string &arguments);
67 void HandleDisconnectCommand(const std::string &arguments);
68
69 void RegisterCommand(const Command &command);
70
71private:
72 static gboolean OnUserInput(GIOChannel *channel, GIOCondition condition, gpointer user_data);
73 static void OnReadlineMessage(char *input);
74 static void OnServiceLost(GDBusConnection *connection, const gchar *name, gpointer user_data);
75 static void OnServiceFound(GDBusConnection *connection, const gchar *name, const gchar *name_owner, gpointer user_data);
76 static void OnManagerConnected(GObject *object, GAsyncResult *res, gpointer user_data);
77 static void OnDeviceAdded(GDBusObjectManager *manager, GDBusObject *object, gpointer user_data);
78 static void OnDeviceRemoved(GDBusObjectManager *manager, GDBusObject *object, gpointer user_data);
79 static void OnObjectManagerCreated(GObject *object, GAsyncResult *res, gpointer user_data);
80
81 static void OnManagerPropertiesChanged(GDBusConnection *connection,
82 const gchar *sender_name,
83 const gchar *object_path,
84 const gchar *interface_name,
85 const gchar *signal_name,
86 GVariant *parameters,
87 gpointer user_data);
88
89 static void OnScanDone(GObject *object, GAsyncResult *res, gpointer user_data);
90 static void OnDeviceConnected(GObject *object, GAsyncResult *res, gpointer user_data);
91 static void OnDeviceDisconnected(GObject *object, GAsyncResult *res, gpointer user_data);
92private:
93 void SetupStandardInput();
94 void ForeachDevice(std::function<void(AethercastInterfaceDevice*)> callback, const std::string &address_filter = "");
95
96private:
97 GDBusConnection *bus_connection_;
98 AethercastInterfaceManager *manager_;
99 guint input_source_;
100 GDBusObjectManager *object_manager_;
101
102private:
103 // Needs to be static otherwise we can't access it from the readline
104 // callback function (which sadly doesn't allow passing any user data)
105 static GMainLoop *main_loop_;
106 static std::map<std::string,Command> available_commands_;
107};
108
109} // namespace client
110} // namespace ac
111
112#endif
diff --git a/src/ac/cmds/service.cpp b/src/ac/cmds/service.cpp
0new file mode 100644113new file mode 100644
index 0000000..36c146e
--- /dev/null
+++ b/src/ac/cmds/service.cpp
@@ -0,0 +1,28 @@
1/*
2 * Copyright (C) 2017 Simon Fels <morphis@gravedo.de>
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU Lesser General Public License as published by
6 * the Free Software Foundation; version 3.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU Lesser General Public License for more details.
12 *
13 * You should have received a copy of the GNU Lesser General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 *
16 */
17
18#include "ac/cmds/service.h"
19#include "ac/service.h"
20
21ac::cmds::Service::Service()
22 : CommandWithFlagsAndAction{
23 cli::Name{"service"}, cli::Usage{"service"},
24 cli::Description{"Start the background service"}} {
25 action([](const cli::Command::Context&) {
26 return ac::Service::Main(ac::Service::MainOptions{});
27 });
28}
diff --git a/src/ac/cmds/service.h b/src/ac/cmds/service.h
0new file mode 10064429new file mode 100644
index 0000000..541a868
--- /dev/null
+++ b/src/ac/cmds/service.h
@@ -0,0 +1,32 @@
1/*
2 * Copyright (C) 2017 Canonical, Ltd.
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU Lesser General Public License as published by
6 * the Free Software Foundation; version 3.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU Lesser General Public License for more details.
12 *
13 * You should have received a copy of the GNU Lesser General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 *
16 */
17
18#ifndef AETHERCAST_CMDS_SERVICE_H_
19#define AETHERCAST_CMDS_SERVICE_H_
20
21#include "ac/cli.h"
22
23namespace ac {
24namespace cmds {
25class Service : public cli::CommandWithFlagsAndAction {
26public:
27 Service();
28};
29} // namespace cmds
30} // namespace ac
31
32#endif
diff --git a/src/ac/cmds/shell.cpp b/src/ac/cmds/shell.cpp
0new file mode 10064433new file mode 100644
index 0000000..b3e1127
--- /dev/null
+++ b/src/ac/cmds/shell.cpp
@@ -0,0 +1,28 @@
1/*
2 * Copyright (C) 2017 Canonical, Ltd.
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU Lesser General Public License as published by
6 * the Free Software Foundation; version 3.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU Lesser General Public License for more details.
12 *
13 * You should have received a copy of the GNU Lesser General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 *
16 */
17
18#include "ac/cmds/shell.h"
19#include "ac/client/application.h"
20
21ac::cmds::Shell::Shell()
22 : CommandWithFlagsAndAction{
23 cli::Name{"shell"}, cli::Usage{"shell"},
24 cli::Description{"Start the interactive shell"}} {
25 action([](const cli::Command::Context&) {
26 return ac::client::Application::Main(ac::client::Application::MainOptions{});
27 });
28}
diff --git a/src/ac/cmds/shell.h b/src/ac/cmds/shell.h
0new file mode 10064429new file mode 100644
index 0000000..a4d905b
--- /dev/null
+++ b/src/ac/cmds/shell.h
@@ -0,0 +1,32 @@
1/*
2 * Copyright (C) 2017 Canonical, Ltd.
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU Lesser General Public License as published by
6 * the Free Software Foundation; version 3.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU Lesser General Public License for more details.
12 *
13 * You should have received a copy of the GNU Lesser General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 *
16 */
17
18#ifndef AETHERCAST_CMDS_SHELL_H_
19#define AETHERCAST_CMDS_SHELL_H_
20
21#include "ac/cli.h"
22
23namespace ac {
24namespace cmds {
25class Shell : public cli::CommandWithFlagsAndAction {
26public:
27 Shell();
28};
29} // namespace cmds
30} // namespace ac
31
32#endif
diff --git a/src/ac/do_not_copy_or_move.h b/src/ac/do_not_copy_or_move.h
0new file mode 10064433new file mode 100644
index 0000000..944c649
--- /dev/null
+++ b/src/ac/do_not_copy_or_move.h
@@ -0,0 +1,36 @@
1/*
2 * Copyright (C) 2017 Canonical, Ltd.
3 *
4 * This program is free software: you can redistribute it and/or modify it
5 * under the terms of the GNU General Public License version 3, as published
6 * by the Free Software Foundation.
7 *
8 * This program is distributed in the hope that it will be useful, but
9 * WITHOUT ANY WARRANTY; without even the implied warranties of
10 * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
11 * PURPOSE. See the GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License along
14 * with this program. If not, see <http://www.gnu.org/licenses/>.
15 *
16 */
17
18#ifndef AETHERCAST_DO_NOT_COPY_OR_MOVE_H_
19#define AETHERCAST_DO_NOT_COPY_OR_MOVE_H_
20
21namespace ac {
22
23class DoNotCopyOrMove {
24 public:
25 DoNotCopyOrMove(const DoNotCopyOrMove&) = delete;
26 DoNotCopyOrMove(DoNotCopyOrMove&&) = delete;
27 virtual ~DoNotCopyOrMove() = default;
28 DoNotCopyOrMove& operator=(const DoNotCopyOrMove&) = delete;
29 DoNotCopyOrMove& operator=(DoNotCopyOrMove&&) = delete;
30
31 protected:
32 DoNotCopyOrMove() = default;
33};
34}
35
36#endif
diff --git a/src/ac/main.cpp b/src/ac/main.cpp
index 8fa7842..7fa1990 100644
--- a/src/ac/main.cpp
+++ b/src/ac/main.cpp
@@ -15,8 +15,21 @@
15 *15 *
16 */16 */
1717
18#include "service.h"18#include "ac/cli.h"
19#include "ac/utils.h"
20#include "ac/cmds/shell.h"
21#include "ac/cmds/service.h"
1922
20int main(int argc, char **argv) {23int main(int argc, char **argv) {
21 return ac::Service::Main(ac::Service::MainOptions::FromCommandLine(argc, argv));24 ac::cli::CommandWithSubcommands cmd{ac::cli::Name{"aethercast"},
25 ac::cli::Usage{"aethercast"},
26 ac::cli::Description{"The Display Casting Service"}};
27 cmd.command(std::make_shared<ac::cmds::Shell>());
28 cmd.command(std::make_shared<ac::cmds::Service>());
29
30 auto arguments = ac::Utils::CollectArguments(argc, argv);
31 if (arguments.size() == 0)
32 arguments = {"shell"};
33
34 return cmd.run({std::cin, std::cout, arguments});
22}35}
diff --git a/src/ac/optional.h b/src/ac/optional.h
23new file mode 10064436new file mode 100644
index 0000000..56b0e55
--- /dev/null
+++ b/src/ac/optional.h
@@ -0,0 +1,31 @@
1/*
2 * Copyright (C) 2016 Canonical, Ltd.
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU Lesser General Public License as published by
6 * the Free Software Foundation; version 3.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU Lesser General Public License for more details.
12 *
13 * You should have received a copy of the GNU Lesser General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 *
16 * Authored by: Thomas Voß <thomas.voss@canonical.com>
17 *
18 */
19
20#ifndef AETHERCAST_OPTIONAL_H_
21#define AETHERCAST_OPTIONAL_H_
22
23#include <boost/optional.hpp>
24#include <boost/optional/optional_io.hpp>
25
26namespace ac {
27template <typename T>
28using Optional = boost::optional<T>;
29}
30
31#endif
diff --git a/src/ac/service.cpp b/src/ac/service.cpp
index 2293560..76b5494 100644
--- a/src/ac/service.cpp
+++ b/src/ac/service.cpp
@@ -62,33 +62,6 @@ void SafeLog (const char *format, ...)
62}62}
63}63}
64namespace ac {64namespace ac {
65Service::MainOptions Service::MainOptions::FromCommandLine(int argc, char** argv) {
66 static gboolean option_debug{FALSE};
67 static gboolean option_version{FALSE};
68
69 static GOptionEntry options[] = {
70 { "debug", 'd', 0, G_OPTION_ARG_NONE, &option_debug, "Enable debugging mode", nullptr },
71 { "version", 'v', 0, G_OPTION_ARG_NONE, &option_version, "Show version information and exit", nullptr },
72 { NULL },
73 };
74
75 std::shared_ptr<GOptionContext> context{g_option_context_new(nullptr), [](GOptionContext *ctxt) { g_option_context_free(ctxt); }};
76 GError *error = nullptr;
77
78 g_option_context_add_main_entries(context.get(), options, NULL);
79
80 if (!g_option_context_parse(context.get(), &argc, &argv, &error)) {
81 if (error) {
82 g_printerr("%s\n", error->message);
83 g_error_free(error);
84 } else
85 g_printerr("An unknown error occurred\n");
86 exit(1);
87 }
88
89 return MainOptions{option_debug == TRUE, option_version == TRUE};
90}
91
92int Service::Main(const Service::MainOptions &options) {65int Service::Main(const Service::MainOptions &options) {
93 if (options.print_version) {66 if (options.print_version) {
94 std::printf("%d.%d\n", Service::kVersionMajor, Service::kVersionMinor);67 std::printf("%d.%d\n", Service::kVersionMajor, Service::kVersionMinor);
diff --git a/src/ac/service.h b/src/ac/service.h
index 94ecb4c..fd23d16 100644
--- a/src/ac/service.h
+++ b/src/ac/service.h
@@ -46,8 +46,6 @@ public:
46 typedef std::shared_ptr<Service> Ptr;46 typedef std::shared_ptr<Service> Ptr;
4747
48 struct MainOptions {48 struct MainOptions {
49 static MainOptions FromCommandLine(int argc, char** argv);
50
51 bool debug;49 bool debug;
52 bool print_version;50 bool print_version;
53 };51 };
diff --git a/src/ac/utils.cpp b/src/ac/utils.cpp
index c89c3a9..0bf497c 100644
--- a/src/ac/utils.cpp
+++ b/src/ac/utils.cpp
@@ -30,6 +30,12 @@
30#include "utils.h"30#include "utils.h"
3131
32namespace ac {32namespace ac {
33std::vector<std::string> Utils::CollectArguments(int argc, char **argv) {
34 std::vector<std::string> result;
35 for (int i = 1; i < argc; i++) result.push_back(argv[i]);
36 return result;
37}
38
33bool Utils::StringStartsWith(const std::string &text, const std::string &prefix) {39bool Utils::StringStartsWith(const std::string &text, const std::string &prefix) {
34 return text.compare(0, prefix.size(), prefix) == 0;40 return text.compare(0, prefix.size(), prefix) == 0;
35}41}
diff --git a/src/ac/utils.h b/src/ac/utils.h
index 847d7d2..d649e8f 100644
--- a/src/ac/utils.h
+++ b/src/ac/utils.h
@@ -33,6 +33,8 @@ struct Utils
33 // Merely used as a namespace.33 // Merely used as a namespace.
34 Utils() = delete;34 Utils() = delete;
3535
36 // Convert plain array into std::vector with strings
37 static std::vector<std::string> CollectArguments(int argc, char **argv);
36 // StringStartsWith returns true iff text[0:prefix.size()-1] == prefix.38 // StringStartsWith returns true iff text[0:prefix.size()-1] == prefix.
37 static bool StringStartsWith(const std::string &text, const std::string &prefix);39 static bool StringStartsWith(const std::string &text, const std::string &prefix);
38 // ParseHex parses an integer with base 16 from str.40 // ParseHex parses an integer with base 16 from str.
diff --git a/src/client/CMakeLists.txt b/src/client/CMakeLists.txt
39deleted file mode 10064441deleted file mode 100644
index 2d8588e..0000000
--- a/src/client/CMakeLists.txt
+++ /dev/null
@@ -1,36 +0,0 @@
1set(SOURCES
2 main.cpp
3 application.cpp
4)
5
6link_directories(
7 ${GLIB_LIBRARY_DIRS}
8 ${GIO_LIBRARY_DIRS}
9 ${GIO-UNIX_LIBRARY_DIRS}
10)
11
12include_directories(
13 ${Boost_INCLUDE_DIRS}
14 ${GLIB_INCLUDE_DIRS}
15 ${GIO_INCLUDE_DIRS}
16 ${GIO-UNIX_INCLUDE_DIRS}
17 ${READLINE_INCLUDE_DIRS}
18 ${CMAKE_CURRENT_SOURCE_DIR}/../
19 ${CMAKE_CURRENT_BINARY_DIR}/src
20)
21
22add_executable(aethercastctl ${SOURCES})
23target_link_libraries(aethercastctl
24 aethercast-gdbus-wrapper
25 ${Boost_LIBRARIES}
26 ${GLIB_LIBRARIES}
27 ${GIO_LIBRARIES}
28 ${GIO-UNIX_LIBRARIES}
29 ${READLINE_LIBRARIES}
30 -ldl
31)
32
33install(
34 TARGETS aethercastctl
35 RUNTIME DESTINATION bin
36)
diff --git a/src/client/application.cpp b/src/client/application.cpp
37deleted file mode 1006440deleted file mode 100644
index 5471586..0000000
--- a/src/client/application.cpp
+++ /dev/null
@@ -1,564 +0,0 @@
1/*
2 * Copyright (C) 2015 Canonical, Ltd.
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
17 *
18 */
19
20#include <memory>
21#include <functional>
22#include <iostream>
23#include <string>
24#include <sstream>
25
26#include <readline/readline.h>
27#include <readline/history.h>
28
29#include "ac/glib_wrapper.h"
30
31#include "application.h"
32
33using namespace std::placeholders;
34
35namespace {
36const unsigned int kDBusTimeout = 120;
37
38class PromptSaver {
39public:
40 PromptSaver() {
41 save_input_ = !RL_ISSTATE(RL_STATE_DONE);
42 if (save_input_) {
43 saved_point_ = rl_point;
44 saved_line_ = rl_copy_text(0, rl_end);
45 rl_save_prompt();
46 rl_replace_line("", 0);
47 rl_redisplay();
48 }
49 }
50
51 ~PromptSaver() {
52 if (save_input_) {
53 rl_restore_prompt();
54 rl_replace_line(saved_line_, 0);
55 rl_point = saved_point_;
56 rl_redisplay();
57 g_free(saved_line_);
58 }
59 }
60
61private:
62 bool save_input_;
63 int saved_point_;
64 char *saved_line_;
65};
66}
67
68namespace ac {
69namespace client {
70
71GMainLoop *Application::main_loop_ = nullptr;
72std::map<std::string,Application::Command> Application::available_commands_ = { };
73
74Application::MainOptions Application::MainOptions::FromCommandLine(int argc, char** argv) {
75
76 static GOptionEntry options[] = {
77 { NULL },
78 };
79
80 std::shared_ptr<GOptionContext> context{g_option_context_new(nullptr), [](GOptionContext *ctxt) { g_option_context_free(ctxt); }};
81 GError *error = nullptr;
82
83 g_option_context_add_main_entries(context.get(), options, NULL);
84
85 if (!g_option_context_parse(context.get(), &argc, &argv, &error)) {
86 if (error) {
87 std::cerr << error->message << std::endl;
88 g_error_free(error);
89 } else
90 std::cerr << "An unknown error occured" << std::endl;
91 exit(1);
92 }
93
94 return MainOptions{};
95}
96
97int Application::Main(const MainOptions &options) {
98 Application app;
99 return app.Run();
100}
101
102Application::Application() :
103 bus_connection_(nullptr),
104 manager_(nullptr),
105 input_source_(0),
106 object_manager_(nullptr) {
107
108 rl_callback_handler_install("aethercastctl> ", &Application::OnReadlineMessage);
109
110 main_loop_ = g_main_loop_new(nullptr, FALSE);
111
112 GError *error = nullptr;
113 bus_connection_ = g_bus_get_sync(G_BUS_TYPE_SYSTEM, NULL, &error);
114 if (!bus_connection_) {
115 std::cerr << "Failed to connect to system bus: " << error->message << std::endl;
116 g_error_free(error);
117 return;
118 }
119
120 g_bus_watch_name_on_connection(bus_connection_, "org.aethercast",
121 G_BUS_NAME_WATCHER_FLAGS_NONE,
122 &Application::OnServiceFound,
123 &Application::OnServiceLost,
124 this, nullptr);
125
126 g_dbus_connection_signal_subscribe(bus_connection_, "org.aethercast", "org.freedesktop.DBus.Properties", "PropertiesChanged", "/org/aethercast", nullptr,
127 G_DBUS_SIGNAL_FLAGS_NONE, &Application::OnManagerPropertiesChanged, this, nullptr);
128
129 RegisterCommand(Command { "enable", "", "Enable manager", std::bind(&Application::HandleEnableCommand, this, _1) });
130 RegisterCommand(Command { "disable", "", "Disable manager", std::bind(&Application::HandleDisableCommand, this, _1) });
131 RegisterCommand(Command { "show", "", "Show manager properties", std::bind(&Application::HandleShowCommand, this, _1) });
132 RegisterCommand(Command { "scan", "", "Scan for devices", std::bind(&Application::HandleScanCommand, this, _1) });
133 RegisterCommand(Command { "devices", "", "List available devices", std::bind(&Application::HandleDevicesCommand, this, _1) });
134 RegisterCommand(Command { "info", "<address>", "Show device information", std::bind(&Application::HandleInfoCommand, this, _1) });
135 RegisterCommand(Command { "connect", "<address>", "Connect a device", std::bind(&Application::HandleConnectCommand, this, _1) });
136 RegisterCommand(Command { "disconnect", "<address>", "Disconnect a device", std::bind(&Application::HandleDisconnectCommand, this, _1) });
137}
138
139Application::~Application() {
140 rl_message();
141 rl_callback_handler_remove();
142
143 g_object_unref(bus_connection_);
144 g_main_loop_unref(main_loop_);
145
146 if (manager_)
147 g_object_unref(manager_);
148
149 if (input_source_ > 0)
150 g_source_remove(input_source_);
151}
152
153int Application::Run() {
154 g_main_loop_run(main_loop_);
155 return 0;
156}
157
158void Application::RegisterCommand(const Command &command) {
159 available_commands_[command.name] = command;
160}
161
162void Application::HandleEnableCommand(const std::string &arguments) {
163 if (!manager_)
164 return;
165
166 aethercast_interface_manager_set_enabled(manager_, true);
167}
168
169void Application::HandleDisableCommand(const std::string &arguments) {
170 if (!manager_)
171 return;
172
173 aethercast_interface_manager_set_enabled(manager_, false);
174}
175
176void Application::HandleShowCommand(const std::string &arguments) {
177 if (!manager_)
178 return;
179
180 auto enabled = aethercast_interface_manager_get_enabled(manager_);
181 std::cout << "Enabled: " << std::boolalpha << (bool) enabled << std::endl;
182
183 auto state = aethercast_interface_manager_get_state(manager_);
184 std::cout << "State: " << state << std::endl;
185
186 auto scanning = aethercast_interface_manager_get_scanning(manager_);
187 std::cout << "Scanning: " << std::boolalpha << (bool) scanning << std::endl;
188
189 auto capabilities = aethercast_interface_manager_get_capabilities(manager_);
190 std::cout << "Capabilities:" << std::endl;
191 for (int n = 0; capabilities[n] != nullptr; n++)
192 std::cout << " " << capabilities[n] << std::endl;
193}
194
195void Application::OnScanDone(GObject *object, GAsyncResult *res, gpointer user_data) {
196 auto inst = static_cast<Application*>(user_data);
197
198 GError *error = nullptr;
199 if (!aethercast_interface_manager_call_scan_finish(inst->manager_, res, &error)) {
200 std::cerr << "Failed to scan:" << error->message << std::endl;
201 g_error_free(error);
202 return;
203 }
204}
205
206void Application::HandleScanCommand(const std::string &arguments) {
207 if (!manager_)
208 return;
209
210 aethercast_interface_manager_call_scan(manager_, nullptr, &Application::OnScanDone, this);
211}
212
213void Application::ForeachDevice(std::function<void(AethercastInterfaceDevice*)> callback, const std::string &address_filter) {
214 if (!callback)
215 return;
216
217 auto objects = g_dbus_object_manager_get_objects(object_manager_);
218
219 for (auto obj = objects; obj != nullptr; obj = obj->next) {
220 auto device = AETHERCAST_INTERFACE_OBJECT_PROXY(obj->data);
221 if (!device)
222 continue;
223
224 auto iface = g_dbus_object_get_interface(G_DBUS_OBJECT(device), "org.aethercast.Device");
225 if (!iface)
226 continue;
227
228 auto device_obj = AETHERCAST_INTERFACE_DEVICE(iface);
229
230 if (address_filter.length() > 0) {
231 auto device_address = aethercast_interface_device_get_address(device_obj);
232 if (address_filter != device_address)
233 continue;
234 }
235
236 callback(device_obj);
237 }
238}
239
240void Application::HandleDevicesCommand(const std::string &arguments) {
241 ForeachDevice([=](AethercastInterfaceDevice *device) {
242 auto address = aethercast_interface_device_get_address(device);
243 auto name = aethercast_interface_device_get_name(device);
244 std::cout << "Device " << address << " " << name << std::endl;
245 });
246}
247
248void Application::HandleInfoCommand(const std::string &arguments) {
249 if (arguments.length() == 0) {
250 std::cerr << "No device address supplied" << std::endl;
251 return;
252 }
253
254 bool found = false;
255
256 ForeachDevice([&](AethercastInterfaceDevice *device) {
257 auto address = aethercast_interface_device_get_address(device);
258 std::cout << "Address: " << address << std::endl;
259
260 auto name = aethercast_interface_device_get_name(device);
261 std::cout << "Name: " << name << std::endl;
262
263 auto state = aethercast_interface_device_get_state(device);
264 std::cout << "State: " << state << std::endl;
265
266 auto capabilities = aethercast_interface_device_get_capabilities(device);
267 std::cout << "Capabilities: " << std::endl;
268 for (int n = 0; capabilities[n] != nullptr; n++)
269 std::cout << " " << capabilities[n] << std::endl;
270
271 found = true;
272 }, arguments);
273
274 if (!found)
275 std::cerr << "Unknown or invalid device address" << std::endl;
276}
277
278void Application::OnDeviceConnected(GObject *object, GAsyncResult *res, gpointer user_data) {
279 PromptSaver ps;
280
281 GError *error = nullptr;
282 if (!aethercast_interface_device_call_connect_finish(AETHERCAST_INTERFACE_DEVICE(object), res, &error)) {
283 std::cerr << "Failed to connect with device: " << error->message << std::endl;
284 g_error_free(error);
285 return;
286 }
287}
288
289void Application::HandleConnectCommand(const std::string &arguments) {
290 PromptSaver ps;
291
292 if (arguments.length() == 0) {
293 std::cerr << "No device address supplied" << std::endl;
294 return;
295 }
296
297 bool found = false;
298
299 ForeachDevice([&](AethercastInterfaceDevice *device) {
300
301 aethercast_interface_device_call_connect(device, "sink", nullptr,
302 &Application::OnDeviceConnected, nullptr);
303
304 found = true;
305 }, arguments);
306
307 if (!found)
308 std::cerr << "Unknown or invalid device address" << std::endl;
309}
310
311void Application::OnDeviceDisconnected(GObject *object, GAsyncResult *res, gpointer user_data) {
312 PromptSaver ps;
313
314 GError *error = nullptr;
315 if (!aethercast_interface_device_call_disconnect_finish(AETHERCAST_INTERFACE_DEVICE(object), res, &error)) {
316 std::cerr << "Failed to disconnect from device: " << error->message << std::endl;
317 g_error_free(error);
318 return;
319 }
320}
321
322void Application::HandleDisconnectCommand(const std::string &arguments) {
323 PromptSaver ps;
324
325 if (arguments.length() == 0) {
326 std::cerr << "No device address supplied" << std::endl;
327 return;
328 }
329
330 bool found = false;
331
332 ForeachDevice([&](AethercastInterfaceDevice *device) {
333
334 aethercast_interface_device_call_disconnect(device, nullptr,
335 &Application::OnDeviceDisconnected, nullptr);
336
337 found = true;
338 }, arguments);
339
340 if (!found)
341 std::cerr << "Unknown or invalid device address" << std::endl;
342}
343
344void Application::SetupStandardInput() {
345 GIOChannel *channel;
346
347 channel = g_io_channel_unix_new(fileno(stdin));
348
349 input_source_ = g_io_add_watch(channel,
350 (GIOCondition) (G_IO_IN | G_IO_HUP | G_IO_ERR | G_IO_NVAL),
351 &Application::OnUserInput, this);
352
353 g_io_channel_unref(channel);
354
355}
356
357gboolean Application::OnUserInput(GIOChannel *channel, GIOCondition condition, gpointer user_data) {
358 auto inst = static_cast<Application*>(user_data);
359
360 if (condition & G_IO_IN) {
361 rl_callback_read_char();
362 return TRUE;
363 }
364
365 if (condition & (G_IO_HUP | G_IO_ERR | G_IO_NVAL)) {
366 g_main_loop_quit(inst->main_loop_);
367 return FALSE;
368 }
369
370 return TRUE;
371}
372
373void Application::OnReadlineMessage(char *input) {
374 PromptSaver ps;
375
376 char *arg;
377 int i;
378
379 if (!input) {
380 rl_newline(1, '\n');
381 g_main_loop_quit(main_loop_);
382 return;
383 }
384
385 std::string cmd = strtok_r(input, " ", &arg) ? : "";
386 if (cmd.size() == 0)
387 return;
388
389 if (arg) {
390 int len = strlen(arg);
391 if (len > 0 && arg[len - 1] == ' ')
392 arg[len - 1] = '\0';
393 }
394
395 auto iter = available_commands_.find(cmd);
396 if (iter != available_commands_.end()) {
397 if (iter->second.func)
398 iter->second.func(arg);
399 }
400 else if (cmd == "help") {
401 std::cout << "Available commands:" << std::endl;
402
403 for (auto cmd : available_commands_) {
404 if (cmd.second.description.size() == 0)
405 continue;
406
407 fprintf(stdout, " %s %-*s %s\n", cmd.second.name.c_str(),
408 (int)(25 - cmd.second.name.length()),
409 cmd.second.arguments.c_str(),
410 cmd.second.description.c_str());
411 }
412 }
413 else {
414 std::cerr << "Invalid command" << std::endl;
415 }
416
417 g_free(input);
418}
419
420void Application::OnServiceLost(GDBusConnection *connection, const gchar *name, gpointer user_data) {
421 auto inst = static_cast<Application*>(user_data);
422
423 if (inst->input_source_ > 0) {
424 g_source_remove(inst->input_source_);
425 inst->input_source_ = 0;
426 }
427
428 if (inst->manager_) {
429 g_object_unref(inst->manager_);
430 inst->manager_ = nullptr;
431 }
432
433 if (inst->object_manager_) {
434 g_object_unref(inst->object_manager_);
435 inst->object_manager_ = nullptr;
436 }
437}
438
439void Application::OnServiceFound(GDBusConnection *connection, const gchar *name, const gchar *name_owner, gpointer user_data) {
440 auto inst = static_cast<Application*>(user_data);
441
442 aethercast_interface_manager_proxy_new(inst->bus_connection_, G_DBUS_PROXY_FLAGS_NONE,
443 "org.aethercast", "/org/aethercast", nullptr,
444 &Application::OnManagerConnected, inst);
445}
446
447void Application::OnManagerConnected(GObject *object, GAsyncResult *res, gpointer user_data) {
448 PromptSaver ps;
449
450 auto inst = static_cast<Application*>(user_data);
451
452 GError *error = nullptr;
453 inst->manager_ = aethercast_interface_manager_proxy_new_finish(res, &error);
454 if (!inst->manager_) {
455 std::cerr << "Could not connect with manager: " << error->message << std::endl;
456 g_error_free(error);
457 g_main_loop_quit(inst->main_loop_);
458 return;
459 }
460
461 // Use a high enough timeout to make sure we wait enough for the service to
462 // respond as the connection process can take some time depending on different
463 // factors.
464 g_dbus_proxy_set_default_timeout(G_DBUS_PROXY(inst->manager_), kDBusTimeout * 1000);
465
466 aethercast_interface_object_manager_client_new(inst->bus_connection_,
467 G_DBUS_OBJECT_MANAGER_CLIENT_FLAGS_NONE,
468 "org.aethercast", "/org/aethercast",
469 nullptr, &Application::OnObjectManagerCreated, inst);
470}
471
472void Application::OnManagerPropertiesChanged(GDBusConnection *connection, const gchar *sender_name, const gchar *object_path,
473 const gchar *interface_name, const gchar *signal_name, GVariant *parameters,
474 gpointer user_data) {
475 PromptSaver ps;
476
477 if (g_variant_n_children(parameters) != 3)
478 return;
479
480 GVariant *changed_props = g_variant_get_child_value(parameters, 1);
481 for (int n = 0; n < g_variant_n_children(changed_props); n++) {
482 auto element = g_variant_get_child_value(changed_props, n);
483 auto key_v = g_variant_get_child_value(element, 0);
484 auto value_v = g_variant_get_child_value(element, 1);
485
486 auto key = std::string(g_variant_get_string(key_v, nullptr));
487
488 std::cout << "[CHG] Manager " << key << " changed: ";
489
490 if (key == "Enabled")
491 std::cout << std::boolalpha << (bool) g_variant_get_boolean(g_variant_get_variant(value_v)) << std::endl;
492 else if (key == "State")
493 std::cout << g_variant_get_string(g_variant_get_variant(value_v), nullptr) << std::endl;
494 else if (key == "Scanning")
495 std::cout << std::boolalpha << (bool) g_variant_get_boolean(g_variant_get_variant(value_v)) << std::endl;
496 else if (key == "Capabilities") {
497 std::stringstream capabilities;
498
499 for (int m = 0; m < g_variant_n_children(value_v); m++) {
500 auto capability = g_variant_get_child_value(value_v, m);
501 if (!g_variant_is_of_type(capability, G_VARIANT_TYPE_STRING))
502 continue;
503
504 capabilities << g_variant_get_string(capability, nullptr) << " ";
505 }
506
507 std::cout << capabilities.str() << std::endl;
508 }
509 else
510 std::cout << "unknown" << std::endl;
511
512 g_variant_unref(key_v);
513 g_variant_unref(value_v);
514 }
515}
516
517void Application::OnDeviceAdded(GDBusObjectManager *manager, GDBusObject *object, gpointer user_data) {
518 PromptSaver ps;
519
520 auto iface = g_dbus_object_get_interface(G_DBUS_OBJECT(object), "org.aethercast.Device");
521 if (!iface)
522 return;
523
524 auto device = AETHERCAST_INTERFACE_DEVICE(iface);
525
526 auto address = aethercast_interface_device_get_address(device);
527 auto name = aethercast_interface_device_get_name(device);
528
529 std::cout << "Device " << address << " " << name << " added" << std::endl;
530}
531
532void Application::OnDeviceRemoved(GDBusObjectManager *manager, GDBusObject *object, gpointer user_data) {
533 PromptSaver ps;
534
535 auto iface = g_dbus_object_get_interface(G_DBUS_OBJECT(object), "org.aethercast.Device");
536 if (!iface)
537 return;
538
539 auto device = AETHERCAST_INTERFACE_DEVICE(iface);
540
541 auto address = aethercast_interface_device_get_address(device);
542 auto name = aethercast_interface_device_get_name(device);
543
544 std::cout << "Device " << address << " " << name <<" removed" << std::endl;
545}
546
547void Application::OnObjectManagerCreated(GObject *object, GAsyncResult *res, gpointer user_data) {
548 auto inst = static_cast<Application*>(user_data);
549
550 GError *error = nullptr;
551 inst->object_manager_ = aethercast_interface_object_manager_client_new_finish(res, &error);
552 if (!inst->object_manager_) {
553 g_error_free(error);
554 return;
555 }
556
557 g_signal_connect(inst->object_manager_, "object-added", G_CALLBACK(&Application::OnDeviceAdded), inst);
558 g_signal_connect(inst->object_manager_, "object-removed", G_CALLBACK(&Application::OnDeviceRemoved), inst);
559
560 inst->SetupStandardInput();
561}
562
563} // namespace client
564} // namespace ac
diff --git a/src/client/application.h b/src/client/application.h
565deleted file mode 1006440deleted file mode 100644
index 9596110..0000000
--- a/src/client/application.h
+++ /dev/null
@@ -1,114 +0,0 @@
1/*
2 * Copyright (C) 2015 Canonical, Ltd.
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
17 *
18 */
19
20#ifndef APPLICATION_H_
21#define APPLICATION_H_
22
23#include <map>
24#include <functional>
25
26#include "ac/glib_wrapper.h"
27
28extern "C" {
29// Ignore all warnings coming from the external headers as we don't
30// control them and also don't want to get any warnings from them
31// which will only pollute our build output.
32#pragma GCC diagnostic push
33#pragma GCC diagnostic warning "-w"
34#include "aethercastinterface.h"
35#pragma GCC diagnostic pop
36}
37
38namespace ac {
39namespace client {
40
41class Application {
42public:
43 struct MainOptions {
44 static MainOptions FromCommandLine(int argc, char** argv);
45 };
46
47 static int Main(const MainOptions &options);
48
49 Application();
50 ~Application();
51
52 int Run();
53
54private:
55 struct Command {
56 std::string name;
57 std::string arguments;
58 std::string description;
59 std::function<void(std::string)> func;
60 };
61
62 void HandleEnableCommand(const std::string &arguments);
63 void HandleDisableCommand(const std::string &arguments);
64 void HandleShowCommand(const std::string &arguments);
65 void HandleScanCommand(const std::string &arguments);
66 void HandleDevicesCommand(const std::string &arguments);
67 void HandleInfoCommand(const std::string &arguments);
68 void HandleConnectCommand(const std::string &arguments);
69 void HandleDisconnectCommand(const std::string &arguments);
70
71 void RegisterCommand(const Command &command);
72
73private:
74 static gboolean OnUserInput(GIOChannel *channel, GIOCondition condition, gpointer user_data);
75 static void OnReadlineMessage(char *input);
76 static void OnServiceLost(GDBusConnection *connection, const gchar *name, gpointer user_data);
77 static void OnServiceFound(GDBusConnection *connection, const gchar *name, const gchar *name_owner, gpointer user_data);
78 static void OnManagerConnected(GObject *object, GAsyncResult *res, gpointer user_data);
79 static void OnDeviceAdded(GDBusObjectManager *manager, GDBusObject *object, gpointer user_data);
80 static void OnDeviceRemoved(GDBusObjectManager *manager, GDBusObject *object, gpointer user_data);
81 static void OnObjectManagerCreated(GObject *object, GAsyncResult *res, gpointer user_data);
82
83 static void OnManagerPropertiesChanged(GDBusConnection *connection,
84 const gchar *sender_name,
85 const gchar *object_path,
86 const gchar *interface_name,
87 const gchar *signal_name,
88 GVariant *parameters,
89 gpointer user_data);
90
91 static void OnScanDone(GObject *object, GAsyncResult *res, gpointer user_data);
92 static void OnDeviceConnected(GObject *object, GAsyncResult *res, gpointer user_data);
93 static void OnDeviceDisconnected(GObject *object, GAsyncResult *res, gpointer user_data);
94private:
95 void SetupStandardInput();
96 void ForeachDevice(std::function<void(AethercastInterfaceDevice*)> callback, const std::string &address_filter = "");
97
98private:
99 GDBusConnection *bus_connection_;
100 AethercastInterfaceManager *manager_;
101 guint input_source_;
102 GDBusObjectManager *object_manager_;
103
104private:
105 // Needs to be static otherwise we can't access it from the readline
106 // callback function (which sadly doesn't allow passing any user data)
107 static GMainLoop *main_loop_;
108 static std::map<std::string,Command> available_commands_;
109};
110
111} // namespace client
112} // namespace ac
113
114#endif
diff --git a/src/client/main.cpp b/src/client/main.cpp
115deleted file mode 1006440deleted file mode 100644
index 0663e9f..0000000
--- a/src/client/main.cpp
+++ /dev/null
@@ -1,25 +0,0 @@
1/*
2 * Copyright (C) 2012 Intel Corporation. All rights reserved.
3 * 2015 Canonical, Ltd.
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
18 *
19 */
20
21#include "application.h"
22
23int main(int argc, char **argv) {
24 return ac::client::Application::Main(ac::client::Application::MainOptions::FromCommandLine(argc, argv));
25}

Subscribers

People subscribed via source and target branches