Merge ~morphis/aethercast/+git/aethercast:feature/cli-rework into aethercast:master
- Git
- lp:~morphis/aethercast/+git/aethercast
- feature/cli-rework
- Merge into master
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) |
Related bugs: |
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_
- 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
1 | diff --git a/snapcraft.yaml b/snapcraft.yaml |
2 | index 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 |
25 | diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt |
26 | index 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) |
74 | diff --git a/src/ac/cli.cpp b/src/ac/cli.cpp |
75 | new file mode 100644 |
76 | index 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); } |
326 | diff --git a/src/ac/cli.h b/src/ac/cli.h |
327 | new file mode 100644 |
328 | index 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 |
675 | diff --git a/src/ac/client/application.cpp b/src/ac/client/application.cpp |
676 | new file mode 100644 |
677 | index 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 |
1222 | diff --git a/src/ac/client/application.h b/src/ac/client/application.h |
1223 | new file mode 100644 |
1224 | index 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 |
1340 | diff --git a/src/ac/cmds/service.cpp b/src/ac/cmds/service.cpp |
1341 | new file mode 100644 |
1342 | index 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 | +} |
1374 | diff --git a/src/ac/cmds/service.h b/src/ac/cmds/service.h |
1375 | new file mode 100644 |
1376 | index 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 |
1412 | diff --git a/src/ac/cmds/shell.cpp b/src/ac/cmds/shell.cpp |
1413 | new file mode 100644 |
1414 | index 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 | +} |
1446 | diff --git a/src/ac/cmds/shell.h b/src/ac/cmds/shell.h |
1447 | new file mode 100644 |
1448 | index 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 |
1484 | diff --git a/src/ac/do_not_copy_or_move.h b/src/ac/do_not_copy_or_move.h |
1485 | new file mode 100644 |
1486 | index 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 |
1526 | diff --git a/src/ac/main.cpp b/src/ac/main.cpp |
1527 | index 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 | } |
1554 | diff --git a/src/ac/optional.h b/src/ac/optional.h |
1555 | new file mode 100644 |
1556 | index 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 |
1591 | diff --git a/src/ac/service.cpp b/src/ac/service.cpp |
1592 | index 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); |
1629 | diff --git a/src/ac/service.h b/src/ac/service.h |
1630 | index 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 | }; |
1642 | diff --git a/src/ac/utils.cpp b/src/ac/utils.cpp |
1643 | index 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 | } |
1659 | diff --git a/src/ac/utils.h b/src/ac/utils.h |
1660 | index 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. |
1672 | diff --git a/src/client/CMakeLists.txt b/src/client/CMakeLists.txt |
1673 | deleted file mode 100644 |
1674 | index 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 | -) |
1714 | diff --git a/src/client/application.cpp b/src/client/application.cpp |
1715 | deleted file mode 100644 |
1716 | index 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 |
2284 | diff --git a/src/client/application.h b/src/client/application.h |
2285 | deleted file mode 100644 |
2286 | index 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 |
2404 | diff --git a/src/client/main.cpp b/src/client/main.cpp |
2405 | deleted file mode 100644 |
2406 | index 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 | -} |