Skip to content

Commit

Permalink
Merge pull request #27 from serpent-os/inhibitor
Browse files Browse the repository at this point in the history
Vendor ddbus & add login1 inhibitor method
  • Loading branch information
ikeycode authored Nov 20, 2023
2 parents 525afdb + d65f2cb commit 715242c
Show file tree
Hide file tree
Showing 29 changed files with 3,571 additions and 1 deletion.
3 changes: 3 additions & 0 deletions dub.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@
"tinyendian": {
"path": "vendor/tinyendian"
},
"ddbus": {
"path": "subprojects/ddbus"
},
"libgit2-d": {
"path": "subprojects/libgit2-d"
},
Expand Down
7 changes: 7 additions & 0 deletions meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,13 @@ else
with_moss_fetcher_git = false
endif

# ddbus binding
ddbus = subproject('ddbus',
default_options: [
'default_library=static'
])
link_ddbus = ddbus.get_variable('link_ddbus')

# libgit2 binding
if with_moss_fetcher_git
libgit2 = subproject('libgit2-d',
Expand Down
6 changes: 5 additions & 1 deletion source/moss/core/meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,19 @@ libmoss_core_sources = [
'util.d',
]

core_deps = [link_ddbus]

libmoss_core = static_library(
'moss-core',
libmoss_core_sources,
install: false,
dependencies: core_deps,
include_directories: [root_includedir]
)

# Allow individually linking to libmosscore
link_libmoss_core = declare_dependency(
link_whole: libmoss_core,
include_directories: [root_includedir]
include_directories: [root_includedir],
dependencies: core_deps,
)
31 changes: 31 additions & 0 deletions source/moss/core/util.d
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,37 @@ string computeSHA256(in string path, bool useMmap = false)
return cast(string) ret;
}

/**
* Take a inhibitor lock to inhibit system shutdowns and sleep states with systemd
* https://www.freedesktop.org/wiki/Software/systemd/inhibit/
*
* Requires ddbus : FileDescriptor to be imported.
*
* Params:
* what = What is a colon-separated list of lock types, i.e. shutdown, sleep, idle, handle-power-key, handle-suspend-key, handle-hibernate-key, handle-lid-switch.
* who = Who is a human-readable, descriptive string of who is taking the lock.
* why = Why is a human-readable, descriptive string of why the lock is taken.
* mode = Mode is one of block or delay, see above.
* Returns: FileDescriptor of lock
*/
import ddbus : FileDescriptor;
FileDescriptor inhibit(in string what, in string who, in string why, in string mode)
{
import ddbus : busName, Connection, connectToBus, FileDescriptor, interfaceName, ObjectPath, PathIface;
import ddbus.c_lib : DBusBusType;

static immutable dbusName = busName("org.freedesktop.login1");
static immutable dbusPath = ObjectPath("/org/freedesktop/login1");
static immutable dbusIface = interfaceName("org.freedesktop.login1.Manager");

Connection conn = connectToBus(DBusBusType.DBUS_BUS_SYSTEM);
PathIface obj = new PathIface(conn, dbusName, dbusPath, dbusIface);

auto lock = obj.call!FileDescriptor("Inhibit", what, who, why, mode);

return lock;
}

/**
* Outperforms buildPath considerably by
* not attempting to fold or normalize the paths
Expand Down
8 changes: 8 additions & 0 deletions subprojects/ddbus/.editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
[**.d]
indent_style = space
indent_size = 2

dfmt_brace_style = otbs
dfmt_space_after_keywords = true
dfmt_space_after_cast = true
dfmt_template_constraint_style = always_newline_indent
10 changes: 10 additions & 0 deletions subprojects/ddbus/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
.dub
docs.json
__dummy.html
*.o
*.obj
/ddbus
libddbus.*
__test__library__
/ddbus-test-*
*.lst
15 changes: 15 additions & 0 deletions subprojects/ddbus/Rakefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
task :testCall do
sh "dbus-send --type=method_call --print-reply --dest=ca.thume.ddbus.test /root ca.thume.test.test int32:5"
end

task :badCall do
sh "dbus-send --type=method_call --print-reply --dest=ca.thume.ddbus.test /root ca.thume.test.test double:5.5"
end

task :testSignal do
sh "dbus-send --dest=ca.thume.ddbus.test /signaler ca.thume.test.signal int32:9"
end

task :testDbus do
sh "dbus-send --session --dest=org.freedesktop.DBus --type=method_call --print-reply /org/freedesktop/DBus org.freedesktop.DBus.ListNames"
end
233 changes: 233 additions & 0 deletions subprojects/ddbus/Readme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,233 @@
# ddbus

<a href="https://code.dlang.org/packages/ddbus" title="Go to ddbus"><img src="https://img.shields.io/dub/v/ddbus.svg" alt="Dub version"></a>
<a href="https://code.dlang.org/packages/ddbus" title="Go to ddbus"><img src="https://img.shields.io/dub/dt/ddbus.svg" alt="Dub downloads"></a>

A [dbus](http://www.freedesktop.org/wiki/Software/dbus/) library for the [D programming language](http://dlang.org).

Provides fancy and convenient highly templated methods that automagically serialize and deserialize things into DBus types so that calling DBus methods is almost as easy as calling local ones.

It currently supports:

- Calling methods
- Creating wrapper objects for DBus interfaces
- Seamlessly converting to and from D types
- Handling method calls and signals (includes introspection support)

# Installation

Before using, you will need to have the DBus C library installed on your computer to link with, and probably also a DBus session bus running so that you can actually do things.

`ddbus` is available on [DUB](http://code.dlang.org/packages/ddbus) so you can simply include it in your `dub.json`:
```json
"dependencies": {
"ddbus": "~>2.3.0"
}
```

# Usage

## Call Interface

The simplest way to call methods over DBus is to create a connection and then a PathIface object
which wraps a destination, path and interface. You can then call methods on that object with any
parameters which ddbus knows how to serialize and it will return a reply message which you can convert
to the correct return type using `.to!T()`. You can also use the templated `call` method. Example:

```d
import ddbus;
Connection conn = connectToBus();
PathIface obj = new PathIface(conn, "org.freedesktop.DBus","/org/freedesktop/DBus",
"org.freedesktop.DBus");
// call any method with any parameters and then convert the result to the right type.
auto name = obj.GetNameOwner("org.freedesktop.DBus").to!string();
// alternative method
obj.call!string("GetNameOwner","org.freedesktop.DBus");
```

### Working with properties

```d
import ddbus;
Connection conn = connectToBus();
PathIface obj = new PathIface(conn, "org.freedesktop.secrets", "/org/freedesktop/secrets/collection/login", "org.freedesktop.DBus.Properties");
// read property
string loginLabel = obj.Get("org.freedesktop.Secret.Collection", "Label").to!string();
loginLabel = "Secret"~login;
// write it back (variant type requires variant() wrapper)
obj.Set("org.freedesktop.Secret.Collection", "Label", variant(loginLabel));
```
Setting read only properties results in a thrown `DBusException`.

## Server Interface

You can register a delegate into a `MessageRouter` and a main loop in order to handle messages.
After that you can request a name so that other DBus clients can connect to your program.

You can return a `Tuple!(args)` to return multiple values (multiple out values in XML) or
return a `Variant!DBusAny` to support returning any dynamic value.

```d
import ddbus;
MessageRouter router = new MessageRouter();
// create a pattern to register a handler at a path, interface and method
MessagePattern patt = MessagePattern("/root","ca.thume.test","test");
router.setHandler!(int,int,Variant!DBusAny)(patt,(int par, Variant!DBusAny anyArgument) {
// anyArgument can contain any type now, it must be specified as argument using Variant!DBusAny.
writeln("Called with ", par, ", ", anyArgument);
return par;
});
// handle a signal
patt = MessagePattern("/signaler","ca.thume.test","signal",true);
router.setHandler!(void,int)(patt,(int par) {
writeln("Signalled with ", par);
});
// register all methods of an object
class Tester {
int lol(int x, string s) {return 5;}
void wat() {}
}
Tester o = new Tester;
registerMethods(router, "/","ca.thume.test",o);
// get a name and start the server
registerRouter(conn, router);
bool gotem = requestName(conn, "ca.thume.ddbus.test");
simpleMainLoop(conn);
```

See the Concurrent Updates section for details how to implement this in a custom main loop.

## Thin(ish) Wrapper

`ddbus` also includes a series of thin D struct wrappers over the DBus types.
- `Message`: wraps `DBusMessage` and provides D methods for common functionality.
- `Connection`: wraps `DBusConnection` and provides D methods for common functionality.
- `DBusException`: used for errors produced by DBus turned into D exceptions.

## Type Marshaling

`ddbus` includes fancy templated methods for marshaling D types in and out of DBus messages.
All DBus-compatible basic types work (except file descriptors).
Any forward range can be marshaled in as DBus array of that type but arrays must be taken out as dynamic arrays.

As per version 2.3.0, D `struct` types are fully supported by `ddbus`. By default all public fields of a structure are marshaled. This behavior can be [changed by UDAs](#customizing-marshaling-of-struct-types). Mapping DBus structures to a matching instance of `std.typecons.Tuple`, like earlier versions of `ddbus` did, is also still supported.

Example using the lower level interface, the simple interfaces use these behind the scenes:
```d
Message msg = Message("org.example.wow","/wut","org.test.iface","meth");
struct S {
double a;
int b;
string[][] c;
bool[] d;
}
auto s = S(6.2, 4, [["lol"]], []);
auto args = tuple(5, true, "wow", [6, 5], s);
msg.build(args.expand);
msg.signature().assertEqual("ibsai(diaasab)");
msg.readTuple!(typeof(args))().assertEqual(args);
```
### Basic types
These are the basic types supported by `ddbus`:
`bool`, `ubyte`, `short`, `ushort`, `int`, `uint`, `long`, `ulong`, `double`, `string`, `ObjectPath`, `InterfaceName`, `BusName`

ObjectPath, InterfaceName and BusName are typesafe wrappers or aliases around strings which should be used to ensure type-safety. They do not allow implicit casts to each other but can be manually converted to strings either by casting to string.

### Overview of mappings of other types:

| D type | DBus type | Comments
| -------------------------------------------- | ------------------------ | ---
| any `enum` | `enum` base type | Only the exact values present in the definition of the `enum` type will be allowed.
| `std.typecons.BitFlags` | `enum` base type | Allows usage of OR'ed values of a flags `enum`.
| dynamic array `T[]` | array |
| associative array `V[K]` | array of key-value pairs | DBus has a special type for key-value pairs, which can be used as the element type of an array only.
| `Tuple!(T...)` | structure | The DBus structure will map all of the `Tuple`'s values in sequence.
| any `struct` | structure | The DBus structure will map all public fields of the `struct` type in order of definition, unless otherwise specified using UDAs.
| `ddbus` style variant `Variant!T` | variant | `Variant!T` is in fact just a wrapper type to force representation as a variant in DBus, use `Variant!DBusAny` for actual dynamic typing.
| Phobos style variants `std.variant.VariantN` | variant | Only supported if set of allowed types is limited to types that can be marshaled by `ddbus`, so `std.variant.Variant` is not supported, but `std.variant.Algebraic` may be, depending on allowed types

### Customizing marshaling of `struct` types
Marshaling behavior can be changed for a `struct` type by adding the `@dbusMarshaling`
UDA with the appropriate flag. The following flags are supported:
- `includePrivateFields` enables marshaling of private fields
- `manualOnly` disables marshaling of all fields

Marshaling of individual fields can be enabled or disabled by setting the `DBusMarshal`
flag as an UDA. I.e. `@Yes.DBusMarshal` or `@No.DBusMarshal`.

Note: symbols `Yes` and `No` are defined in `std.typecons`.

After converting a DBus structure to a D `struct`, any fields that are not marshaled
will appear freshly initialized. This is true even when just converting a `struct` to
`DBusAny` and back.

```d
import ddbus.attributes;
import std.typecons;
@dbusMarshaling(MarshalingFlag.includePrivateFields)
struct WeirdThing {
int a; // marshaled (default behavior not overridden)
@No.DBusMarshal int b; // explicitly not marshaled
private int c; // marshaled, because of includePrivateFields
}
```

## Modules

- `attributes`: defines some UDAs (and related templates) that can be used to customize
struct marshaling.
- `bus`: bus functionality like requesting names and event loops.
- `conv`: low level type marshaling methods.
- `exception`: exception classes
- `router`: message and signal routing based on `MessagePattern` structs.
- `simple`: simpler wrappers around other functionality.
- `thin`: thin wrapper types
- `util`: templates for working with D type marshaling like `canDBus!T`.
- `c_lib`: a D translation of the DBus C headers
(you generally should not need to use these directly).

Importing `ddbus` will publicly import the `thin`, `router`, `bus`, `simple` and
`attributes` modules. These provide most of the functionality you probably want,
you can import the others if you want lower level control.

Nothing is hidden so if `ddbus` doesn't provide something, you can always import
`c_lib` and use the pointers contained in the thin wrapper structs to do it yourself.

# Concurrent Updates

If you want to use the DBus connection concurrently with some other features
or library like a GUI or vibe.d you can do so by placing this code in the update/main loop:

```d
// initialize Connection conn; somewhere
// on update:
if (!conn.tick)
return;
```

Or in vibe.d:

```d
runTask({
import vibe.core.core : yield;
while (conn.tick)
yield(); // Or sleep(1.msecs);
});
```

It would be better to watch a file descriptor asynchronously in the event loop instead of checking on a timer, but that hasn't been implemented yet, see Todo.

# Todo

`ddbus` should be complete for everyday use but is missing some fanciness that it easily could and should have:

- Support for adding file descriptors to event loops like vibe.d so that it only wakes up when messages arrive and not on a timer.
- Marshaling of file descriptor objects
- Better efficiency in some places, particularly the object wrapping allocates tons of delegates for every method.

Pull requests are welcome, the codebase is pretty small and other than the template metaprogramming for type marshaling is fairly straightforward.
12 changes: 12 additions & 0 deletions subprojects/ddbus/dub.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"name": "ddbus",
"description": "A DBus library for D",
"homepage": "https://github.com/trishume/ddbus",
"copyright": "Copyright © 2017, Tristan Hume",
"license": "MIT",
"authors": ["Tristan Hume"],
"lflags": ["-ldbus-1"],
"dependencies": {
"dunit": "~>1.0.14"
}
}
6 changes: 6 additions & 0 deletions subprojects/ddbus/dub.selections.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"fileVersion": 1,
"versions": {
"dunit": "1.0.14"
}
}
1 change: 1 addition & 0 deletions subprojects/ddbus/examples/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
dub.selections.json
1 change: 1 addition & 0 deletions subprojects/ddbus/examples/client/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
client
6 changes: 6 additions & 0 deletions subprojects/ddbus/examples/client/dub.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"name": "client",
"dependencies": {
"ddbus": {"path": "../../"}
}
}
Loading

0 comments on commit 715242c

Please sign in to comment.