├── .github
├── FUNDING.yml
└── workflows
│ ├── ci.yml
│ ├── codeql-analysis.yml
│ └── test.yml
├── .gitignore
├── Dockerfile
├── Dockerfile.toolbox
├── LICENSE
├── Makefile
├── README.md
├── RELEASING.md
├── dbus
└── org.sessiond.session1.xml
├── man
├── sessionctl.1.pod
├── sessiond-dbus.8.pod
├── sessiond-hooks.5.pod
├── sessiond-inhibit.1.pod
├── sessiond.1.pod
└── sessiond.conf.5.pod
├── meson.build
├── meson_options.txt
├── python-sessiond
├── .gitignore
├── Makefile
├── sessiond.py
└── setup.py
├── scripts
└── install_sysfs_writer.sh
├── sessionctl
├── sessiond-inhibit
├── sessiond.conf
├── sessiond.desktop
├── spec
├── sessiond-0.6.1.spec
└── sessiond.rpkg.spec
├── src
├── backlight.c
├── backlight.h
├── common.c
├── common.h
├── config.c
├── config.h
├── dbus-audiosink.c
├── dbus-audiosink.h
├── dbus-backlight.c
├── dbus-backlight.h
├── dbus-gen.c
├── dbus-gen.h
├── dbus-logind.c
├── dbus-logind.h
├── dbus-server.c
├── dbus-server.h
├── dbus-systemd.c
├── dbus-systemd.h
├── helper
│ └── sessiond-sysfs-writer.c
├── hooks.c
├── hooks.h
├── sessiond.c
├── timeline.c
├── timeline.h
├── toml
│ ├── .gitignore
│ ├── LICENSE
│ ├── Makefile
│ ├── README.md
│ ├── test1
│ │ ├── .gitignore
│ │ ├── README.md
│ │ ├── build.sh
│ │ ├── extra
│ │ │ ├── array_of_tables.toml
│ │ │ ├── inline_array.toml
│ │ │ └── inline_table.toml
│ │ └── run.sh
│ ├── test2
│ │ ├── .gitignore
│ │ ├── build.sh
│ │ └── run.sh
│ ├── toml.c
│ ├── toml.h
│ ├── toml_cat.c
│ ├── toml_json.c
│ └── unittest
│ │ ├── Makefile
│ │ └── t1.c
├── version.h
├── wireplumber.c
├── wireplumber.h
├── xsource.c
└── xsource.h
├── systemd
├── graphical-idle.target
├── graphical-lock.target
├── graphical-unidle.target
├── graphical-unlock.target
├── sessiond-session.target
├── sessiond.service
├── user-shutdown.target
├── user-sleep-finished.target
└── user-sleep.target
└── test
├── config_test.c
├── hooks.conf
├── hooks.d
├── 00-inactive.hook
└── 01-lock.hook
├── hooks_test.c
├── meson.build
├── test.conf
└── timeline_test.c
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # These are supported funding model platforms
2 |
3 | github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
4 | patreon: # Replace with a single Patreon username
5 | open_collective: # Replace with a single Open Collective username
6 | ko_fi: jcrd_dev
7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
9 | liberapay: # Replace with a single Liberapay username
10 | issuehunt: # Replace with a single IssueHunt username
11 | otechie: # Replace with a single Otechie username
12 | lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
13 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
14 |
--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | name: ci
2 |
3 | on:
4 | push:
5 | branches: master
6 | workflow_dispatch:
7 |
8 | jobs:
9 | docker:
10 | runs-on: ubuntu-latest
11 | steps:
12 | - name: Set up Docker Buildx
13 | uses: docker/setup-buildx-action@v1
14 |
15 | - name: Login to DockerHub
16 | uses: docker/login-action@v1
17 | with:
18 | username: ${{ secrets.DOCKERHUB_USERNAME }}
19 | password: ${{ secrets.DOCKERHUB_TOKEN }}
20 |
21 | - name: Build and push
22 | id: docker_build
23 | uses: docker/build-push-action@v2
24 | with:
25 | push: true
26 | tags: supplantr/sessiond:latest
27 |
--------------------------------------------------------------------------------
/.github/workflows/codeql-analysis.yml:
--------------------------------------------------------------------------------
1 | name: "CodeQL"
2 |
3 | on:
4 | push:
5 | branches: [ master, devel ]
6 | pull_request:
7 | # The branches below must be a subset of the branches above
8 | branches: [ master ]
9 | schedule:
10 | - cron: '41 5 * * 1'
11 |
12 | jobs:
13 | analyze:
14 | name: Analyze
15 | runs-on: ubuntu-latest
16 | container: supplantr/sessiond:latest
17 | permissions:
18 | actions: read
19 | contents: read
20 | security-events: write
21 |
22 | strategy:
23 | fail-fast: false
24 | matrix:
25 | language: [ 'cpp', 'python' ]
26 |
27 | steps:
28 | - name: Checkout repository
29 | uses: actions/checkout@v2
30 |
31 | # Initializes the CodeQL tools for scanning.
32 | - name: Initialize CodeQL
33 | uses: github/codeql-action/init@v1
34 | with:
35 | languages: ${{ matrix.language }}
36 |
37 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
38 | # If this step fails, then you should remove it and run the build manually (see below)
39 | - name: Autobuild
40 | uses: github/codeql-action/autobuild@v1
41 |
42 | #- run: |
43 | # make bootstrap
44 | # make release
45 |
46 | - name: Perform CodeQL Analysis
47 | uses: github/codeql-action/analyze@v1
48 |
--------------------------------------------------------------------------------
/.github/workflows/test.yml:
--------------------------------------------------------------------------------
1 | name: test
2 |
3 | on: [push, pull_request, workflow_dispatch]
4 |
5 | jobs:
6 | test:
7 | runs-on: ubuntu-latest
8 | container: supplantr/sessiond:latest
9 | steps:
10 | - uses: actions/checkout@v2
11 |
12 | - name: Build
13 | run: meson builddir && ninja -C builddir
14 |
15 | - name: Test
16 | run: meson test -C builddir
17 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | builddir
2 | .clangd
3 | sessiond.org
4 | compile_commands.json
5 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM debian:testing-slim
2 |
3 | RUN apt-get update
4 | RUN apt-get install -y meson
5 | RUN apt-get install -y git
6 | RUN apt-get install -y gcc
7 | RUN apt-get install -y pkgconf
8 | RUN apt-get install -y libx11-dev
9 | RUN apt-get install -y libxi-dev
10 | RUN apt-get install -y libglib2.0-dev
11 | RUN apt-get install -y libudev-dev
12 | RUN apt-get install -y libpipewire-0.3-dev
13 | RUN apt-get install -y libwireplumber-0.4-dev
14 |
--------------------------------------------------------------------------------
/Dockerfile.toolbox:
--------------------------------------------------------------------------------
1 | FROM supplantr/python-toolbox
2 |
3 | RUN dnf install -y gcc
4 | RUN dnf install -y meson
5 | RUN dnf install -y libX11-devel
6 | RUN dnf install -y libXi-devel
7 | RUN dnf install -y glib2-devel
8 | RUN dnf install -y systemd-devel
9 | RUN dnf install -y dbus-devel
10 | RUN dnf install -y wireplumber-devel
11 | RUN dnf install -y python-dbus
12 |
13 | RUN dnf install -y valgrind
14 |
15 | # clangd
16 | RUN dnf install -y clang-tools-extra
17 |
18 | # Install Pod::Markdown perl module to convert .pod manpages to .md for site.
19 | RUN dnf install -y perl
20 | RUN curl -L http://cpanmin.us | perl - App::cpanminus
21 | RUN cpanm Pod::Markdown
22 |
23 | RUN pip install sphinx-markdown-builder
24 |
25 | RUN dnf copr enable -y jcrd/hugo-extended
26 | RUN dnf install -y hugo
27 |
28 | RUN dnf install -y npm
29 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | SITE_URL = git@github.com:jcrd/sessiond.org
2 |
3 | BUILDDIR ?= builddir
4 |
5 | all: compile_commands.json sessiond.org
6 | build: $(BUILDDIR)/sessiond
7 |
8 | $(BUILDDIR):
9 | meson $(BUILDDIR)
10 |
11 | compile_commands.json: $(BUILDDIR)
12 | ln -s $(BUILDDIR)/compile_commands.json compile_commands.json
13 |
14 | $(BUILDDIR)/sessiond: $(BUILDDIR)
15 | ninja -C $(BUILDDIR)
16 |
17 | sessiond.org:
18 | git clone $(SITE_URL) $@
19 |
20 | clean:
21 | rm -fr $(BUILDDIR)
22 | rm -f compile_commands.json
23 |
24 | .PHONY: all build clean
25 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # [
][site] sessiond
2 |
3 | [![test][test-badge]][test]
4 | [![CodeQL][codeql-badge]][codeql]
5 | [![Copr build status][copr-badge]][copr]
6 |
7 | [test-badge]: https://github.com/jcrd/sessiond/actions/workflows/test.yml/badge.svg
8 | [test]: https://github.com/jcrd/sessiond/actions/workflows/test.yml
9 | [codeql-badge]: https://github.com/jcrd/sessiond/actions/workflows/codeql-analysis.yml/badge.svg
10 | [codeql]: https://github.com/jcrd/sessiond/actions/workflows/codeql-analysis.yml
11 | [copr-badge]: https://copr.fedorainfracloud.org/coprs/jcrd/sessiond/package/sessiond/status_image/last_build.png
12 | [copr]: https://copr.fedorainfracloud.org/coprs/jcrd/sessiond/package/sessiond/
13 |
14 | ## Overview
15 |
16 | sessiond is a daemon for **systemd**-based Linux systems that interfaces with
17 | **systemd-logind** to provide session management features to X11 window managers.
18 |
19 | Its primary responsibility is to monitor keyboard and mouse activity to
20 | determine when a session has become idle, and to then act accordingly.
21 |
22 | It also provides a DBus service with interfaces to backlights and audio sinks.
23 |
24 | ## Features
25 |
26 | * automatic screen locking on session idle and before sleeping
27 | * automatic backlight dimming on session idle
28 | * automatic muting of audio while session is locked
29 | * systemd targets activated by systemd-logind's lock, unlock, sleep,
30 | and shutdown signals
31 | * hooks triggered by inactivity or signals
32 | * a DBus service
33 | * backlight interaction
34 | * audio sink interaction
35 | * (optional) management of DPMS settings
36 |
37 | ## Documentation
38 |
39 | Documentation is available at [here][site].
40 |
41 | See the [Getting started](https://jcrd.github.io/sessiond/getting-started/) section to get
42 | started using sessiond.
43 |
44 | ## License
45 |
46 | sessiond is licensed under the GNU General Public License v3.0 or later
47 | (see [LICENSE](LICENSE)).
48 |
49 | [site]: https://jcrd.github.io/sessiond/
50 |
--------------------------------------------------------------------------------
/RELEASING.md:
--------------------------------------------------------------------------------
1 | 1. Bump version in `meson.build`
2 | 2. Rename `spec/sessiond-$VERSION.spec`
3 | - Bump Version
4 | - Adjust source URL
5 | - Add changelog entry
6 | 3. Ensure `spec/sessiond.rpkg.spec` reflects packaging changes
7 | 4. Test spec with `tb rpkg-install`
8 | 5. Ensure `spec/sessiond-$VERSION.spec` reflects rpkg spec
9 | 6. Commit with `Update version to $VERSION`
10 | 7. Tag release
11 | 8. Push commits and tag
12 | 9. Bump download version on sessiond.org
13 |
--------------------------------------------------------------------------------
/dbus/org.sessiond.session1.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
--------------------------------------------------------------------------------
/man/sessionctl.1.pod:
--------------------------------------------------------------------------------
1 | =head1 NAME
2 |
3 | sessionctl - standalone X session manager client
4 |
5 | =head1 SYNOPSIS
6 |
7 | B [command]
8 |
9 | =head1 DESCRIPTION
10 |
11 | sessionctl is responsible for running a sessiond session and interacting with
12 | its DBus service.
13 |
14 | =head1 COMMANDS
15 |
16 | =over
17 |
18 | =item B [I]
19 |
20 | Run a new session, with I as the window manager service if provided.
21 | By default, the service installed under the I alias is
22 | used.
23 |
24 | =item B
25 |
26 | Stop the running session.
27 |
28 | =item B
29 |
30 | Show session status.
31 |
32 | =item B
33 |
34 | Lock the session.
35 |
36 | =item B
37 |
38 | Unlock the session.
39 |
40 | =item B
41 |
42 | List sessiond properties.
43 |
44 | =item B [I]
45 |
46 | Interact with backlights.
47 | If backlight I is given with no options, print brightness.
48 | If no arguments are given, list backlights.
49 |
50 | Options:
51 |
52 | =over
53 |
54 | =item B<-s> I, B<--set> I
55 |
56 | Set backlight brightness.
57 |
58 | =item B<-i> I, B<--inc> I
59 |
60 | Increment backlight brightness.
61 | Prints the new brightness value.
62 |
63 | =back
64 |
65 | =item B [I]
66 |
67 | Interact with audio sinks.
68 | If audio sink I is given with no options, print volume and mute state.
69 | If no arguments are given, list audio sinks.
70 |
71 | Options:
72 |
73 | =over
74 |
75 | =item B<-s> I, B<--set> I
76 |
77 | Set audio sink volume.
78 |
79 | =item B<-i> I, B<--inc> I
80 |
81 | Increment audio sink volume.
82 | Prints the new volume value.
83 |
84 | =item B<-m>, B<--mute>
85 |
86 | Mute audio sink.
87 |
88 | =item B<-u>, B<--unmute>
89 |
90 | Unmute audio sink.
91 |
92 | =item B<-t>, B<--toggle-mute>
93 |
94 | Toggle audio sink mute state.
95 | Prints the new mute state.
96 |
97 | =back
98 |
99 | =item B
100 |
101 | Show sessiond version.
102 |
103 | =back
104 |
105 | =head1 AUTHOR
106 |
107 | James Reed Ejcrd@sessiond.orgE
108 |
109 | =head1 REPORTING BUGS
110 |
111 | Bugs and issues can be reported here: L
112 |
113 | =head1 COPYRIGHT
114 |
115 | Copyright 2019-2020 James Reed. sessiond is licensed under the
116 | GNU General Public License v3.0 or later.
117 |
118 | =head1 SEE ALSO
119 |
120 | B(1), B(1)
121 |
--------------------------------------------------------------------------------
/man/sessiond-dbus.8.pod:
--------------------------------------------------------------------------------
1 | =head1 NAME
2 |
3 | sessiond-dbus - sessiond DBus service information
4 |
5 | =head1 SYNOPSIS
6 |
7 | DBus service information.
8 |
9 | =head1 DESCRIPTION
10 |
11 | sessiond provides a DBus service on the session bus at the well-known name
12 | I.
13 |
14 | =head2 Session interface
15 |
16 | The B object implements the
17 | B interface, which exposes the following
18 | methods, properties, and signals:
19 |
20 | =head3 METHODS
21 |
22 | =over
23 |
24 | =item B
25 |
26 | Lock the session. Returns an error if the session is already locked.
27 |
28 | =item B
29 |
30 | Unlock the session. Returns an error if the session is not locked.
31 |
32 | =item B
33 |
34 | Inhibit inactivity. The session will always be considered active if at least
35 | one inhibitor is running. Takes two arguments:
36 |
37 | =over
38 |
39 | =item I
40 |
41 | A string describing who is inhibiting.
42 |
43 | =item I
44 |
45 | A string describing why the inhibitor is running.
46 |
47 | =back
48 |
49 | Returns a unique ID used to stop the inhibitor.
50 |
51 | =item B
52 |
53 | Stop an inhibitor. Takes one argument:
54 |
55 | =over
56 |
57 | =item I
58 |
59 | The unique ID of the inhibitor to stop.
60 |
61 | =back
62 |
63 | Returns an error if the ID is not valid or does not exist.
64 |
65 | =item B
66 |
67 | Stop running inhibitors. Returns the number of inhibitors stopped.
68 |
69 | =item B
70 |
71 | List running inhibitors. Returns a dictionary mapping IDs to tuples of the
72 | creation timestamp and I and I strings.
73 |
74 | =back
75 |
76 | =head3 PROPERTIES
77 |
78 | =over
79 |
80 | =item B
81 |
82 | The inhibited state of the session.
83 |
84 | =item B
85 |
86 | The locked state of the session.
87 |
88 | =item B
89 |
90 | The idle state of the session.
91 |
92 | =item B
93 |
94 | The timestamp of the last change to B.
95 |
96 | =item B
97 |
98 | The timestamp of the last change to B in monotonic time.
99 |
100 | =item B
101 |
102 | An array of object paths to exported Backlights.
103 |
104 | =item B
105 |
106 | An array of object paths to exported AudioSinks.
107 |
108 | =item B
109 |
110 | Object path to the default AudioSink.
111 |
112 | =item B
113 |
114 | The version of sessiond.
115 |
116 | =back
117 |
118 | =head3 SIGNALS
119 |
120 | =over
121 |
122 | =item B
123 |
124 | Emitted when the session is locked. B will be true.
125 |
126 | =item B
127 |
128 | Emitted when the session is unlocked. B will be false.
129 |
130 | =item B
131 |
132 | Emitted when the session becomes idle. B will be true.
133 |
134 | =item B
135 |
136 | Emitted when activity resumes in an inactive session.
137 | B will be false.
138 |
139 | =item B B
140 |
141 | Emitted when the session becomes inactive, with the B argument being
142 | the number of seconds since activity. Its value will be equal to either the
143 | I or I configuration option (see B(5)), or the
144 | I option of a hook with an B trigger
145 | (see B(5)).
146 |
147 | =item B B
148 |
149 | Emitted before and after system sleep, with the B argument being true
150 | and false respectively.
151 |
152 | =item B B
153 |
154 | Emitted before and after system shutdown, with the B argument being true
155 | and false respectively.
156 |
157 | =item B B
158 |
159 | Emitted when a backlight is added, with B being the object path of the
160 | new backlight.
161 |
162 | =item B B
163 |
164 | Emitted when a backlight is removed, with B being the old object path of
165 | the backlight.
166 |
167 | =item B B
168 |
169 | Emitted when an audio sink is added, with B being the object path of the
170 | new audio sink.
171 |
172 | =item B B
173 |
174 | Emitted when an audio sink is removed, with B being the old object path of
175 | the audio sink.
176 |
177 | =item B B
178 |
179 | Emitted when the default audio sink changes, with B being the object path
180 | of the default audio sink.
181 |
182 | =back
183 |
184 | =head2 Backlight interface
185 |
186 | The B objects implement the
187 | B interface, which exposes the following
188 | methods and properties:
189 |
190 | =head3 METHODS
191 |
192 | =over
193 |
194 | =item B
195 |
196 | Set the brightness of the backlight. Takes one argument:
197 |
198 | =over
199 |
200 | =item I
201 |
202 | An unsigned integer value.
203 |
204 | =back
205 |
206 | Returns an error if unable to set brightness.
207 |
208 | =item B
209 |
210 | Increment the brightness of the backlight. Takes one argument:
211 |
212 | =over
213 |
214 | =item I
215 |
216 | An integer value added to the backlight's current brightness.
217 |
218 | =back
219 |
220 | Returns the new brightness value or an error if unable to set brightness.
221 |
222 | =back
223 |
224 | =head3 PROPERTIES
225 |
226 | =over
227 |
228 | =item B
229 |
230 | True if the backlight is online, false otherwise.
231 |
232 | =item B
233 |
234 | Path to the backlight device without the sys mount point.
235 |
236 | =item B
237 |
238 | Name of the backlight.
239 |
240 | =item B
241 |
242 | Subsystem to which the backlight belongs. Possible values are: "backlight" or
243 | "leds".
244 |
245 | =item B
246 |
247 | Path to the device via sys mount point. Format is:
248 | "/sys/class/I/I".
249 |
250 | =item B
251 |
252 | Current brightness of backlight.
253 |
254 | =item B
255 |
256 | Max brightness of backlight.
257 |
258 | =back
259 |
260 | =head2 AudioSink interface
261 |
262 | The B objects implement the
263 | B interface, which exposes the following
264 | methods, properties, and signals:
265 |
266 | =head3 METHODS
267 |
268 | =over
269 |
270 | =item B
271 |
272 | Set the volume of the audio sink. Takes one argument:
273 |
274 | =over
275 |
276 | =item I
277 |
278 | A double value.
279 |
280 | =back
281 |
282 | Returns an error if unable to set volume.
283 |
284 | =item B
285 |
286 | Increment the volume of the audio sink. Takes one argument:
287 |
288 | =over
289 |
290 | =item I
291 |
292 | A double value added to the audio sink's current volume.
293 |
294 | =back
295 |
296 | Returns the new volume value or an error if unable to set volume.
297 |
298 | =item B
299 |
300 | Set the mute state of the audio sink. Takes one argument:
301 |
302 | =over
303 |
304 | =item I
305 |
306 | A boolean value indicating the mute state.
307 |
308 | =back
309 |
310 | Returns an error if unable to set mute state.
311 |
312 | =item B
313 |
314 | Toggle the mute state of the audio sink.
315 | Returns the new mute state or an error if unable to set mute state.
316 |
317 | =back
318 |
319 | =head3 PROPERTIES
320 |
321 | =over
322 |
323 | =item B
324 |
325 | ID of the audio sink.
326 |
327 | =item B
328 |
329 | Name of the audio sink.
330 |
331 | =item B
332 |
333 | Mute state of the audio sink.
334 |
335 | =item B
336 |
337 | Volume of the audio sink.
338 |
339 | =back
340 |
341 | =head3 SIGNALS
342 |
343 | =over
344 |
345 | =item B B
346 |
347 | Emitted when the mute state changes, with the B argument being the new
348 | mute state.
349 |
350 | =item B B
351 |
352 | Emitted when the volume changes, with the B argument being the new
353 | volume value.
354 |
355 | =back
356 |
357 | =head1 INTROSPECTION
358 |
359 | =over
360 |
361 | =item For complete introspection data, use B(1):
362 |
363 | B introspect --session --dest I --object-path
364 | I
365 |
366 | =back
367 |
368 | =head1 AUTHOR
369 |
370 | James Reed Ejcrd@sessiond.orgE
371 |
372 | =head1 REPORTING BUGS
373 |
374 | Bugs and issues can be reported here: L
375 |
376 | =head1 COPYRIGHT
377 |
378 | Copyright 2018-2020 James Reed. sessiond is licensed under the
379 | GNU General Public License v3.0 or later.
380 |
381 | =head1 SEE ALSO
382 |
383 | B(1), B(8)
384 |
--------------------------------------------------------------------------------
/man/sessiond-hooks.5.pod:
--------------------------------------------------------------------------------
1 | =head1 NAME
2 |
3 | sessiond-hooks - sessiond hook file format
4 |
5 | =head1 SYNOPSIS
6 |
7 | =over
8 |
9 | =item [Hook]
10 |
11 | =item Trigger=Lock|Idle|Sleep|Shutdown|Inactive
12 |
13 | =item InactiveSec=EsecondsE (Inactive only)
14 |
15 | =item ExecStart=EcommandE
16 |
17 | =item ExecStop=EcommandE (Lock|Idle|Inactive only)
18 |
19 | =back
20 |
21 | =head1 DESCRIPTION
22 |
23 | sessiond provides the ability to define hooks that are triggered by events.
24 | The "Inactive" event is unique to hooks. It allows commands to be run after a
25 | period of inactivity. It is more general than the "Idle" event, which occurs
26 | after I (see B(5)) seconds of inactivity.
27 |
28 | Hooks can be specified in the configuration file using the section "[[Hook]]".
29 | See B(5).
30 |
31 | Hook files with the ".hook" suffix are read from
32 | I/sessiond/hooks.d or I/.config/sessiond/hooks.d.
33 |
34 | =head1 OPTIONS
35 |
36 | =over
37 |
38 | =item I
39 |
40 | Event type that will trigger the hook. Values are "Lock", "Idle", "Sleep",
41 | "Shutdown", "Inactive".
42 |
43 | =item I
44 |
45 | Seconds of inactivity after which the hook is triggered.
46 |
47 | =item I
48 |
49 | Command to execute when the hook is triggered.
50 |
51 | =item I
52 |
53 | Command to execute when the trigger event ends. For "Lock", this is when the
54 | screen is unlocked. For "Idle" and "Inactive", this is when activity resumes.
55 |
56 | =back
57 |
58 | =head1 SEE ALSO
59 |
60 | B(1), B(5)
61 |
--------------------------------------------------------------------------------
/man/sessiond-inhibit.1.pod:
--------------------------------------------------------------------------------
1 | =head1 NAME
2 |
3 | sessiond-inhibit - manage sessiond inhibitors
4 |
5 | =head1 SYNOPSIS
6 |
7 | B [options] [COMMAND]
8 |
9 | =head1 DESCRIPTION
10 |
11 | sessiond-inhibit creates an inhibitor lock before running I and
12 | releases it when the command returns.
13 | If no command is provided, it lists running inhibitors.
14 |
15 | =head1 OPTIONS
16 |
17 | =over
18 |
19 | =item B<-h>
20 |
21 | Show help message.
22 |
23 | =item B<-w> I
24 |
25 | Set who is inhibiting.
26 |
27 | =item B<-y> I
28 |
29 | Set why this inhibitor is running.
30 |
31 | =item B<-s>
32 |
33 | Stop running inhibitors.
34 |
35 | =item B<-i>
36 |
37 | Inhibit without a command.
38 |
39 | =item B<-u> [I]
40 |
41 | Uninhibit last inhibitor or by ID.
42 |
43 | =back
44 |
45 | =head1 AUTHOR
46 |
47 | James Reed Ejcrd@sessiond.orgE
48 |
49 | =head1 REPORTING BUGS
50 |
51 | Bugs and issues can be reported here: L
52 |
53 | =head1 COPYRIGHT
54 |
55 | Copyright 2019-2020 James Reed. sessiond is licensed under the
56 | GNU General Public License v3.0 or later.
57 |
58 | =head1 SEE ALSO
59 |
60 | B(1), B(1)
61 |
--------------------------------------------------------------------------------
/man/sessiond.1.pod:
--------------------------------------------------------------------------------
1 | =head1 NAME
2 |
3 | sessiond - standalone X session manager
4 |
5 | =head1 SYNOPSIS
6 |
7 | B [OPTIONS]
8 |
9 | =head1 DESCRIPTION
10 |
11 | sessiond is a standalone X session manager that reports the idle status of a
12 | session to B(8) and handles its lock, unlock, sleep, and
13 | shutdown signals. sessiond also provides hooks triggered by inactivity or a
14 | signal, automatic backlight dimming on idle, and optional management of DPMS
15 | settings.
16 |
17 | =head1 OPTIONS
18 |
19 | =over
20 |
21 | =item B<-h>, B<--help>
22 |
23 | Show help options.
24 |
25 | =item B<-c>, B<--config>=I
26 |
27 | Path to config file. See B(5) for configuration options.
28 |
29 | =item B<-i>, B<--idle-sec>=I
30 |
31 | Seconds the session must be inactive before considered idle.
32 |
33 | =item B<-v>, B<--version>
34 |
35 | Show version.
36 |
37 | =back
38 |
39 | =head1 DEBUGGING
40 |
41 | Running sessiond with the environment variable I set to "all"
42 | will print debug messages.
43 |
44 | =head1 AUTHOR
45 |
46 | James Reed Ejcrd@sessiond.orgE
47 |
48 | =head1 REPORTING BUGS
49 |
50 | Bugs and issues can be reported here: L
51 |
52 | =head1 COPYRIGHT
53 |
54 | Copyright 2018-2020 James Reed. sessiond is licensed under the
55 | GNU General Public License v3.0 or later.
56 |
57 | =head1 SEE ALSO
58 |
59 | B(5), B(5), B(8), B(8)
60 |
--------------------------------------------------------------------------------
/man/sessiond.conf.5.pod:
--------------------------------------------------------------------------------
1 | =head1 NAME
2 |
3 | sessiond.conf - sessiond configuration file format
4 |
5 | =head1 SYNOPSIS
6 |
7 | I/sessiond/sessiond.conf or
8 | I/.config/sessiond/sessiond.conf
9 |
10 | =head1 DESCRIPTION
11 |
12 | This file configures the X session manager B(1).
13 | Its syntax is toml v0.5.0.
14 | See: https://github.com/toml-lang/toml/tree/v0.5.0.
15 |
16 | =head1 OPTIONS
17 |
18 | =head2 [Idle]
19 |
20 | =over
21 |
22 | =item I
23 |
24 | A list (of the format ["item", "item"]) of input event types used to determine
25 | activity. Values are "motion", "button-press", "button-release", "key-press",
26 | "key-release".
27 |
28 | =item I
29 |
30 | Seconds the session must be inactive before considered idle.
31 |
32 | =back
33 |
34 | =head2 [Lock]
35 |
36 | =over
37 |
38 | =item I
39 |
40 | If "true", lock the session when it becomes idle.
41 |
42 | =item I
43 |
44 | If "true", lock the session when B(8) sends the
45 | "PrepareForSleep" signal.
46 |
47 | =item I
48 |
49 | DPMS standby timeout in seconds to use while session is locked.
50 | Must occur before or simultaneously with Suspend timeout.
51 |
52 | =item I
53 |
54 | DPMS suspend timeout in seconds to use while session is locked.
55 | Must occur before or simultaneously with Off timeout.
56 |
57 | =item I
58 |
59 | DPMS off timeout in seconds to use while session is locked.
60 |
61 | =item I
62 |
63 | If "true", mute the default audio sink while the session is locked.
64 | The mute state will be restored when unlocked.
65 |
66 | =back
67 |
68 | =head2 [DPMS]
69 |
70 | =over
71 |
72 | =item I
73 |
74 | If "true", apply DPMS settings, including those in the "[Lock]" section.
75 |
76 | =item I
77 |
78 | DPMS standby timeout in seconds. Must occur before or simultaneously with
79 | Suspend timeout.
80 |
81 | =item I
82 |
83 | DPMS suspend timeout in seconds. Must occur before or simultaneously with
84 | Off timeout.
85 |
86 | =item I
87 |
88 | DPMS off timeout in seconds.
89 |
90 | =back
91 |
92 | =head2 [[Backlight]]
93 |
94 | Backlights are configured as an array of tables, using the section
95 | "[[Backlight]]". The options will be applied to backlights with the same path.
96 |
97 | =over
98 |
99 | =item I
100 |
101 | Path to the backlight device via sys mount point. Should be of the format:
102 | "/sys/class/I/I".
103 |
104 | =item I
105 |
106 | Seconds the session must be inactive before the backlight is dimmed.
107 |
108 | =item I
109 |
110 | Value of the backlight brightness when dimming.
111 |
112 | =item I
113 |
114 | Percentage to lower backlight brightness when dimming.
115 |
116 | =back
117 |
118 | =head2 [[Hook]]
119 |
120 | Hooks are configured as an array of tables, using the section "[[Hook]]".
121 | See B(5) for a description of options.
122 |
123 | =head1 SEE ALSO
124 |
125 | B(1), B(8), B(5)
126 |
--------------------------------------------------------------------------------
/meson.build:
--------------------------------------------------------------------------------
1 | project('sessiond', 'c',
2 | version : 'v0.6.1',
3 | meson_version : '>=0.47.0')
4 |
5 | deps = [
6 | dependency('gio-2.0'),
7 | dependency('gio-unix-2.0'),
8 | dependency('x11'),
9 | dependency('xi'),
10 | dependency('libudev'),
11 | ]
12 |
13 | # alternative to vcs_tag that allows reuse of version
14 | # see https://github.com/mesonbuild/meson/issues/3903
15 | version = meson.project_version()
16 | git = find_program('git', required : false)
17 | if git.found()
18 | r = run_command('git', 'describe', '--dirty', '--tags', '--always')
19 | if r.returncode() == 0
20 | version = r.stdout().strip()
21 | endif
22 | endif
23 |
24 | add_project_arguments('-DVERSION="@0@"'.format(version), language : 'c')
25 | add_project_arguments('-DG_LOG_USE_STRUCTURED', language : 'c')
26 | add_project_arguments('-DPREFIX="@0@"'.format(get_option('prefix')), language : 'c')
27 |
28 | if get_option('dpms').enabled()
29 | deps += dependency('xext')
30 | add_project_arguments('-DDPMS', language : 'c')
31 | endif
32 |
33 | if get_option('backlight_helper').enabled()
34 | add_project_arguments('-DBACKLIGHT_HELPER', language : 'c')
35 | executable('sessiond-sysfs-writer', 'src/helper/sessiond-sysfs-writer.c')
36 |
37 | # install setuid helper
38 | meson.add_install_script('scripts/install_sysfs_writer.sh')
39 | endif
40 |
41 | # generate dbus sources
42 | gdbus_codegen = find_program('gdbus-codegen')
43 |
44 | dbus_srcs = custom_target('dbus sources',
45 | command : [gdbus_codegen,
46 | '--generate-c-code', 'dbus-gen',
47 | '--interface-prefix', 'org.sessiond.session1.',
48 | '--c-namespace', 'DBus',
49 | '@INPUT@'],
50 | input : ['dbus/org.sessiond.session1.xml'],
51 | output : ['dbus-gen.c', 'dbus-gen.h'])
52 |
53 | srcs = [
54 | dbus_srcs,
55 | 'src/backlight.c',
56 | 'src/common.c',
57 | 'src/config.c',
58 | 'src/dbus-logind.c',
59 | 'src/dbus-systemd.c',
60 | 'src/dbus-server.c',
61 | 'src/dbus-backlight.c',
62 | 'src/hooks.c',
63 | 'src/sessiond.c',
64 | 'src/timeline.c',
65 | 'src/xsource.c',
66 | 'src/toml/toml.c',
67 | ]
68 |
69 | if get_option('wireplumber').enabled()
70 | deps += dependency('wireplumber-0.4')
71 | deps += dependency('libpipewire-0.3')
72 | srcs += 'src/dbus-audiosink.c'
73 | srcs += 'src/wireplumber.c'
74 | add_project_arguments('-DWIREPLUMBER', language : 'c')
75 | endif
76 |
77 | executable('sessiond', sources : srcs, dependencies : deps, install : true)
78 |
79 | install_data('sessiond.conf',
80 | install_dir : join_paths(get_option('datadir'), meson.project_name()))
81 |
82 | install_data('sessionctl', install_dir : get_option('bindir'))
83 | install_data('sessiond-inhibit', install_dir : get_option('bindir'))
84 |
85 | install_data('sessiond.desktop',
86 | install_dir : join_paths(get_option('datadir'), 'xsessions'))
87 |
88 | units = [
89 | 'systemd/graphical-idle.target',
90 | 'systemd/graphical-unidle.target',
91 | 'systemd/graphical-lock.target',
92 | 'systemd/graphical-unlock.target',
93 | 'systemd/user-sleep.target',
94 | 'systemd/user-sleep-finished.target',
95 | 'systemd/user-shutdown.target',
96 | 'systemd/sessiond-session.target',
97 | 'systemd/sessiond.service',
98 | ]
99 |
100 | install_data(sources : units,
101 | install_dir : join_paths(get_option('libdir'), 'systemd/user'))
102 |
103 | manpages = {
104 | 'sessiond': '1',
105 | 'sessiond.conf': '5',
106 | 'sessiond-hooks': '5',
107 | 'sessiond-dbus': '8',
108 | 'sessionctl': '1',
109 | 'sessiond-inhibit': '1',
110 | }
111 |
112 | # install manpages built with pod2man
113 | mandir = get_option('mandir')
114 | pod2man = find_program('pod2man')
115 |
116 | foreach name, section : manpages
117 | manpage = '.'.join([name, section])
118 | custom_target(
119 | 'manpage @0@'.format(manpage),
120 | input : join_paths('man', '@0@.pod'.format(manpage)),
121 | output : manpage,
122 | command : [
123 | pod2man,
124 | '--name=@0@'.format(name),
125 | '--center=@0@'.format(name),
126 | '--section=@0@'.format(section),
127 | '--release=@0@'.format(version),
128 | '@INPUT@', '@OUTPUT@'],
129 | install : true,
130 | install_dir : join_paths(mandir, 'man@0@'.format(section)))
131 | endforeach
132 |
133 | subdir('test')
134 |
--------------------------------------------------------------------------------
/meson_options.txt:
--------------------------------------------------------------------------------
1 | option('dpms', type : 'feature', value : 'enabled')
2 | option('wireplumber', type : 'feature', value : 'enabled')
3 | option('backlight_helper', type : 'feature', value : 'disabled')
4 |
--------------------------------------------------------------------------------
/python-sessiond/.gitignore:
--------------------------------------------------------------------------------
1 | __pycache__
2 | build
3 | dist
4 | sessiond.egg-info
5 |
--------------------------------------------------------------------------------
/python-sessiond/Makefile:
--------------------------------------------------------------------------------
1 | install:
2 | python setup.py install --user
3 |
4 | uninstall:
5 | pip uninstall -y sessiond
6 |
7 | clean:
8 | rm -rf __pycache__ build dist sessiond.egg-info
9 |
10 | .PHONY: install uninstall clean
11 |
--------------------------------------------------------------------------------
/python-sessiond/sessiond.py:
--------------------------------------------------------------------------------
1 | # This project is licensed under the MIT License.
2 |
3 | """
4 | .. module:: sessiond
5 | :synopsis: Interface to the sessiond DBus service.
6 |
7 | .. moduleauthor:: James Reed
8 | """
9 |
10 | import dbus
11 |
12 | BUS_NAME = "org.sessiond.session1"
13 |
14 |
15 | class DBusIFace:
16 | """
17 | Base interface to the sessiond DBus service.
18 |
19 | :param path: DBus object path
20 | :param iface: DBus interface name
21 | """
22 |
23 | bus = dbus.SessionBus()
24 |
25 | @staticmethod
26 | def convert(val):
27 | """
28 | Convert a DBus-typed value to its python-type.
29 |
30 | :param val: Value to convert
31 | :return: The python-typed value
32 | """
33 | if isinstance(val, (dbus.String, dbus.ObjectPath)):
34 | return str(val)
35 | if isinstance(val, dbus.Boolean):
36 | return bool(val)
37 | if isinstance(val, dbus.UInt32) or isinstance(val, dbus.UInt64):
38 | return int(val)
39 | if isinstance(val, dbus.Double):
40 | return float(val)
41 | if isinstance(val, dbus.Array):
42 | return list(map(DBusIFace.convert, list(val)))
43 |
44 | return None
45 |
46 | def __init__(self, path, iface):
47 | self.obj = DBusIFace.bus.get_object(BUS_NAME, path)
48 | self.iface = "{}.{}".format(BUS_NAME, iface)
49 | self.props = dbus.Interface(
50 | self.obj, dbus_interface="org.freedesktop.DBus.Properties"
51 | )
52 | self.interface = dbus.Interface(self.obj, dbus_interface=self.iface)
53 |
54 | def get_properties(self):
55 | """
56 | Get all DBus properties and values.
57 |
58 | :return: A dictionary mapping property names to values
59 | """
60 | return {
61 | DBusIFace.convert(k): DBusIFace.convert(v)
62 | for k, v in self.props.GetAll(self.iface).items()
63 | }
64 |
65 | def get_property(self, name):
66 | """
67 | Get a DBus property's value.
68 |
69 | :param name: The property's name
70 | :return: The property's value
71 | """
72 | return DBusIFace.convert(self.props.Get(self.iface, name))
73 |
74 |
75 | class Backlight(DBusIFace):
76 | """
77 | An interface to a sessiond Backlight object.
78 |
79 | :param name: The backlight's name
80 | """
81 |
82 | def __init__(self, name):
83 | self.name = name
84 | path = "/org/sessiond/session1/backlight/{}".format(self.name)
85 | super().__init__(path, "Backlight")
86 |
87 | def set_brightness(self, v):
88 | """
89 | Set the backlight's brightness.
90 |
91 | :param v: Brightness value
92 | :raises dbus.exception.DBusException: Raised if unable to set brightness
93 | """
94 | self.interface.SetBrightness(v)
95 |
96 | def inc_brightness(self, v):
97 | """
98 | Increment the backlight's brightness.
99 |
100 | :param v: Brightness value by which to increment
101 | :return: The new brightness value
102 | :raises dbus.exception.DBusException: Raised if unable to set brightness
103 | """
104 | return self.interface.IncBrightness(v)
105 |
106 | @property
107 | def online(self):
108 | """
109 | Backlight's online status.
110 |
111 | :return: `True` if online, `False` otherwise
112 | """
113 | return self.get_property("Online")
114 |
115 | @property
116 | def subsystem(self):
117 | """
118 | Backlight's subsystem.
119 |
120 | :return: Name of backlight's subsystem
121 | """
122 | return self.get_property("Subsystem")
123 |
124 | @property
125 | def sys_path(self):
126 | """
127 | Backlight device's /sys path.
128 |
129 | :return: Path to the device via sys mount point
130 | """
131 | return self.get_property("SysPath")
132 |
133 | @property
134 | def dev_path(self):
135 | """
136 | Backlight device's path.
137 |
138 | :return: Path to the backlight device without the sys mount point
139 | """
140 | return self.get_property("DevPath")
141 |
142 | @property
143 | def max_brightness(self):
144 | """
145 | Backlight's maximum brightness.
146 |
147 | :return: Maximum brightness value
148 | """
149 | return self.get_property("MaxBrightness")
150 |
151 | @property
152 | def brightness(self):
153 | """
154 | Backlight's brightness.
155 |
156 | :return: Brightness value
157 | """
158 | return self.get_property("Brightness")
159 |
160 |
161 | class AudioSink(DBusIFace):
162 | """
163 | An interface to a sessiond AudioSink object.
164 |
165 | :param id: The audio sink's ID
166 | """
167 |
168 | def __init__(self, id):
169 | self.as_id = id
170 | path = "/org/sessiond/session1/audiosink/{}".format(self.as_id)
171 | super().__init__(path, "AudioSink")
172 |
173 | def set_volume(self, v):
174 | """
175 | Set the audio sink's volume.
176 |
177 | :param v: Volume value
178 | :raises dbus.exception.DBusException: Raised if unable to set volume
179 | """
180 | self.interface.SetVolume(v)
181 |
182 | def inc_volume(self, v):
183 | """
184 | Increment the audio sink's volume.
185 |
186 | :param v: Volume value by which to increment
187 | :return: The new volume value
188 | :raises dbus.exception.DBusException: Raised if unable to increment volume
189 | """
190 | return self.interface.IncVolume(v)
191 |
192 | def set_mute(self, m):
193 | """
194 | Set the audio sink's mute state.
195 |
196 | :param m: Mute state
197 | :raises dbus.exception.DBusException: Raised if unable to set mute state
198 | """
199 | self.interface.SetMute(m)
200 |
201 | def toggle_mute(self):
202 | """
203 | Toggle the audio sink's mute state.
204 |
205 | :return: The new mute state
206 | :raises dbus.exception.DBusException: Raised if unable to toggle mute state
207 | """
208 | return self.interface.ToggleMute()
209 |
210 | @property
211 | def id(self):
212 | """
213 | Audio sink's ID.
214 |
215 | :return: Audio sink's ID
216 | """
217 | return self.get_property("Id")
218 |
219 | @property
220 | def name(self):
221 | """
222 | Audio sink's name.
223 |
224 | :return: Audio sink's name
225 | """
226 | return self.get_property("Name")
227 |
228 | @property
229 | def mute(self):
230 | """
231 | Audio sink's mute state.
232 |
233 | :return: `True` if muted, `False` otherwise
234 | """
235 | return self.get_property("Mute")
236 |
237 | @property
238 | def volume(self):
239 | """
240 | Audio sink's volume.
241 |
242 | :return: Audio sink's volume
243 | """
244 | return self.get_property("Volume")
245 |
246 |
247 | class Session(DBusIFace):
248 | """
249 | An interface to sessiond's Session.
250 | """
251 |
252 | def __init__(self):
253 | super().__init__("/org/sessiond/session1", "Session")
254 |
255 | def inhibit(self, who="", why=""):
256 | """
257 | Add an inhibitor.
258 |
259 | :param who: A string describing who is inhibiting
260 | :param why: A string describing why this inhibitor is running
261 | :return: The inhibitor's ID
262 | """
263 | return self.interface.Inhibit(who, why)
264 |
265 | def uninhibit(self, id):
266 | """
267 | Remove an inhibitor.
268 |
269 | :param id: The inhibitor's ID
270 | :raises dbus.exception.DBusException: Raised if the ID is not valid or \
271 | does not exist
272 | """
273 | self.interface.Uninhibit(id)
274 |
275 | def stop_inhibitors(self):
276 | """
277 | Stop running inhibitors.
278 |
279 | :return: The number of inhibitors stopped
280 | """
281 | return self.interface.StopInhibitors()
282 |
283 | def list_inhibitors(self):
284 | """
285 | List running inhibitors.
286 |
287 | :return: A dictionary mapping IDs to tuples of the creation timestamp \
288 | and 'who' and 'why' strings
289 | """
290 | return {
291 | str(k): (int(s[0]), str(s[1]), str(s[2]))
292 | for k, s in self.interface.ListInhibitors().items()
293 | }
294 |
295 | def lock(self):
296 | """
297 | Lock the session.
298 |
299 | :raises dbus.exception.DBusException: Raised if the session is already \
300 | locked
301 | """
302 | self.interface.Lock()
303 |
304 | def unlock(self):
305 | """
306 | Unlock the session.
307 |
308 | :raises dbus.exception.DBusException: Raised if the session is not \
309 | locked
310 | """
311 | self.interface.Unlock()
312 |
313 | @property
314 | def backlights(self):
315 | """
316 | List of backlights.
317 |
318 | :return: A list of Backlight DBus object paths
319 | """
320 | return self.get_property("Backlights")
321 |
322 | @property
323 | def audiosinks(self):
324 | """
325 | List of audio sinks.
326 |
327 | :return: A list of AudioSink DBus object paths
328 | """
329 | return self.get_property("AudioSinks")
330 |
331 | @property
332 | def default_audiosink(self):
333 | """
334 | Get the default audio sink.
335 |
336 | :return: The default audio sink's DBus object path
337 | """
338 | return self.get_property("DefaultAudioSink")
339 |
340 | @property
341 | def idle_hint(self):
342 | """
343 | Session idle hint.
344 |
345 | :return: `True` if session is idle, `False` otherwise
346 | """
347 | return self.get_property("IdleHint")
348 |
349 | @property
350 | def inhibited_hint(self):
351 | """
352 | Session inhibited hint.
353 |
354 | :return: `True` if session is inhibited, `False` otherwise
355 | """
356 | return self.get_property("InhibitedHint")
357 |
358 | @property
359 | def locked_hint(self):
360 | """
361 | Session locked hint.
362 |
363 | :return: `True` if session is locked, `False` otherwise
364 | """
365 | return self.get_property("LockedHint")
366 |
367 | @property
368 | def version(self):
369 | """
370 | sessiond version.
371 |
372 | :return: sessiond's version string
373 | """
374 | return self.get_property("Version")
375 |
376 | @property
377 | def idle_since_hint(self):
378 | """
379 | The timestamp of the last change to IdleHint.
380 |
381 | :return: The timestamp
382 | """
383 | return self.get_property("IdleSinceHint")
384 |
385 | @property
386 | def idle_since_hint_monotonic(self):
387 | """
388 | The timestamp of the last change to IdleHint in monotonic time.
389 |
390 | :return: The timestamp
391 | """
392 | return self.get_property("IdleSinceHintMonotonic")
393 |
--------------------------------------------------------------------------------
/python-sessiond/setup.py:
--------------------------------------------------------------------------------
1 | from setuptools import setup
2 |
3 |
4 | setup(
5 | name="sessiond",
6 | version="0.1.0",
7 | py_modules=["sessiond"],
8 | install_requires=["dbus-python"],
9 | description="Interface to sessiond DBus service",
10 | url="https://github.com/jcrd/sessiond/python-sessiond",
11 | license="MIT",
12 | author="James Reed",
13 | author_email="jcrd@sessiond.org",
14 | keywords="dbus sessiond",
15 | classifiers=[
16 | "Development Status :: 2 - Pre-Alpha",
17 | "License :: OSI Approved :: MIT License",
18 | ],
19 | )
20 |
--------------------------------------------------------------------------------
/scripts/install_sysfs_writer.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | # install setuid helper
4 | install -Dm4755 "$MESON_BUILD_ROOT"/sessiond-sysfs-writer \
5 | "$DESTDIR/$MESON_INSTALL_PREFIX"/lib/sessiond/sessiond-sysfs-writer
6 |
--------------------------------------------------------------------------------
/sessionctl:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python3
2 |
3 | # sessiond - standalone X session manager
4 | # Copyright (C) 2020 James Reed
5 | #
6 | # This program is free software: you can redistribute it and/or modify it under
7 | # the terms of the GNU General Public License as published by the Free Software
8 | # Foundation, either version 3 of the License, or (at your option) any later
9 | # version.
10 | #
11 | # This program is distributed in the hope that it will be useful, but WITHOUT
12 | # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
13 | # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
14 | #
15 | # You should have received a copy of the GNU General Public License along with
16 | # this program. If not, see .
17 |
18 | import logging
19 | import sys
20 | from argparse import ArgumentParser
21 | from pathlib import PurePath
22 | from subprocess import run
23 | from time import sleep
24 |
25 | from dbus.exceptions import DBusException
26 | from sessiond import Session, Backlight, AudioSink
27 |
28 | SESSIOND_SESSION = "sessiond-session.target"
29 | GRAPHICAL_SESSION = "graphical-session.target"
30 | WINDOW_MANAGER_ALIAS = "window-manager.service"
31 |
32 |
33 | def log_error(msg):
34 | sys.stderr.write(msg)
35 | logging.error(msg)
36 |
37 |
38 | def _run(cmd, **kw):
39 | return run(cmd.split(), **kw)
40 |
41 |
42 | def consists_of(unit=GRAPHICAL_SESSION):
43 | cmd = "systemctl --user show -p ConsistsOf --value {}".format(unit)
44 | return _run(cmd, capture_output=True, text=True, check=True).stdout.split()
45 |
46 |
47 | def stop_session(step=0.1, timeout=5):
48 | def check_active():
49 | for u in consists_of():
50 | if Unit(u).active():
51 | return True
52 | return False
53 |
54 | Unit(SESSIOND_SESSION).stop()
55 |
56 | while check_active():
57 | if timeout == 0:
58 | for n in consists_of():
59 | u = Unit(n)
60 | if u.active():
61 | u.kill()
62 | break
63 | sleep(step)
64 | timeout -= step
65 |
66 |
67 | def get_window_manager():
68 | cmd = "systemctl --user show -p Id --value {}".format(WINDOW_MANAGER_ALIAS)
69 | wm = _run(cmd, capture_output=True, text=True).stdout.rstrip()
70 | if wm == WINDOW_MANAGER_ALIAS:
71 | raise ValueError("No window manager is enabled")
72 | return wm
73 |
74 |
75 | class Unit:
76 | def __init__(self, name):
77 | self.name = name
78 |
79 | def active(self):
80 | cmd = "systemctl --user -q is-active {}".format(self.name)
81 | return _run(cmd).returncode == 0
82 |
83 | def failed(self):
84 | cmd = "systemctl --user -q is-failed {}".format(self.name)
85 | return _run(cmd).returncode == 0
86 |
87 | def status(self):
88 | cmd = "systemctl --user is-active {}".format(self.name)
89 | return _run(cmd, capture_output=True, text=True).stdout.rstrip()
90 |
91 | def stop(self):
92 | _run("systemctl --user stop {}".format(self.name), check=True)
93 |
94 | def reset(self):
95 | if not self.failed():
96 | return
97 | _run("systemctl --user reset-failed {}".format(self.name), check=True)
98 |
99 | def kill(self):
100 | _run("systemctl --user kill {}".format(self.name), check=True)
101 |
102 | def start_wait(self):
103 | _run("systemctl --user --wait restart {}".format(self.name))
104 | stop_session()
105 |
106 |
107 | def status():
108 | g = Unit(GRAPHICAL_SESSION)
109 | if not g.active():
110 | sys.stderr.write("Session is not active\n")
111 | sys.exit(1)
112 | units = consists_of()
113 | units.sort()
114 | n = len(max(units, key=len))
115 | status = {}
116 | for u in units:
117 | status[u] = Unit(u).status()
118 | for u, s in status.items():
119 | print("{u: <{n}} {s}".format(u=u + ":", n=n + 1, s=s))
120 |
121 |
122 | def get_session():
123 | try:
124 | return Session()
125 | except DBusException as e:
126 | log_error(
127 | "Failed to connect to sessiond DBus service: {}".format(
128 | e.get_dbus_message()
129 | )
130 | )
131 | sys.exit(1)
132 |
133 |
134 | def add_audiosinks_parser(sp):
135 | p = sp.add_parser("audiosink", help="Interact with audio sinks")
136 | p.add_argument("id", nargs="?", metavar="ID")
137 | p.add_argument(
138 | "-s",
139 | "--set",
140 | type=float,
141 | metavar="VALUE",
142 | help="Set audio sink volume",
143 | )
144 | p.add_argument(
145 | "-i",
146 | "--inc",
147 | type=float,
148 | metavar="VALUE",
149 | help="Increment audio sink volume",
150 | )
151 | p.add_argument(
152 | "-m",
153 | "--mute",
154 | action="store_true",
155 | help="Mute audio sink",
156 | )
157 | p.add_argument(
158 | "-u",
159 | "--unmute",
160 | action="store_true",
161 | help="Unmute audio sink",
162 | )
163 | p.add_argument(
164 | "-t",
165 | "--toggle-mute",
166 | action="store_true",
167 | help="Toggle audio sink mute state",
168 | )
169 |
170 | return p
171 |
172 |
173 | if __name__ == "__main__":
174 | logging.basicConfig(
175 | filename=".sessionctl.log", filemode="w", encoding="utf-8", level=logging.ERROR
176 | )
177 |
178 | p = ArgumentParser(description="With no arguments, show session status.")
179 | subp = p.add_subparsers(dest="cmd")
180 |
181 | r = subp.add_parser("run", help="Run session")
182 | r.add_argument("service", nargs="?", help="Window manager service to run")
183 |
184 | subp.add_parser("stop", help="Stop the running session")
185 | subp.add_parser("status", help="Show session status")
186 | subp.add_parser("lock", help="Lock the session")
187 | subp.add_parser("unlock", help="Unlock the session")
188 | subp.add_parser("properties", help="List session properties")
189 |
190 | blp = subp.add_parser("backlight", help="Interact with backlights")
191 | blp.add_argument("name", nargs="?", metavar="NAME")
192 | blp.add_argument(
193 | "-s",
194 | "--set",
195 | type=int,
196 | metavar="VALUE",
197 | help="Set backlight brightness",
198 | )
199 | blp.add_argument(
200 | "-i",
201 | "--inc",
202 | type=int,
203 | metavar="VALUE",
204 | help="Increment backlight brightness",
205 | )
206 |
207 | asp = add_audiosinks_parser(subp)
208 |
209 | subp.add_parser("version", help="Show sessiond version")
210 |
211 | args = p.parse_args()
212 |
213 | if not args.cmd:
214 | status()
215 | sys.exit(0)
216 |
217 | if args.cmd == "run":
218 | if Unit(SESSIOND_SESSION).active():
219 | log_error("A session is already running")
220 | sys.exit(1)
221 | if not args.service:
222 | try:
223 | args.service = get_window_manager()
224 | except ValueError as e:
225 | log_error(str(e))
226 | sys.exit(1)
227 | _run("systemctl --user import-environment", check=True)
228 | for u in consists_of():
229 | Unit(u).reset()
230 | Unit(args.service).start_wait()
231 | elif args.cmd == "stop":
232 | stop_session()
233 | elif args.cmd == "status":
234 | status()
235 | elif args.cmd == "lock":
236 | get_session().lock()
237 | elif args.cmd == "unlock":
238 | get_session().unlock()
239 | elif args.cmd == "properties":
240 | for k, v in get_session().get_properties().items():
241 | print("{}={}".format(k, v))
242 | elif args.cmd == "backlight":
243 |
244 | def check_name():
245 | if not args.name:
246 | sys.stderr.write("Backlight NAME required\n\n")
247 | blp.print_help()
248 | sys.exit(2)
249 |
250 | if args.set:
251 | check_name()
252 | Backlight(args.name).set_brightness(args.set)
253 | elif args.inc:
254 | check_name()
255 | print(Backlight(args.name).inc_brightness(args.inc))
256 | elif args.name:
257 | print(Backlight(args.name).brightness)
258 | else:
259 | bls = get_session().backlights
260 | if len(bls) == 0:
261 | sys.stderr.write("No backlights\n")
262 | sys.exit(1)
263 | for o in bls:
264 | print(PurePath(o).name)
265 | elif args.cmd == "audiosink":
266 | try:
267 | s = get_session()
268 | assert s.audiosinks
269 | except DBusException:
270 | sys.stderr.write(
271 | "sessiond {} does not support audio sinks\n".format(
272 | s.version
273 | )
274 | )
275 | sys.exit(1)
276 |
277 | def check_id():
278 | if not args.id:
279 | sys.stderr.write("Audio sink ID required\n\n")
280 | asp.print_help()
281 | sys.exit(2)
282 |
283 | def muted(m):
284 | return "MUTED" if m else "UNMUTED"
285 |
286 | if args.set:
287 | check_id()
288 | AudioSink(args.id).set_volume(args.set)
289 | elif args.inc:
290 | check_id()
291 | print(round(AudioSink(args.id).inc_volume(args.inc), 2))
292 | elif args.mute:
293 | check_id()
294 | AudioSink(args.id).set_mute(True)
295 | elif args.unmute:
296 | check_id()
297 | AudioSink(args.id).set_mute(False)
298 | elif args.toggle_mute:
299 | check_id()
300 | print(muted(AudioSink(args.id).toggle_mute()))
301 | elif args.id:
302 | a = AudioSink(args.id)
303 | print(round(a.volume, 2), muted(a.mute))
304 | else:
305 | s = get_session()
306 | if len(s.audiosinks) == 0:
307 | sys.stderr.write("No audio sinks\n")
308 | sys.exit(1)
309 | def_id = 0
310 | try:
311 | def_id = int(PurePath(s.default_audiosink).name)
312 | except ValueError:
313 | pass
314 | for o in s.audiosinks:
315 | a = AudioSink(PurePath(o).name)
316 | print(
317 | "{}: {}{}".format(
318 | a.id, a.name, " [DEFAULT]" if a.id == def_id else ""
319 | )
320 | )
321 | elif args.cmd == "version":
322 | print(get_session().version)
323 |
--------------------------------------------------------------------------------
/sessiond-inhibit:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python3
2 |
3 | # sessiond - standalone X session manager
4 | # Copyright (C) 2020 James Reed
5 | #
6 | # This program is free software: you can redistribute it and/or modify it under
7 | # the terms of the GNU General Public License as published by the Free Software
8 | # Foundation, either version 3 of the License, or (at your option) any later
9 | # version.
10 | #
11 | # This program is distributed in the hope that it will be useful, but WITHOUT
12 | # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
13 | # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
14 | #
15 | # You should have received a copy of the GNU General Public License along with
16 | # this program. If not, see .
17 |
18 | import sys
19 | from argparse import ArgumentParser
20 | from subprocess import run
21 |
22 | from dbus.exceptions import DBusException
23 | from sessiond import Session
24 |
25 |
26 | def get_inhibitors(session):
27 | # Sort by timestamp.
28 | inh = sorted(session.list_inhibitors().items(), key=lambda x: x[1][0], reverse=True)
29 | if len(inh) == 0:
30 | sys.stderr.write("No inhibitors\n")
31 | sys.exit(1)
32 | return inh
33 |
34 |
35 | if __name__ == "__main__":
36 | p = ArgumentParser(description="With no command, list running inhibitors.")
37 | p.add_argument("command", nargs="?", help="Command to run")
38 | p.add_argument("-w", "--who", default="", help="Set who is inhibiting")
39 | p.add_argument("-y", "--why", default="", help="Set why this inhibitor is running")
40 | p.add_argument("-s", "--stop", action="store_true", help="Stop running inhibitors")
41 | p.add_argument(
42 | "-i", "--inhibit", action="store_true", help="Inhibit without a command"
43 | )
44 | p.add_argument(
45 | "-u",
46 | "--uninhibit",
47 | nargs="?",
48 | const=True,
49 | metavar="ID",
50 | help="Uninhibit last inhibitor or by ID",
51 | )
52 |
53 | args = p.parse_args()
54 | s = Session()
55 |
56 | if args.stop:
57 | n = s.stop_inhibitors()
58 | if n == 0:
59 | sys.exit(1)
60 | elif args.inhibit:
61 | print(s.inhibit(args.who, args.why))
62 | elif args.uninhibit:
63 | if args.uninhibit is True:
64 | # Uninhibit by first ID.
65 | args.uninhibit = get_inhibitors(s)[0][0]
66 | s.uninhibit(args.uninhibit)
67 | print(args.uninhibit)
68 | elif args.command:
69 | i = s.inhibit(args.who or args.command, args.why)
70 |
71 | def try_uninhibit():
72 | try:
73 | s.uninhibit(i)
74 | except DBusException:
75 | pass
76 |
77 | try:
78 | run(args.command, shell=True)
79 | except KeyboardInterrupt:
80 | try_uninhibit()
81 | sys.exit(130)
82 | try_uninhibit()
83 | else:
84 | for (k, t) in get_inhibitors(s):
85 | out = "id='{}' time='{}' who='{}'".format(k, t[0], t[1])
86 | if t[2]:
87 | out += " why='{}'".format(t[2])
88 | print(out)
89 |
--------------------------------------------------------------------------------
/sessiond.conf:
--------------------------------------------------------------------------------
1 | # Entries in this file show the compile time defaults.
2 | # See sessiond.conf(5) for details.
3 |
4 | [Idle]
5 | #Inputs=motion,button-press,key-press
6 | #IdleSec=1200
7 |
8 | [Lock]
9 | #OnIdle=true
10 | #OnSleep=true
11 | #StandbySec=60
12 | #SuspendSec=60
13 | #OffSec=60
14 | #MuteAudio=true
15 |
16 | [DPMS]
17 | #Enable=true
18 | #StandbySec=600
19 | #SuspendSec=600
20 | #OffSec=600
21 |
22 | [[Backlight]]
23 | #Path=
24 | #DimSec=480
25 | #DimPercent=0.3
26 |
--------------------------------------------------------------------------------
/sessiond.desktop:
--------------------------------------------------------------------------------
1 | [Desktop Entry]
2 | Name=sessiond session
3 | Comment=Runs a sessiond session
4 | TryExec=sessionctl
5 | Exec=sessionctl run
6 | Type=Application
7 |
--------------------------------------------------------------------------------
/spec/sessiond-0.6.1.spec:
--------------------------------------------------------------------------------
1 | Name: sessiond
2 | Version: 0.6.1
3 | Release: 1%{?dist}
4 | Summary: Standalone X11 session manager for logind
5 |
6 | License: GPLv3+
7 | URL: https://github.com/jcrd/sessiond
8 | Source0: https://github.com/jcrd/sessiond/archive/v0.6.1.tar.gz
9 |
10 | BuildRequires: meson
11 | BuildRequires: gcc
12 | BuildRequires: perl
13 | BuildRequires: pkgconfig(glib-2.0)
14 | BuildRequires: pkgconfig(libudev)
15 | BuildRequires: pkgconfig(x11)
16 | BuildRequires: pkgconfig(xi)
17 | BuildRequires: pkgconfig(xext)
18 | BuildRequires: pkgconfig(wireplumber-0.4)
19 | BuildRequires: python3-devel
20 | BuildRequires: python3-setuptools
21 |
22 | Requires: python3-dbus
23 |
24 | %description
25 | sessiond is a standalone X session manager that reports the idle status of a
26 | graphical session to systemd-logind. It can be used alongside a window manager
27 | or desktop environment that does not provide its own session management.
28 |
29 | %prep
30 | %setup
31 |
32 | %build
33 | %meson --libdir lib
34 | %meson_build
35 |
36 | cd python-sessiond
37 | %py3_build
38 |
39 | %install
40 | %meson_install
41 |
42 | cd python-sessiond
43 | %py3_install
44 |
45 | %check
46 | %meson_test
47 |
48 | %files
49 | %license LICENSE
50 | %doc README.md
51 | %{_bindir}/sessionctl
52 | %{_bindir}/sessiond
53 | %{_bindir}/sessiond-inhibit
54 | /usr/lib/systemd/user/graphical-idle.target
55 | /usr/lib/systemd/user/graphical-lock.target
56 | /usr/lib/systemd/user/graphical-unidle.target
57 | /usr/lib/systemd/user/graphical-unlock.target
58 | /usr/lib/systemd/user/sessiond-session.target
59 | /usr/lib/systemd/user/sessiond.service
60 | /usr/lib/systemd/user/user-shutdown.target
61 | /usr/lib/systemd/user/user-sleep.target
62 | /usr/lib/systemd/user/user-sleep-finished.target
63 | %{_mandir}/man1/sessionctl.1.gz
64 | %{_mandir}/man1/sessiond-inhibit.1.gz
65 | %{_mandir}/man1/sessiond.1.gz
66 | %{_mandir}/man5/sessiond-hooks.5.gz
67 | %{_mandir}/man5/sessiond.conf.5.gz
68 | %{_mandir}/man8/sessiond-dbus.8.gz
69 | %{_datadir}/sessiond/sessiond.conf
70 | %{_datadir}/xsessions/sessiond.desktop
71 |
72 | %{python3_sitelib}/%{name}-*.egg-info/
73 | %{python3_sitelib}/%{name}.py
74 | %{python3_sitelib}/__pycache__/%{name}.*
75 |
76 | %changelog
77 | * Wed Jan 26 2022 James Reed - 0.6.1-1
78 | - Release v0.6.1 hotfix
79 |
80 | * Mon Jan 24 2022 James Reed - 0.6.0-3
81 | - Add missing file declaration
82 | - Third time's a charm...
83 |
84 | * Mon Jan 24 2022 James Reed - 0.6.0-2
85 | - Add missing build requirements
86 |
87 | * Mon Jan 24 2022 James Reed - 0.6.0-1
88 | - Release v0.6.0
89 |
90 | * Mon Apr 12 2021 James Reed - 0.5.0-1
91 | - Release v0.5.0
92 |
93 | * Fri Apr 2 2021 James Reed - 0.4.0-1
94 | - Release v0.4.0
95 |
96 | * Thu Dec 31 2020 James Reed - 0.3.1-1
97 | - Release v0.3.1
98 |
99 | * Sun Nov 1 2020 James Reed - 0.3.0-1
100 | - Release v0.3.0
101 |
102 | * Wed Jun 17 2020 James Reed - 0.2.0-1
103 | - Release v0.2.0
104 |
105 | * Sat May 23 2020 James Reed - 0.1.0-2
106 | - Use pkgconfig in build requires
107 |
108 | * Mon May 11 2020 James Reed - 0.1.0
109 | - Initial package
110 |
--------------------------------------------------------------------------------
/spec/sessiond.rpkg.spec:
--------------------------------------------------------------------------------
1 | Name: {{{ git_cwd_name name="sessiond" }}}
2 | Version: {{{ git_cwd_version lead="$(git tag | sed -n 's/^v//p' | sort --version-sort -r | head -n1)" }}}
3 | Release: 1%{?dist}
4 | Summary: Standalone X11 session manager for logind
5 |
6 | License: GPLv3+
7 | URL: https://github.com/jcrd/sessiond
8 | VCS: {{{ git_cwd_vcs }}}
9 | Source0: {{{ git_cwd_pack }}}
10 |
11 | BuildRequires: meson
12 | BuildRequires: gcc
13 | BuildRequires: perl
14 | BuildRequires: pkgconfig(glib-2.0)
15 | BuildRequires: pkgconfig(libudev)
16 | BuildRequires: pkgconfig(x11)
17 | BuildRequires: pkgconfig(xi)
18 | BuildRequires: pkgconfig(xext)
19 | BuildRequires: pkgconfig(wireplumber-0.4)
20 | BuildRequires: python3-devel
21 | BuildRequires: python3-setuptools
22 |
23 | Requires: python3-dbus
24 |
25 | %description
26 | sessiond is a standalone X session manager that reports the idle status of a
27 | graphical session to systemd-logind. It can be used alongside a window manager
28 | or desktop environment that does not provide its own session management.
29 |
30 | %prep
31 | {{{ git_cwd_setup_macro }}}
32 |
33 | %build
34 | %meson --libdir lib
35 | %meson_build
36 |
37 | cd python-sessiond
38 | %py3_build
39 |
40 | %install
41 | %meson_install
42 |
43 | cd python-sessiond
44 | %py3_install
45 |
46 | %check
47 | %meson_test
48 |
49 | %files
50 | %license LICENSE
51 | %doc README.md
52 | %{_bindir}/sessionctl
53 | %{_bindir}/sessiond
54 | %{_bindir}/sessiond-inhibit
55 | /usr/lib/systemd/user/graphical-idle.target
56 | /usr/lib/systemd/user/graphical-lock.target
57 | /usr/lib/systemd/user/graphical-unidle.target
58 | /usr/lib/systemd/user/graphical-unlock.target
59 | /usr/lib/systemd/user/sessiond-session.target
60 | /usr/lib/systemd/user/sessiond.service
61 | /usr/lib/systemd/user/user-shutdown.target
62 | /usr/lib/systemd/user/user-sleep.target
63 | /usr/lib/systemd/user/user-sleep-finished.target
64 | %{_mandir}/man1/sessionctl.1.gz
65 | %{_mandir}/man1/sessiond-inhibit.1.gz
66 | %{_mandir}/man1/sessiond.1.gz
67 | %{_mandir}/man5/sessiond-hooks.5.gz
68 | %{_mandir}/man5/sessiond.conf.5.gz
69 | %{_mandir}/man8/sessiond-dbus.8.gz
70 | %{_datadir}/sessiond/sessiond.conf
71 | %{_datadir}/xsessions/sessiond.desktop
72 |
73 | %{python3_sitelib}/%{name}-*.egg-info/
74 | %{python3_sitelib}/%{name}.py
75 | %{python3_sitelib}/__pycache__/%{name}.*
76 |
77 | %changelog
78 | {{{ git_cwd_changelog }}}
79 |
--------------------------------------------------------------------------------
/src/backlight.c:
--------------------------------------------------------------------------------
1 | /*
2 | sessiond - standalone X session manager
3 | Copyright (C) 2018-2020 James Reed
4 |
5 | This program is free software: you can redistribute it and/or modify it under
6 | the terms of the GNU General Public License as published by the Free Software
7 | Foundation, either version 3 of the License, or (at your option) any later
8 | version.
9 |
10 | This program is distributed in the hope that it will be useful, but WITHOUT ANY
11 | WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
12 | PARTICULAR PURPOSE. See the GNU General Public License for more details.
13 |
14 | You should have received a copy of the GNU General Public License along with
15 | this program. If not, see .
16 | */
17 |
18 | #define G_LOG_DOMAIN "sessiond"
19 |
20 | #include "backlight.h"
21 | #include "common.h"
22 | #include "config.h"
23 | #include "dbus-logind.h"
24 |
25 | #include
26 | #include
27 | #include
28 | #include
29 | #include
30 | #include
31 |
32 | #ifndef PREFIX
33 | #define PREFIX "/usr/local"
34 | #endif /* PREFIX */
35 |
36 | #define SYSFS_WRITER PREFIX "/lib/sessiond/sessiond-sysfs-writer"
37 |
38 | static gchar *
39 | get_sys_path(const char *subsystem, const char *name)
40 | {
41 | return g_strjoin("/", "", "sys", "class", subsystem, name, NULL);
42 | }
43 |
44 | static gint32
45 | get_uint_sysattr(struct Backlight *b, const char *attr)
46 | {
47 | const char *str = udev_device_get_sysattr_value(b->device, attr);
48 | if (!str)
49 | return -1;
50 | gchar *end;
51 | errno = 0;
52 | guint64 i = g_ascii_strtoull(str, &end, 10);
53 |
54 | if (errno || end == str) {
55 | g_warning("Failed to parse backlight (%s) attr: %s", b->sys_path, attr);
56 | return -1;
57 | }
58 |
59 | return i;
60 | }
61 |
62 | static gboolean
63 | update_backlight(struct Backlight *bl, struct udev_device *dev)
64 | {
65 | if (bl->device)
66 | udev_device_unref(bl->device);
67 | if (!((bl->name = udev_device_get_sysname(dev)) &&
68 | (bl->subsystem = udev_device_get_subsystem(dev)) &&
69 | (bl->sys_path = get_sys_path(bl->subsystem, bl->name)) &&
70 | (bl->dev_path = udev_device_get_devpath(dev))))
71 | return FALSE;
72 | bl->device = udev_device_ref(dev);
73 | bl->brightness = get_uint_sysattr(bl, "brightness");
74 | bl->max_brightness = get_uint_sysattr(bl, "max_brightness");
75 |
76 | return TRUE;
77 | }
78 |
79 | static struct Backlight *
80 | new_backlight(struct udev_device *dev)
81 | {
82 | struct Backlight *bl = g_malloc(sizeof(struct Backlight));
83 |
84 | bl->device = NULL;
85 | bl->online = TRUE;
86 | bl->pre_dim_brightness = -1;
87 | if (!update_backlight(bl, dev)) {
88 | g_free(bl);
89 | return NULL;
90 | }
91 |
92 | return bl;
93 | }
94 |
95 | static void
96 | free_backlight(struct Backlight *bl)
97 | {
98 | udev_device_unref(bl->device);
99 | g_free(bl->sys_path);
100 | g_free(bl);
101 | }
102 |
103 | static BacklightAction
104 | get_action(struct udev_device *dev)
105 | {
106 | const char *action = udev_device_get_action(dev);
107 |
108 | if (!action)
109 | return BL_ACTION_ADD;
110 |
111 | #define X(a, str) \
112 | if (g_strcmp0(action, str) == 0) \
113 | return BL_ACTION_##a;
114 | BL_ACTION_LIST
115 | #undef X
116 |
117 | return BL_ACTION_ADD;
118 | }
119 |
120 | static gboolean
121 | device_is_backlight(struct udev_device *dev)
122 | {
123 | const char *sys = udev_device_get_subsystem(dev);
124 | const char *path = udev_device_get_syspath(dev);
125 | gboolean bl = g_strcmp0(sys, "backlight") == 0;
126 | return bl || g_str_has_suffix(path, "::kbd_backlight");
127 | }
128 |
129 | static gboolean
130 | source_prepare(GSource *source, gint *timeout)
131 | {
132 | Backlights *self = (Backlights *)source;
133 | *timeout = -1;
134 | return !g_queue_is_empty(self->queue);
135 | }
136 |
137 | static gboolean
138 | source_check(GSource *source)
139 | {
140 | Backlights *self = (Backlights *)source;
141 | GIOCondition revents = g_source_query_unix_fd(source, self->fd);
142 |
143 | if (revents & G_IO_IN) {
144 | struct udev_device *dev;
145 | while ((dev = udev_monitor_receive_device(self->udev_mon)))
146 | if (device_is_backlight(dev))
147 | g_queue_push_tail(self->queue, dev);
148 | }
149 |
150 | return !g_queue_is_empty(self->queue);
151 | }
152 |
153 | static gboolean
154 | source_dispatch(GSource *source, GSourceFunc func, UNUSED gpointer user_data)
155 | {
156 | Backlights *self = (Backlights *)source;
157 | struct udev_device *dev = g_queue_pop_head(self->queue);
158 | gchar *sys_path = get_sys_path(udev_device_get_subsystem(dev),
159 | udev_device_get_sysname(dev));
160 | BacklightAction action = get_action(dev);
161 | struct Backlight *bl = NULL;
162 | gboolean ret = TRUE;
163 |
164 | switch (action) {
165 | case BL_ACTION_ADD:
166 | bl = new_backlight(dev);
167 | if (!bl || !g_hash_table_insert(self->devices, (char *)sys_path, bl))
168 | goto end;
169 | break;
170 | case BL_ACTION_REMOVE:
171 | if (!g_hash_table_remove(self->devices, sys_path))
172 | goto end;
173 | break;
174 | case BL_ACTION_CHANGE:
175 | bl = g_hash_table_lookup(self->devices, sys_path);
176 | if (!bl)
177 | goto end;
178 | update_backlight(bl, dev);
179 | break;
180 | case BL_ACTION_ONLINE:
181 | bl = g_hash_table_lookup(self->devices, sys_path);
182 | if (!bl || bl->online)
183 | goto end;
184 | bl->online = TRUE;
185 | break;
186 | case BL_ACTION_OFFLINE:
187 | bl = g_hash_table_lookup(self->devices, sys_path);
188 | if (!bl || !bl->online)
189 | goto end;
190 | bl->online = FALSE;
191 | break;
192 | }
193 |
194 | ret = ((BacklightsFunc)func)(action, sys_path, bl);
195 |
196 | end:
197 | g_free(sys_path);
198 | udev_device_unref(dev);
199 | return ret;
200 | }
201 |
202 | static void
203 | source_finalize(GSource *source)
204 | {
205 | Backlights *self = (Backlights *)source;
206 |
207 | g_queue_free_full(self->queue, (GDestroyNotify)udev_device_unref);
208 | g_hash_table_unref(self->devices);
209 | udev_monitor_unref(self->udev_mon);
210 | udev_unref(self->udev);
211 | }
212 |
213 | static GSourceFuncs source_funcs = {
214 | source_prepare,
215 | source_check,
216 | source_dispatch,
217 | source_finalize,
218 | NULL,
219 | NULL,
220 | };
221 |
222 | static void
223 | backlights_init_devices(Backlights *self, BacklightsFunc func)
224 | {
225 | struct udev_enumerate *e = udev_enumerate_new(self->udev);
226 |
227 | udev_enumerate_add_match_subsystem(e, "backlight");
228 | udev_enumerate_add_match_subsystem(e, "leds");
229 | udev_enumerate_scan_devices(e);
230 |
231 | struct udev_list_entry *devs = udev_enumerate_get_list_entry(e);
232 | struct udev_list_entry *entry;
233 |
234 | udev_list_entry_foreach(entry, devs) {
235 | const char *name = udev_list_entry_get_name(entry);
236 | struct udev_device *dev = udev_device_new_from_syspath(self->udev,
237 | name);
238 |
239 | if (!device_is_backlight(dev))
240 | continue;
241 |
242 | struct Backlight *bl = new_backlight(dev);
243 | udev_device_unref(dev);
244 |
245 | if (g_hash_table_insert(self->devices, (char *)bl->sys_path, bl))
246 | func(BL_ACTION_ADD, bl->sys_path, bl);
247 | }
248 |
249 | udev_enumerate_unref(e);
250 | }
251 |
252 | static gboolean
253 | set_backlight_brightness(struct Backlight *bl, guint32 v)
254 | {
255 | #ifndef BACKLIGHT_HELPER
256 | return FALSE;
257 | #endif
258 |
259 | if (bl->max_brightness == -1)
260 | return FALSE;
261 |
262 | v = MIN(v, bl->max_brightness);
263 |
264 | gchar *str = g_strdup_printf("%u", v);
265 | gchar *brightness = g_strjoin("/", bl->sys_path, "brightness", NULL);
266 | gchar *argv[] = {SYSFS_WRITER, brightness, str, NULL};
267 |
268 | if (spawn_exec(argv) == 0)
269 | g_debug("Set %s brightness: %u", bl->sys_path, v);
270 |
271 | g_free(brightness);
272 | g_free(str);
273 |
274 | return TRUE;
275 | }
276 |
277 | static void
278 | backlight_dim_value(struct Backlight *bl, guint32 v, LogindContext *ctx)
279 | {
280 | bl->pre_dim_brightness = bl->brightness;
281 | backlight_set_brightness(bl, v, ctx);
282 | }
283 |
284 | static void
285 | backlight_dim_percent(struct Backlight *bl, gdouble percent, LogindContext *ctx)
286 | {
287 | gint32 v = bl->brightness;
288 | if (v == -1)
289 | return;
290 | bl->pre_dim_brightness = v;
291 | gdouble d = v - v * percent;
292 | backlight_set_brightness(bl, (guint32)(d > 0 ? d + 0.5 : d), ctx);
293 | }
294 |
295 | static void
296 | backlight_restore(struct Backlight *bl, LogindContext *ctx)
297 | {
298 | if (bl->pre_dim_brightness == -1)
299 | return;
300 | backlight_set_brightness(bl, bl->pre_dim_brightness, ctx);
301 | bl->pre_dim_brightness = -1;
302 | }
303 |
304 | Backlights *
305 | backlights_new(GMainContext *ctx, BacklightsFunc func)
306 | {
307 | GSource *source = g_source_new(&source_funcs, sizeof(Backlights));
308 | Backlights *self = (Backlights *)source;
309 |
310 | self->udev = udev_new();
311 | self->udev_mon = udev_monitor_new_from_netlink(self->udev, "udev");
312 |
313 | udev_monitor_filter_add_match_subsystem_devtype(self->udev_mon,
314 | "backlight", NULL);
315 | udev_monitor_filter_add_match_subsystem_devtype(self->udev_mon,
316 | "leds", NULL);
317 | udev_monitor_enable_receiving(self->udev_mon);
318 |
319 | self->queue = g_queue_new();
320 | self->devices = g_hash_table_new_full(g_str_hash, g_str_equal, NULL,
321 | (GDestroyNotify)free_backlight);
322 |
323 | backlights_init_devices(self, func);
324 |
325 | self->fd = g_source_add_unix_fd(source,
326 | udev_monitor_get_fd(self->udev_mon), G_IO_IN);
327 |
328 | g_source_set_callback(source, (GSourceFunc)func, NULL, NULL);
329 | g_source_attach(source, ctx);
330 |
331 | return self;
332 | }
333 |
334 | void
335 | backlights_free(Backlights *bls)
336 | {
337 | if (!bls)
338 | return;
339 | GSource *source = (GSource *)bls;
340 | g_source_destroy(source);
341 | g_source_unref(source);
342 | }
343 |
344 | void
345 | backlights_restore(GHashTable *devs, LogindContext *ctx)
346 | {
347 | GHashTableIter iter;
348 | gpointer bl;
349 |
350 | g_hash_table_iter_init(&iter, devs);
351 | while (g_hash_table_iter_next(&iter, NULL, &bl))
352 | backlight_restore(bl, ctx);
353 | }
354 |
355 | gchar *
356 | backlight_normalize_name(const char *name)
357 | {
358 | GRegex *regex = g_regex_new("::", 0, 0, NULL);
359 | gchar *norm = g_regex_replace(regex, name, -1, 0, "_", 0, NULL);
360 | g_regex_unref(regex);
361 |
362 | return norm;
363 | }
364 |
365 | gboolean
366 | backlight_set_brightness(struct Backlight *bl, guint32 v, LogindContext *ctx)
367 | {
368 | if (!logind_set_brightness(ctx, bl->subsystem, bl->name, v))
369 | return set_backlight_brightness(bl, v);
370 |
371 | return TRUE;
372 | }
373 |
374 | void
375 | backlights_on_timeout(GHashTable *devs, GHashTable *cs, guint timeout,
376 | gboolean state, LogindContext *ctx)
377 | {
378 | GHashTableIter iter;
379 | gpointer key, val;
380 |
381 | g_hash_table_iter_init(&iter, cs);
382 | while (g_hash_table_iter_next(&iter, &key, &val)) {
383 | const char *sys_path = key;
384 | struct BacklightConf *c = val;
385 |
386 | if (c->dim_sec != timeout)
387 | continue;
388 |
389 | struct Backlight *bl = g_hash_table_lookup(devs, sys_path);
390 |
391 | if (!bl)
392 | continue;
393 |
394 | if (state) {
395 | if (c->dim_value != -1)
396 | backlight_dim_value(bl, c->dim_value, ctx);
397 | else
398 | backlight_dim_percent(bl, c->dim_percent, ctx);
399 | } else {
400 | backlight_restore(bl, ctx);
401 | }
402 | }
403 | }
404 |
--------------------------------------------------------------------------------
/src/backlight.h:
--------------------------------------------------------------------------------
1 | /*
2 | sessiond - standalone X session manager
3 | Copyright (C) 2018-2020 James Reed
4 |
5 | This program is free software: you can redistribute it and/or modify it under
6 | the terms of the GNU General Public License as published by the Free Software
7 | Foundation, either version 3 of the License, or (at your option) any later
8 | version.
9 |
10 | This program is distributed in the hope that it will be useful, but WITHOUT ANY
11 | WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
12 | PARTICULAR PURPOSE. See the GNU General Public License for more details.
13 |
14 | You should have received a copy of the GNU General Public License along with
15 | this program. If not, see .
16 | */
17 |
18 | #pragma once
19 |
20 | #include "dbus-logind.h"
21 |
22 | #include
23 | #include
24 |
25 | #define BL_ACTION_LIST \
26 | X(ADD, "add") \
27 | X(REMOVE, "remove") \
28 | X(CHANGE, "change") \
29 | X(ONLINE, "online") \
30 | X(OFFLINE, "offline")
31 |
32 | typedef enum {
33 | #define X(action, _) BL_ACTION_##action,
34 | BL_ACTION_LIST
35 | #undef X
36 | } BacklightAction;
37 |
38 | typedef enum {
39 | BL_TYPE_DISPLAY,
40 | BL_TYPE_KEYBOARD,
41 | } BacklightType;
42 |
43 | struct Backlight {
44 | struct udev_device *device;
45 | const char *name;
46 | const char *subsystem;
47 | gchar *sys_path;
48 | const char *dev_path;
49 | gboolean online;
50 | gint32 brightness;
51 | gint32 max_brightness;
52 | gint32 pre_dim_brightness;
53 | };
54 |
55 | typedef gboolean (*BacklightsFunc)(BacklightAction a, const gchar *path,
56 | struct Backlight *bl);
57 |
58 | typedef struct {
59 | GSource source;
60 | gpointer fd;
61 | struct udev *udev;
62 | struct udev_monitor *udev_mon;
63 | GQueue *queue;
64 | GHashTable *devices;
65 | } Backlights;
66 |
67 | extern Backlights *
68 | backlights_new(GMainContext *ctx, BacklightsFunc func);
69 | extern void
70 | backlights_free(Backlights *bls);
71 | extern void
72 | backlights_restore(GHashTable *devs, LogindContext *ctx);
73 | extern gchar *
74 | backlight_normalize_name(const char *name);
75 | extern gboolean
76 | backlight_set_brightness(struct Backlight *bl, guint32 v, LogindContext *ctx);
77 | extern void
78 | backlights_on_timeout(GHashTable *devs, GHashTable *cs, guint timeout,
79 | gboolean state, LogindContext *ctx);
80 |
--------------------------------------------------------------------------------
/src/common.c:
--------------------------------------------------------------------------------
1 | /*
2 | sessiond - standalone X session manager
3 | Copyright (C) 2018-2020 James Reed
4 |
5 | This program is free software: you can redistribute it and/or modify it under
6 | the terms of the GNU General Public License as published by the Free Software
7 | Foundation, either version 3 of the License, or (at your option) any later
8 | version.
9 |
10 | This program is distributed in the hope that it will be useful, but WITHOUT ANY
11 | WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
12 | PARTICULAR PURPOSE. See the GNU General Public License for more details.
13 |
14 | You should have received a copy of the GNU General Public License along with
15 | this program. If not, see .
16 | */
17 |
18 | #include "common.h"
19 |
20 | #include
21 | #include
22 |
23 | gint
24 | spawn_exec(gchar **argv)
25 | {
26 | gint status = -1;
27 | gchar *std_out = NULL;
28 | gchar *std_err = NULL;
29 | GError *err = NULL;
30 |
31 | g_spawn_sync(NULL, argv, NULL, G_SPAWN_DEFAULT, NULL, NULL,
32 | &std_out, &std_err, &status, &err);
33 |
34 | if (err)
35 | goto error;
36 |
37 | if (std_out) {
38 | fprintf(stdout, "%s\n", std_out);
39 | g_free(std_out);
40 | }
41 | if (std_err) {
42 | fprintf(stderr, "%s\n", std_err);
43 | g_free(std_err);
44 | }
45 |
46 | g_spawn_check_wait_status(status, &err);
47 |
48 | if (err) {
49 | error:
50 | g_warning("%s", err->message);
51 | g_error_free(err);
52 | return status;
53 | }
54 |
55 | return status;
56 | }
57 |
--------------------------------------------------------------------------------
/src/common.h:
--------------------------------------------------------------------------------
1 | /*
2 | sessiond - standalone X session manager
3 | Copyright (C) 2018-2020 James Reed
4 |
5 | This program is free software: you can redistribute it and/or modify it under
6 | the terms of the GNU General Public License as published by the Free Software
7 | Foundation, either version 3 of the License, or (at your option) any later
8 | version.
9 |
10 | This program is distributed in the hope that it will be useful, but WITHOUT ANY
11 | WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
12 | PARTICULAR PURPOSE. See the GNU General Public License for more details.
13 |
14 | You should have received a copy of the GNU General Public License along with
15 | this program. If not, see .
16 | */
17 |
18 | #pragma once
19 |
20 | #include
21 |
22 | #define UNUSED G_GNUC_UNUSED
23 | #define BOOLSTR(b) ((b) ? "true" : "false")
24 |
25 | extern gint
26 | spawn_exec(gchar **argv);
27 |
--------------------------------------------------------------------------------
/src/config.c:
--------------------------------------------------------------------------------
1 | /*
2 | sessiond - standalone X session manager
3 | Copyright (C) 2018-2020 James Reed
4 |
5 | This program is free software: you can redistribute it and/or modify it under
6 | the terms of the GNU General Public License as published by the Free Software
7 | Foundation, either version 3 of the License, or (at your option) any later
8 | version.
9 |
10 | This program is distributed in the hope that it will be useful, but WITHOUT ANY
11 | WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
12 | PARTICULAR PURPOSE. See the GNU General Public License for more details.
13 |
14 | You should have received a copy of the GNU General Public License along with
15 | this program. If not, see .
16 | */
17 |
18 | #define G_LOG_DOMAIN "sessiond"
19 |
20 | #include "config.h"
21 | #include "backlight.h"
22 | #include "hooks.h"
23 | #include "xsource.h"
24 |
25 | #include
26 | #include
27 | #include "toml/toml.h"
28 |
29 | static gchar *
30 | subsystem_from_path(const gchar *path)
31 | {
32 | GRegex *regex = g_regex_new("/sys/class/(.*)/.*", 0, 0, NULL);
33 |
34 | GMatchInfo *match;
35 | g_regex_match(regex, path, 0, &match);
36 | gchar *sys = g_match_info_fetch(match, 1);
37 |
38 | g_match_info_free(match);
39 | g_regex_unref(regex);
40 |
41 | return sys;
42 | }
43 |
44 | static struct BacklightConf *
45 | new_backlight(const gchar *path)
46 | {
47 | struct BacklightConf *bl = g_malloc(sizeof(struct BacklightConf));
48 |
49 | gchar *subsystem = subsystem_from_path(path);
50 |
51 | if (g_strcmp0(subsystem, "backlight") == 0) {
52 | bl->dim_sec = 60 * 8;
53 | bl->dim_value = -1;
54 | bl->dim_percent = 0.3;
55 | } else if (g_strcmp0(subsystem, "leds") == 0) {
56 | bl->dim_sec = 60;
57 | bl->dim_value = 0;
58 | bl->dim_percent = -1;
59 | } else {
60 | g_warning("Unrecognized backlight path: %s", path);
61 | g_free(bl);
62 | return NULL;
63 | }
64 |
65 | g_free(subsystem);
66 | return bl;
67 | }
68 |
69 | static toml_table_t *
70 | parse_file(const gchar *path)
71 | {
72 | FILE *fp = fopen(path, "r");
73 |
74 | if (!fp) {
75 | perror("fopen");
76 | return NULL;
77 | }
78 |
79 | char errbuf[BUFSIZ];
80 | toml_table_t *tab = toml_parse_file(fp, errbuf, sizeof(errbuf));
81 |
82 | if (!tab)
83 | g_warning("Failed to parse configuration %s: %s", path, errbuf);
84 |
85 | fclose(fp);
86 |
87 | return tab;
88 | }
89 |
90 | static gint
91 | load_int(toml_table_t *tab, const char *key, gint *ret)
92 | {
93 | const char *raw = toml_raw_in(tab, key);
94 |
95 | if (!raw)
96 | return 0;
97 |
98 | int64_t i;
99 |
100 | if (toml_rtoi(raw, &i) == -1) {
101 | g_warning("Failed to parse %s: expected integer", key);
102 | return -1;
103 | }
104 |
105 | *ret = (gint)i;
106 | return 0;
107 | }
108 |
109 | static gint
110 | load_uint(toml_table_t *tab, const char *key, guint *ret)
111 | {
112 | gint i = -1;
113 | gint r = load_int(tab, key, &i);
114 | if (i > -1)
115 | *ret = (guint)i;
116 | return r;
117 | }
118 |
119 | static gdouble
120 | load_double(toml_table_t *tab, const char *key, gdouble *ret)
121 | {
122 | const char *raw = toml_raw_in(tab, key);
123 |
124 | if (!raw)
125 | return 0;
126 |
127 | double d;
128 |
129 | if (toml_rtod(raw, &d) == -1) {
130 | g_warning("Failed to parse %s: expected double", key);
131 | return -1;
132 | }
133 |
134 | *ret = (gdouble)d;
135 | return 0;
136 | }
137 |
138 | static gint
139 | load_bool(toml_table_t *tab, const char *key, gboolean *ret)
140 | {
141 | const char *raw = toml_raw_in(tab, key);
142 |
143 | if (!raw)
144 | return 0;
145 |
146 | int b;
147 |
148 | if (toml_rtob(raw, &b) == -1) {
149 | g_warning("Failed to parse %s: expected boolean", key);
150 | return -1;
151 | }
152 |
153 | *ret = (gboolean)b;
154 | return 0;
155 | }
156 |
157 | static gint
158 | load_str(toml_table_t *tab, const char *key, gchar **ret)
159 | {
160 | const char *raw = toml_raw_in(tab, key);
161 |
162 | if (!raw)
163 | return 0;
164 |
165 | char *str;
166 |
167 | if (toml_rtos(raw, &str) == -1) {
168 | g_warning("Failed to parse %s: expected string", key);
169 | return -1;
170 | }
171 |
172 | *ret = g_strdup(str);
173 | return 0;
174 | }
175 |
176 | static gint
177 | load_exec(toml_table_t *tab, const char *key, gchar ***ret)
178 | {
179 | gchar *str = NULL;
180 | load_str(tab, key, &str);
181 |
182 | if (!str)
183 | return 0;
184 |
185 | gchar **argv;
186 | GError *err = NULL;
187 | g_shell_parse_argv(str, NULL, &argv, &err);
188 |
189 | if (err) {
190 | g_warning("Failed to parse %s: %s", key, err->message);
191 | g_error_free(err);
192 | g_free(str);
193 | return -1;
194 | }
195 |
196 | g_free(str);
197 | *ret = argv;
198 | return 0;
199 | }
200 |
201 | static gint
202 | load_input_mask(toml_table_t *tab, const char *key, guint *ret)
203 | {
204 | int len;
205 | toml_array_t *inputs = toml_array_in(tab, key);
206 |
207 | if (!inputs || (len = toml_array_nelem(inputs)) == 0)
208 | return 0;
209 |
210 | if (toml_array_kind(inputs) != 'v') {
211 | g_warning("Failed to parse %s: expected array of values", key);
212 | return -1;
213 | }
214 |
215 | if (toml_array_type(inputs) != 's') {
216 | g_warning("Failed to parse %s: expected array of strings", key);
217 | return -1;
218 | }
219 |
220 | guint mask = 0;
221 |
222 | for (int i = 0; i < toml_array_nelem(inputs); i++) {
223 | const char *raw = toml_raw_at(inputs, i);
224 | char *str;
225 | if (toml_rtos(raw, &str) == -1) {
226 | g_warning("Failed to parse %s at index %d", key, i);
227 | return -1;
228 | }
229 | #define X(t, n) \
230 | if (g_strcmp0(str, n) == 0) { \
231 | mask |= INPUT_TYPE_MASK(t); \
232 | goto end; \
233 | }
234 | INPUT_TYPE_LIST
235 | #undef X
236 | end:
237 | free(str);
238 | }
239 |
240 | *ret = mask;
241 | return 0;
242 | }
243 |
244 | static gint
245 | load_backlights(toml_table_t *tab, const char *key, GHashTable *out)
246 | {
247 | int len;
248 | toml_array_t *backlights = toml_array_in(tab, key);
249 |
250 | if (!backlights || (len = toml_array_nelem(backlights)) == 0)
251 | return 0;
252 |
253 | if (toml_array_kind(backlights) != 't') {
254 | g_warning("Failed to parse %s: expected array of tables", key);
255 | return -1;
256 | }
257 |
258 | for (int i = 0; i < len; i++) {
259 | toml_table_t *t = toml_table_at(backlights, i);
260 |
261 | gchar *path = NULL;
262 | load_str(t, "Path", &path);
263 |
264 | if (!path)
265 | continue;
266 |
267 | struct BacklightConf *bl = new_backlight(path);
268 |
269 | if (!bl)
270 | continue;
271 |
272 | #define X(key, type, name) \
273 | load_##type(t, key, &bl->name);
274 | BACKLIGHT_TABLE_LIST
275 | #undef X
276 |
277 | if (bl->dim_percent != -1)
278 | bl->dim_percent = CLAMP(bl->dim_percent, 0.01, 1.0);
279 |
280 | g_hash_table_insert(out, path, bl);
281 | }
282 |
283 | return 0;
284 | }
285 |
286 | static gint
287 | load_trigger(toml_table_t *tab, const char *key, guint *ret)
288 | {
289 | gchar *str = NULL;
290 | load_str(tab, key, &str);
291 |
292 | if (!str)
293 | return 0;
294 |
295 | #define X(t, n) \
296 | if (g_strcmp0(str, n) == 0) { \
297 | *ret = HOOK_TRIGGER_##t; \
298 | return 0; \
299 | }
300 | HOOK_TRIGGER_LIST
301 | #undef X
302 |
303 | g_warning("Failed to parse %s: unknown trigger %s", key, str);
304 | return -1;
305 | }
306 |
307 | static void
308 | free_hook(struct Hook *hook)
309 | {
310 | if (!hook)
311 | return;
312 | g_strfreev(hook->exec_start);
313 | g_strfreev(hook->exec_stop);
314 | g_free(hook);
315 | }
316 |
317 | static gint
318 | load_hook(toml_table_t *tab, GPtrArray *out, const gchar **err)
319 | {
320 | struct Hook *h = g_malloc0(sizeof(struct Hook));
321 |
322 | #define X(key, type, name) \
323 | load_##type(tab, key, &h->name);
324 | HOOKS_TABLE_LIST
325 | #undef X
326 |
327 | if (!h->trigger) {
328 | *err = "expected Trigger key";
329 | goto err;
330 | } else if (h->trigger == HOOK_TRIGGER_INACTIVE) {
331 | if (!h->inactive_sec) {
332 | *err = "expected InactiveSec key";
333 | goto err;
334 | }
335 | } else if (!(h->exec_start || h->exec_stop)) {
336 | *err = "expected ExecStart key or ExecStop key";
337 | goto err;
338 | }
339 |
340 | g_ptr_array_add(out, h);
341 | return 0;
342 |
343 | err:
344 | free_hook(h);
345 | return -1;
346 | }
347 |
348 | static gint
349 | load_hooks(toml_table_t *tab, const char *key, GPtrArray *out)
350 | {
351 | int len;
352 | toml_array_t *hooks = toml_array_in(tab, key);
353 |
354 | if (!hooks || (len = toml_array_nelem(hooks)) == 0)
355 | return 0;
356 |
357 | if (toml_array_kind(hooks) != 't') {
358 | g_warning("Failed to parse %s: expected array of tables", key);
359 | return -1;
360 | }
361 |
362 | for (int i = 0; i < len; i++) {
363 | const gchar *err;
364 | if (load_hook(toml_table_at(hooks, i), out, &err) == -1) {
365 | g_warning("Failed to parse %s at index %d: %s", key, i, err);
366 | return -1;
367 | }
368 | }
369 |
370 | return 0;
371 | }
372 |
373 | static gint
374 | load_hooks_dir(const gchar *path, GPtrArray *out)
375 | {
376 | if (!g_file_test(path, G_FILE_TEST_IS_DIR)) {
377 | g_debug("Load hooks: directory not found at %s; skipping", path);
378 | return 0;
379 | }
380 |
381 | GError *err = NULL;
382 | GDir *dir = g_dir_open(path, 0, &err);
383 |
384 | if (err) {
385 | g_warning("Failed to load hook files: %s", err->message);
386 | g_error_free(err);
387 | return -1;
388 | }
389 |
390 | gint ret = 0;
391 | const gchar *name;
392 |
393 | while ((name = g_dir_read_name(dir))) {
394 | if (!g_str_has_suffix(name, ".hook"))
395 | continue;
396 |
397 | gchar *p = g_strjoin("/", path, name, NULL);
398 | toml_table_t *hook = parse_file(p);
399 |
400 | toml_table_t *tab = toml_table_in(hook, "Hook");
401 |
402 | if (!tab) {
403 | g_warning("Failed to parse hook %s: expected Hook table", p);
404 | ret = -1;
405 | goto end;
406 | }
407 |
408 | const gchar *err;
409 | if (load_hook(tab, out, &err) == -1) {
410 | g_warning("Failed to parse hook %s: %s", p, err);
411 | ret = -1;
412 | }
413 |
414 | end:
415 | g_free(p);
416 | }
417 |
418 | g_dir_close(dir);
419 | return ret;
420 | }
421 |
422 | Config
423 | config_new(void)
424 | {
425 | Config c;
426 |
427 | c.input_mask = INPUT_TYPE_MASK(RawMotion)
428 | | INPUT_TYPE_MASK(RawButtonPress) | INPUT_TYPE_MASK(RawKeyPress);
429 | c.idle_sec = 60 * 20;
430 | c.on_idle = TRUE;
431 | c.on_sleep = TRUE;
432 | c.backlights = NULL;
433 | c.hooks = NULL;
434 | #ifdef DPMS
435 | c.dpms_enable = TRUE;
436 | c.standby_sec = 60 * 10;
437 | c.suspend_sec = 60 * 10;
438 | c.off_sec = 60 * 10;
439 |
440 | c.lock_standby_sec = 60;
441 | c.lock_suspend_sec = 60;
442 | c.lock_off_sec = 60;
443 | #endif /* DPMS */
444 |
445 | return c;
446 | }
447 |
448 | gboolean
449 | config_load(const gchar *path, const gchar *hooksd, Config *c)
450 | {
451 | toml_table_t *conf = parse_file(path);
452 |
453 | if (!conf)
454 | return FALSE;
455 |
456 | gint ret = 0;
457 | toml_table_t *tab;
458 |
459 | #define X(key, type, name) \
460 | ret += load_##type(tab, key, &c->name);
461 |
462 | if ((tab = toml_table_in(conf, "Idle"))) {
463 | IDLE_TABLE_LIST
464 | }
465 |
466 | if ((tab = toml_table_in(conf, "Lock"))) {
467 | LOCK_TABLE_LIST
468 | }
469 |
470 | #ifdef DPMS
471 | if ((tab = toml_table_in(conf, "DPMS"))) {
472 | DPMS_TABLE_LIST
473 | }
474 |
475 | if ((tab = toml_table_in(conf, "Lock"))) {
476 | DPMS_LOCK_TABLE_LIST
477 | }
478 | #endif /* DPMS */
479 |
480 | #ifdef WIREPLUMBER
481 | if ((tab = toml_table_in(conf, "Lock"))) {
482 | WP_LOCK_TABLE_LIST
483 | }
484 | #endif /* WIREPLUMBER */
485 |
486 | #undef X
487 |
488 | c->backlights = g_hash_table_new_full(g_str_hash, g_str_equal,
489 | (GDestroyNotify)g_free, (GDestroyNotify)g_free);
490 |
491 | ret += load_backlights(conf, "Backlight", c->backlights);
492 |
493 | if (!g_hash_table_size(c->backlights)) {
494 | g_hash_table_unref(c->backlights);
495 | c->backlights = NULL;
496 | }
497 |
498 | c->hooks = g_ptr_array_new_with_free_func((GDestroyNotify)free_hook);
499 |
500 | ret += load_hooks(conf, "Hook", c->hooks);
501 |
502 | if (hooksd)
503 | ret += load_hooks_dir(hooksd, c->hooks);
504 |
505 | if (!c->hooks->len) {
506 | g_ptr_array_unref(c->hooks);
507 | c->hooks = NULL;
508 | }
509 |
510 | toml_free(conf);
511 |
512 | return ret == 0;
513 | }
514 |
515 | void
516 | config_free(Config *c)
517 | {
518 | if (!c)
519 | return;
520 |
521 | if (c->backlights)
522 | g_hash_table_unref(c->backlights);
523 | if (c->hooks)
524 | g_ptr_array_free(c->hooks, TRUE);
525 | }
526 |
--------------------------------------------------------------------------------
/src/config.h:
--------------------------------------------------------------------------------
1 | /*
2 | sessiond - standalone X session manager
3 | Copyright (C) 2018-2020 James Reed
4 |
5 | This program is free software: you can redistribute it and/or modify it under
6 | the terms of the GNU General Public License as published by the Free Software
7 | Foundation, either version 3 of the License, or (at your option) any later
8 | version.
9 |
10 | This program is distributed in the hope that it will be useful, but WITHOUT ANY
11 | WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
12 | PARTICULAR PURPOSE. See the GNU General Public License for more details.
13 |
14 | You should have received a copy of the GNU General Public License along with
15 | this program. If not, see .
16 | */
17 |
18 | #pragma once
19 |
20 | #include
21 |
22 | #define IDLE_TABLE_LIST \
23 | X("Inputs", input_mask, input_mask) \
24 | X("IdleSec", uint, idle_sec)
25 |
26 | #define LOCK_TABLE_LIST \
27 | X("OnIdle", bool, on_idle) \
28 | X("OnSleep", bool, on_sleep)
29 |
30 | #define BACKLIGHT_TABLE_LIST \
31 | X("DimSec", uint, dim_sec) \
32 | X("DimValue", int, dim_value) \
33 | X("DimPercent", double, dim_percent)
34 |
35 | #ifdef DPMS
36 | #define DPMS_TABLE_LIST \
37 | X("Enable", bool, dpms_enable) \
38 | X("StandbySec", uint, standby_sec) \
39 | X("SuspendSec", uint, suspend_sec) \
40 | X("OffSec", uint, off_sec)
41 |
42 | #define DPMS_LOCK_TABLE_LIST \
43 | X("StandbySec", uint, lock_standby_sec) \
44 | X("SuspendSec", uint, lock_suspend_sec) \
45 | X("OffSec", uint, lock_off_sec)
46 | #endif /* DPMS */
47 |
48 | #ifdef WIREPLUMBER
49 | #define WP_LOCK_TABLE_LIST \
50 | X("MuteAudio", bool, mute_audio)
51 | #endif /* WIREPLUMBER */
52 |
53 | struct BacklightConf {
54 | guint dim_sec;
55 | gint dim_value;
56 | gdouble dim_percent;
57 | };
58 |
59 | typedef struct {
60 | /* Idle */
61 | guint input_mask;
62 | guint idle_sec;
63 | /* Lock */
64 | gboolean on_idle;
65 | gboolean on_sleep;
66 | /* Backlights */
67 | GHashTable *backlights;
68 | /* Hooks */
69 | GPtrArray *hooks;
70 | #ifdef DPMS
71 | /* DPMS */
72 | gboolean dpms_enable;
73 | guint standby_sec;
74 | guint suspend_sec;
75 | guint off_sec;
76 |
77 | guint lock_standby_sec;
78 | guint lock_suspend_sec;
79 | guint lock_off_sec;
80 | #endif /* DPMS */
81 | #ifdef WIREPLUMBER
82 | /* WIREPLUMBER */
83 | gboolean mute_audio;
84 | #endif /* WIREPLUMBER */
85 | } Config;
86 |
87 | extern Config
88 | config_new(void);
89 | extern gboolean
90 | config_load(const gchar *path, const gchar *hooksd, Config *c);
91 | extern void
92 | config_free(Config *c);
93 |
--------------------------------------------------------------------------------
/src/dbus-audiosink.c:
--------------------------------------------------------------------------------
1 | /*
2 | sessiond - standalone X session manager
3 | Copyright (C) 2021 James Reed
4 |
5 | This program is free software: you can redistribute it and/or modify it under
6 | the terms of the GNU General Public License as published by the Free Software
7 | Foundation, either version 3 of the License, or (at your option) any later
8 | version.
9 |
10 | This program is distributed in the hope that it will be useful, but WITHOUT ANY
11 | WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
12 | PARTICULAR PURPOSE. See the GNU General Public License for more details.
13 |
14 | You should have received a copy of the GNU General Public License along with
15 | this program. If not, see .
16 | */
17 |
18 | #define G_LOG_DOMAIN "sessiond"
19 |
20 | #include "dbus-audiosink.h"
21 | #include "dbus-server.h"
22 | #include "dbus-gen.h"
23 | #include "wireplumber.h"
24 |
25 | #include
26 |
27 | #define INT_PTR(i) GINT_TO_POINTER(i)
28 |
29 | static void
30 | set_default_audio_sink_property(DBusServer *s, DBusAudioSink *das)
31 | {
32 | const gchar *path = g_dbus_interface_skeleton_get_object_path(
33 | G_DBUS_INTERFACE_SKELETON(das));
34 | dbus_session_set_default_audio_sink(s->session, path);
35 | dbus_session_emit_change_default_audio_sink(s->session, path);
36 | }
37 |
38 | static void
39 | set_audio_sinks_property(DBusServer *s)
40 | {
41 | GList *dass = g_hash_table_get_values(s->audiosinks);
42 | const gchar **paths = g_malloc0_n(g_list_length(dass) + 1, sizeof(gchar *));
43 |
44 | guint n = 0;
45 | for (GList *i = dass; i; i = i->next, n++) {
46 | DBusAudioSink *das = i->data;
47 | const gchar *path = g_dbus_interface_skeleton_get_object_path(
48 | G_DBUS_INTERFACE_SKELETON(das));
49 | if (path)
50 | paths[n] = path;
51 | }
52 |
53 | dbus_session_set_audio_sinks(s->session, paths);
54 | g_free(paths);
55 | }
56 |
57 | static gboolean
58 | on_handle_set_volume(DBusAudioSink *das, GDBusMethodInvocation *i,
59 | gdouble v, DBusServer *s)
60 | {
61 | if (!s->wp_conn)
62 | return FALSE;
63 |
64 | guint32 id = dbus_audio_sink_get_id(das);
65 |
66 | if (!audiosink_set_volume(id, v, s->wp_conn)) {
67 | g_dbus_method_invocation_return_dbus_error(i,
68 | DBUS_AUDIOSINK_ERROR ".SetVolume",
69 | "Failed to set volume");
70 | return TRUE;
71 | }
72 |
73 | dbus_audio_sink_complete_set_volume(das, i);
74 |
75 | return TRUE;
76 | }
77 |
78 | static gboolean
79 | on_handle_inc_volume(DBusAudioSink *das, GDBusMethodInvocation *i,
80 | gdouble v, DBusServer *s)
81 | {
82 | if (!s->wp_conn)
83 | return FALSE;
84 |
85 | guint32 id = dbus_audio_sink_get_id(das);
86 | gdouble vol = dbus_audio_sink_get_volume(das);
87 | vol = MIN(MAX(vol + v, 0.0), 1.0);
88 |
89 | if (!audiosink_set_volume(id, vol, s->wp_conn)) {
90 | g_dbus_method_invocation_return_dbus_error(i,
91 | DBUS_AUDIOSINK_ERROR ".IncVolume",
92 | "Failed to increment volume");
93 | return TRUE;
94 | }
95 |
96 | dbus_audio_sink_complete_inc_volume(das, i, vol);
97 |
98 | return TRUE;
99 | }
100 |
101 | static gboolean
102 | on_handle_set_mute(DBusAudioSink *das, GDBusMethodInvocation *i,
103 | gboolean m, DBusServer *s)
104 | {
105 | if (!s->wp_conn)
106 | return FALSE;
107 |
108 | guint32 id = dbus_audio_sink_get_id(das);
109 |
110 | if (!audiosink_set_mute(id, m, s->wp_conn)) {
111 | g_dbus_method_invocation_return_dbus_error(i,
112 | DBUS_AUDIOSINK_ERROR ".SetMute",
113 | "Failed to set mute state");
114 | return TRUE;
115 | }
116 |
117 | dbus_audio_sink_complete_set_mute(das, i);
118 |
119 | return TRUE;
120 | }
121 |
122 | static gboolean
123 | on_handle_toggle_mute(DBusAudioSink *das, GDBusMethodInvocation *i,
124 | DBusServer *s)
125 | {
126 | if (!s->wp_conn)
127 | return FALSE;
128 |
129 | guint32 id = dbus_audio_sink_get_id(das);
130 | gboolean mute = !dbus_audio_sink_get_mute(das);
131 |
132 | if (!audiosink_set_mute(id, mute, s->wp_conn)) {
133 | g_dbus_method_invocation_return_dbus_error(i,
134 | DBUS_AUDIOSINK_ERROR ".ToggleMute",
135 | "Failed to toggle mute state");
136 | return TRUE;
137 | }
138 |
139 | dbus_audio_sink_complete_toggle_mute(das, i, mute);
140 |
141 | return TRUE;
142 | }
143 |
144 | static void
145 | update_audiosink(DBusAudioSink *das, struct AudioSink *as)
146 | {
147 | if (as->name) {
148 | const gchar *str = dbus_audio_sink_get_name(das);
149 | if (!str || g_strcmp0(str, as->name) != 0)
150 | dbus_audio_sink_set_name(das, as->name);
151 | }
152 |
153 | if (dbus_audio_sink_get_id(das) != as->id)
154 | dbus_audio_sink_set_id(das, as->id);
155 |
156 | if (dbus_audio_sink_get_mute(das) != as->mute) {
157 | dbus_audio_sink_set_mute(das, as->mute);
158 | dbus_audio_sink_emit_change_mute(das, as->mute);
159 | }
160 |
161 | if (dbus_audio_sink_get_volume(das) != as->volume) {
162 | dbus_audio_sink_set_volume(das, as->volume);
163 | dbus_audio_sink_emit_change_volume(das, as->volume);
164 | }
165 | }
166 |
167 | gboolean
168 | dbus_server_export_audiosink(DBusServer *s, DBusAudioSink *das)
169 | {
170 | guint32 id = dbus_audio_sink_get_id(das);
171 | gchar *path = g_strdup_printf("%s/%d", DBUS_AUDIOSINK_PATH, id);
172 |
173 | GError *err = NULL;
174 | g_dbus_interface_skeleton_export(G_DBUS_INTERFACE_SKELETON(das), s->conn,
175 | path, &err);
176 |
177 | if (err) {
178 | g_error("Failed to export DBus AudioSink interface: %s", err->message);
179 | g_error_free(err);
180 | g_free(path);
181 | return FALSE;
182 | }
183 |
184 | set_audio_sinks_property(s);
185 |
186 | dbus_session_emit_add_audio_sink(s->session, path);
187 | g_free(path);
188 |
189 | return TRUE;
190 | }
191 |
192 | void
193 | dbus_server_unexport_audiosink(DBusServer *s, DBusAudioSink *das)
194 | {
195 | GDBusInterfaceSkeleton *skel = G_DBUS_INTERFACE_SKELETON(das);
196 | gchar *path = g_strdup(g_dbus_interface_skeleton_get_object_path(skel));
197 |
198 | g_dbus_interface_skeleton_unexport(skel);
199 | set_audio_sinks_property(s);
200 |
201 | dbus_session_emit_remove_audio_sink(s->session, path);
202 | g_free(path);
203 | }
204 |
205 | void
206 | dbus_server_add_audiosink(DBusServer *s, struct AudioSink *as)
207 | {
208 |
209 | DBusAudioSink *das = dbus_audio_sink_skeleton_new();
210 | g_hash_table_insert(s->audiosinks, INT_PTR(as->id), das);
211 |
212 | g_signal_connect(das, "handle-set-volume",
213 | G_CALLBACK(on_handle_set_volume), s);
214 | g_signal_connect(das, "handle-inc-volume",
215 | G_CALLBACK(on_handle_inc_volume), s);
216 | g_signal_connect(das, "handle-set-mute",
217 | G_CALLBACK(on_handle_set_mute), s);
218 | g_signal_connect(das, "handle-toggle-mute",
219 | G_CALLBACK(on_handle_toggle_mute), s);
220 |
221 | update_audiosink(das, as);
222 |
223 | if (s->name_acquired)
224 | dbus_server_export_audiosink(s, das);
225 | }
226 |
227 | void
228 | dbus_server_remove_audiosink(DBusServer *s, guint32 id)
229 | {
230 | DBusAudioSink *das = g_hash_table_lookup(s->audiosinks, INT_PTR(id));
231 |
232 | if (!das)
233 | return;
234 |
235 | g_hash_table_steal(s->audiosinks, INT_PTR(id));
236 | dbus_server_unexport_audiosink(s, das);
237 | g_object_unref(das);
238 | }
239 |
240 | void
241 | dbus_server_update_audiosink(DBusServer *s, struct AudioSink *as)
242 | {
243 | DBusAudioSink *das = g_hash_table_lookup(s->audiosinks, INT_PTR(as->id));
244 |
245 | if (das)
246 | update_audiosink(das, as);
247 | }
248 |
249 | void
250 | dbus_server_update_default_audiosink(DBusServer *s, guint32 id)
251 | {
252 | DBusAudioSink *das = g_hash_table_lookup(s->audiosinks, INT_PTR(id));
253 |
254 | if (das)
255 | set_default_audio_sink_property(s, das);
256 | }
257 |
--------------------------------------------------------------------------------
/src/dbus-audiosink.h:
--------------------------------------------------------------------------------
1 | /*
2 | sessiond - standalone X session manager
3 | Copyright (C) 2021 James Reed
4 |
5 | This program is free software: you can redistribute it and/or modify it under
6 | the terms of the GNU General Public License as published by the Free Software
7 | Foundation, either version 3 of the License, or (at your option) any later
8 | version.
9 |
10 | This program is distributed in the hope that it will be useful, but WITHOUT ANY
11 | WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
12 | PARTICULAR PURPOSE. See the GNU General Public License for more details.
13 |
14 | You should have received a copy of the GNU General Public License along with
15 | this program. If not, see .
16 | */
17 |
18 | #pragma once
19 |
20 | #include "dbus-server.h"
21 | #include "wireplumber.h"
22 |
23 | #include
24 |
25 | #define DBUS_AUDIOSINK_ERROR DBUS_NAME ".AudioSink.Error"
26 | #define DBUS_AUDIOSINK_PATH DBUS_PATH "/audiosink"
27 |
28 | extern gboolean
29 | dbus_server_export_audiosink(DBusServer *s, DBusAudioSink *das);
30 | extern void
31 | dbus_server_unexport_audiosink(DBusServer *s, DBusAudioSink *das);
32 | extern void
33 | dbus_server_add_audiosink(DBusServer *s, struct AudioSink *as);
34 | extern void
35 | dbus_server_remove_audiosink(DBusServer *s, guint32 id);
36 | extern void
37 | dbus_server_update_audiosink(DBusServer *s, struct AudioSink *as);
38 | extern void
39 | dbus_server_update_default_audiosink(DBusServer *s, guint32 id);
40 |
--------------------------------------------------------------------------------
/src/dbus-backlight.c:
--------------------------------------------------------------------------------
1 | /*
2 | sessiond - standalone X session manager
3 | Copyright (C) 2019-2020 James Reed
4 |
5 | This program is free software: you can redistribute it and/or modify it under
6 | the terms of the GNU General Public License as published by the Free Software
7 | Foundation, either version 3 of the License, or (at your option) any later
8 | version.
9 |
10 | This program is distributed in the hope that it will be useful, but WITHOUT ANY
11 | WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
12 | PARTICULAR PURPOSE. See the GNU General Public License for more details.
13 |
14 | You should have received a copy of the GNU General Public License along with
15 | this program. If not, see .
16 | */
17 |
18 | #define G_LOG_DOMAIN "sessiond"
19 |
20 | #include "dbus-backlight.h"
21 | #include "dbus-server.h"
22 | #include "dbus-gen.h"
23 | #include "backlight.h"
24 |
25 | #include
26 |
27 | static void
28 | set_backlights_property(DBusServer *s)
29 | {
30 | GList *dbls = g_hash_table_get_values(s->backlights);
31 | const gchar **paths = g_malloc0_n(g_list_length(dbls) + 1, sizeof(gchar *));
32 |
33 | guint n = 0;
34 | for (GList *i = dbls; i; i = i->next, n++) {
35 | DBusBacklight *dbl = i->data;
36 | const gchar *path = g_dbus_interface_skeleton_get_object_path(
37 | G_DBUS_INTERFACE_SKELETON(dbl));
38 | if (path)
39 | paths[n] = path;
40 | }
41 |
42 | dbus_session_set_backlights(s->session, paths);
43 | g_free(paths);
44 | }
45 |
46 | static gboolean
47 | on_handle_set_brightness(DBusBacklight *dbl, GDBusMethodInvocation *i,
48 | guint32 v, gpointer user_data)
49 | {
50 | DBusServer *s = (DBusServer *)user_data;
51 |
52 | if (!s->bl_devices)
53 | return FALSE;
54 |
55 | const gchar *sys_path = dbus_backlight_get_sys_path(dbl);
56 | if (!sys_path)
57 | return FALSE;
58 | struct Backlight *bl = g_hash_table_lookup(s->bl_devices, sys_path);
59 | if (!bl)
60 | return FALSE;
61 |
62 | if (!backlight_set_brightness(bl, v, s->ctx)) {
63 | g_dbus_method_invocation_return_dbus_error(i,
64 | DBUS_BACKLIGHT_ERROR ".SetBrightness",
65 | "Failed to set brightness");
66 | return TRUE;
67 | }
68 |
69 | dbus_backlight_complete_set_brightness(dbl, i);
70 | return TRUE;
71 | }
72 |
73 | static gboolean
74 | on_handle_inc_brightness(DBusBacklight *dbl, GDBusMethodInvocation *i,
75 | gint v, gpointer user_data)
76 | {
77 | DBusServer *s = (DBusServer *)user_data;
78 |
79 | if (!s->bl_devices)
80 | return FALSE;
81 |
82 | const gchar *sys_path = dbus_backlight_get_sys_path(dbl);
83 | if (!sys_path)
84 | return FALSE;
85 | struct Backlight *bl = g_hash_table_lookup(s->bl_devices, sys_path);
86 | if (!bl)
87 | return FALSE;
88 |
89 | guint b = MAX(bl->brightness + v, 0);
90 | if (!backlight_set_brightness(bl, b, s->ctx)) {
91 | g_dbus_method_invocation_return_dbus_error(i,
92 | DBUS_BACKLIGHT_ERROR ".IncBrightness",
93 | "Failed to increment brightness");
94 | return TRUE;
95 | }
96 |
97 | dbus_backlight_complete_inc_brightness(dbl, i, b);
98 | return TRUE;
99 | }
100 |
101 | static void
102 | update_backlight(DBusBacklight *dbl, struct Backlight *bl)
103 | {
104 | const gchar *str = NULL;
105 | #define SET_STR(n) \
106 | str = dbus_backlight_get_##n(dbl); \
107 | if (!str || g_strcmp0(str, bl->n) != 0) \
108 | dbus_backlight_set_##n(dbl, bl->n)
109 | SET_STR(name);
110 | SET_STR(subsystem);
111 | SET_STR(sys_path);
112 | SET_STR(dev_path);
113 | #undef SET_STR
114 |
115 | #define SET_INT(n) \
116 | if (dbus_backlight_get_##n(dbl) != bl->n) \
117 | dbus_backlight_set_##n(dbl, bl->n)
118 | SET_INT(online);
119 | SET_INT(brightness);
120 | SET_INT(max_brightness);
121 | #undef SET_INT
122 | }
123 |
124 | gboolean
125 | dbus_server_export_backlight(DBusServer *s, DBusBacklight *dbl)
126 | {
127 | const gchar *name = dbus_backlight_get_name(dbl);
128 | gchar *norm = backlight_normalize_name(name);
129 | gchar *path = g_strdup_printf("%s/%s", DBUS_BACKLIGHT_PATH,
130 | norm ? norm : name);
131 |
132 | if (norm)
133 | g_free(norm);
134 |
135 | GError *err = NULL;
136 | g_dbus_interface_skeleton_export(G_DBUS_INTERFACE_SKELETON(dbl), s->conn,
137 | path, &err);
138 |
139 | if (err) {
140 | g_error("Failed to export DBus Backlight interface: %s", err->message);
141 | g_error_free(err);
142 | g_free(path);
143 | return FALSE;
144 | }
145 |
146 | set_backlights_property(s);
147 |
148 | dbus_session_emit_add_backlight(s->session, path);
149 | g_free(path);
150 |
151 | return TRUE;
152 | }
153 |
154 | void
155 | dbus_server_unexport_backlight(DBusServer *s, DBusBacklight *dbl)
156 | {
157 | GDBusInterfaceSkeleton *skel = G_DBUS_INTERFACE_SKELETON(dbl);
158 | gchar *path = g_strdup(g_dbus_interface_skeleton_get_object_path(skel));
159 |
160 | g_dbus_interface_skeleton_unexport(skel);
161 | set_backlights_property(s);
162 |
163 | dbus_session_emit_remove_backlight(s->session, path);
164 | g_free(path);
165 | }
166 |
167 | void
168 | dbus_server_add_backlight(DBusServer *s, struct Backlight *bl)
169 | {
170 | DBusBacklight *dbl = dbus_backlight_skeleton_new();
171 | g_hash_table_insert(s->backlights, (char *)bl->sys_path, dbl);
172 |
173 | g_signal_connect(dbl, "handle-set-brightness",
174 | G_CALLBACK(on_handle_set_brightness), s);
175 | g_signal_connect(dbl, "handle-inc-brightness",
176 | G_CALLBACK(on_handle_inc_brightness), s);
177 |
178 | update_backlight(dbl, bl);
179 |
180 | if (s->name_acquired)
181 | dbus_server_export_backlight(s, dbl);
182 | }
183 |
184 | void
185 | dbus_server_remove_backlight(DBusServer *s, const char *path)
186 | {
187 | DBusBacklight *dbl = g_hash_table_lookup(s->backlights, path);
188 |
189 | if (!dbl)
190 | return;
191 |
192 | g_hash_table_steal(s->backlights, path);
193 | dbus_server_unexport_backlight(s, dbl);
194 | g_object_unref(dbl);
195 | }
196 |
197 | void
198 | dbus_server_update_backlight(DBusServer *s, struct Backlight *bl)
199 | {
200 | DBusBacklight *dbl = g_hash_table_lookup(s->backlights, bl->sys_path);
201 |
202 | if (dbl)
203 | update_backlight(dbl, bl);
204 | }
205 |
--------------------------------------------------------------------------------
/src/dbus-backlight.h:
--------------------------------------------------------------------------------
1 | /*
2 | sessiond - standalone X session manager
3 | Copyright (C) 2019-2020 James Reed
4 |
5 | This program is free software: you can redistribute it and/or modify it under
6 | the terms of the GNU General Public License as published by the Free Software
7 | Foundation, either version 3 of the License, or (at your option) any later
8 | version.
9 |
10 | This program is distributed in the hope that it will be useful, but WITHOUT ANY
11 | WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
12 | PARTICULAR PURPOSE. See the GNU General Public License for more details.
13 |
14 | You should have received a copy of the GNU General Public License along with
15 | this program. If not, see .
16 | */
17 |
18 | #pragma once
19 |
20 | #include "dbus-server.h"
21 |
22 | #include
23 |
24 | #define DBUS_BACKLIGHT_ERROR DBUS_NAME ".Backlight.Error"
25 | #define DBUS_BACKLIGHT_PATH DBUS_PATH "/backlight"
26 |
27 | extern gboolean
28 | dbus_server_export_backlight(DBusServer *s, DBusBacklight *dbl);
29 | extern void
30 | dbus_server_unexport_backlight(DBusServer *s, DBusBacklight *dbl);
31 | extern void
32 | dbus_server_add_backlight(DBusServer *s, struct Backlight *bl);
33 | extern void
34 | dbus_server_remove_backlight(DBusServer *s, const char *path);
35 | extern void
36 | dbus_server_update_backlight(DBusServer *s, struct Backlight *bl);
37 |
--------------------------------------------------------------------------------
/src/dbus-gen.c:
--------------------------------------------------------------------------------
1 | ../builddir/dbus-gen.c
--------------------------------------------------------------------------------
/src/dbus-gen.h:
--------------------------------------------------------------------------------
1 | ../builddir/dbus-gen.h
--------------------------------------------------------------------------------
/src/dbus-logind.c:
--------------------------------------------------------------------------------
1 | /*
2 | sessiond - standalone X session manager
3 | Copyright (C) 2018-2020 James Reed
4 |
5 | This program is free software: you can redistribute it and/or modify it under
6 | the terms of the GNU General Public License as published by the Free Software
7 | Foundation, either version 3 of the License, or (at your option) any later
8 | version.
9 |
10 | This program is distributed in the hope that it will be useful, but WITHOUT ANY
11 | WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
12 | PARTICULAR PURPOSE. See the GNU General Public License for more details.
13 |
14 | You should have received a copy of the GNU General Public License along with
15 | this program. If not, see .
16 | */
17 |
18 | #define G_LOG_DOMAIN "sessiond"
19 |
20 | #include "dbus-logind.h"
21 | #include "common.h"
22 | #include "config.h"
23 |
24 | #include
25 | #include
26 |
27 | #define LOGIND_NAME "org.freedesktop.login1"
28 | #define LOGIND_MANAGER_IFACE LOGIND_NAME ".Manager"
29 | #define LOGIND_SESSION_IFACE LOGIND_NAME ".Session"
30 | #define LOGIND_USER_IFACE LOGIND_NAME ".User"
31 | #define LOGIND_PATH "/org/freedesktop/login1"
32 | #define LOGIND_USER_PATH LOGIND_PATH "/user/self"
33 |
34 | G_DEFINE_TYPE(LogindContext, logind_context, G_TYPE_OBJECT);
35 |
36 | enum {
37 | LOCK_SIGNAL,
38 | SLEEP_SIGNAL,
39 | SHUTDOWN_SIGNAL,
40 | APPEAR_SIGNAL,
41 | VANISH_SIGNAL,
42 | LAST_SIGNAL,
43 | };
44 |
45 | static guint signals[LAST_SIGNAL] = {0};
46 |
47 | static void
48 | logind_on_session_signal(UNUSED GDBusProxy *proxy, UNUSED gchar *sender,
49 | gchar *signal, UNUSED GVariant *params,
50 | gpointer user_data)
51 | {
52 | LogindContext *c = (LogindContext *)user_data;
53 |
54 | if (g_strcmp0(signal, "Lock") == 0) {
55 | g_debug("Lock signal received");
56 | g_signal_emit(c, signals[LOCK_SIGNAL], 0, TRUE);
57 | } else if (g_strcmp0(signal, "Unlock") == 0) {
58 | g_debug("Unlock signal received");
59 | g_signal_emit(c, signals[LOCK_SIGNAL], 0, FALSE);
60 | }
61 | }
62 |
63 | static void
64 | logind_on_manager_signal(UNUSED GDBusProxy *proxy, UNUSED gchar *sender,
65 | gchar *signal, GVariant *params, gpointer user_data)
66 | {
67 | LogindContext *c = (LogindContext *)user_data;
68 |
69 | gboolean state;
70 |
71 | if (g_strcmp0(signal, "PrepareForSleep") == 0) {
72 | g_variant_get(params, "(b)", &state);
73 | g_debug("PrepareForSleep signal received: %s", BOOLSTR(state));
74 | g_signal_emit(c, signals[SLEEP_SIGNAL], 0, state);
75 | } else if (g_strcmp0(signal, "PrepareForShutdown") == 0) {
76 | g_variant_get(params, "(b)", &state);
77 | g_debug("PrepareForShutdown signal received: %s", BOOLSTR(state));
78 | g_signal_emit(c, signals[SHUTDOWN_SIGNAL], 0, state);
79 | }
80 | }
81 |
82 | static gchar *
83 | logind_get_session(GDBusConnection *conn, gchar **id)
84 | {
85 | GError *err = NULL;
86 | GDBusProxy *proxy = g_dbus_proxy_new_sync(
87 | conn, G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS, NULL, LOGIND_NAME,
88 | LOGIND_USER_PATH, LOGIND_USER_IFACE, NULL, &err);
89 |
90 | if (err) {
91 | g_warning("%s", err->message);
92 | g_error_free(err);
93 | return NULL;
94 | }
95 |
96 | gchar *path = NULL;
97 | GVariant *display = g_dbus_proxy_get_cached_property(proxy, "Display");
98 |
99 | if (!display) {
100 | g_warning("%s does not have Display property", LOGIND_USER_IFACE);
101 | goto error;
102 | }
103 |
104 | g_variant_get(display, "(so)", id, &path);
105 |
106 | if (!*id) {
107 | g_warning("Failed to read Display session Id");
108 | goto error;
109 | }
110 |
111 | if (!path)
112 | g_warning("Failed to read Display session object path");
113 |
114 | error:
115 | if (display)
116 | g_variant_unref(display);
117 | g_object_unref(proxy);
118 |
119 | return path;
120 | }
121 |
122 | static void
123 | logind_get_property(LogindContext *c, const gchar *prop, const gchar *fmt,
124 | gpointer out)
125 | {
126 | if (!c->logind_session) {
127 | g_warning("Cannot get %s: %s does not exist", prop, LOGIND_NAME);
128 | return;
129 | }
130 |
131 | GVariant *v = g_dbus_proxy_get_cached_property(c->logind_session,
132 | prop);
133 | if (v) {
134 | g_variant_get(v, fmt, out);
135 | g_variant_unref(v);
136 | } else {
137 | g_warning("Failed to get logind %s", prop);
138 | }
139 | }
140 |
141 | gboolean
142 | logind_get_locked_hint(LogindContext *c)
143 | {
144 | gboolean b = FALSE;
145 | logind_get_property(c, "LockedHint", "b", &b);
146 | return b;
147 | }
148 |
149 | gboolean
150 | logind_get_idle_hint(LogindContext *c)
151 | {
152 | gboolean b = FALSE;
153 | logind_get_property(c, "IdleHint", "b", &b);
154 | return b;
155 | }
156 |
157 | guint64
158 | logind_get_idle_since_hint(LogindContext *c)
159 | {
160 | guint64 i = 0;
161 | logind_get_property(c, "IdleSinceHint", "t", &i);
162 | return i;
163 | }
164 |
165 | guint64
166 | logind_get_idle_since_hint_monotonic(LogindContext *c)
167 | {
168 | guint64 i = 0;
169 | logind_get_property(c, "IdleSinceHintMonotonic", "t", &i);
170 | return i;
171 | }
172 |
173 | static void
174 | logind_on_appear(GDBusConnection *conn, const gchar *name, const gchar *owner,
175 | gpointer user_data)
176 | {
177 | g_debug("%s appeared (owned by %s)", name, owner);
178 |
179 | LogindContext *c = (LogindContext *)user_data;
180 | gchar *path = logind_get_session(conn, &c->session_id);
181 | GError *err = NULL;
182 |
183 | if (path) {
184 | c->logind_session = g_dbus_proxy_new_sync(
185 | conn, G_DBUS_PROXY_FLAGS_NONE, NULL, LOGIND_NAME,
186 | path, LOGIND_SESSION_IFACE, NULL, &err);
187 |
188 | if (err) {
189 | g_warning("%s", err->message);
190 | g_error_free(err);
191 | err = NULL;
192 | } else {
193 | g_signal_connect(c->logind_session, "g-signal",
194 | G_CALLBACK(logind_on_session_signal), user_data);
195 | g_debug("Using logind session %s: %s", c->session_id, path);
196 | }
197 | g_free(path);
198 | }
199 |
200 | c->logind_manager = g_dbus_proxy_new_sync(
201 | conn, G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES, NULL, LOGIND_NAME,
202 | LOGIND_PATH, LOGIND_MANAGER_IFACE, NULL, &err);
203 |
204 | if (err) {
205 | g_warning("%s", err->message);
206 | g_error_free(err);
207 | } else {
208 | g_signal_connect(c->logind_manager, "g-signal",
209 | G_CALLBACK(logind_on_manager_signal), user_data);
210 | }
211 |
212 | g_signal_emit(c, signals[APPEAR_SIGNAL], 0);
213 | }
214 |
215 | static void
216 | logind_on_vanish(UNUSED GDBusConnection *conn, const gchar *name,
217 | gpointer user_data)
218 | {
219 | g_debug("%s vanished", name);
220 |
221 | LogindContext *c = (LogindContext *)user_data;
222 | g_signal_emit(c, signals[VANISH_SIGNAL], 0);
223 | logind_context_free(c);
224 | }
225 |
226 | static void
227 | logind_context_class_init(LogindContextClass *c)
228 | {
229 | GType type = G_OBJECT_CLASS_TYPE(c);
230 |
231 | signals[LOCK_SIGNAL] = g_signal_new("lock",
232 | type, G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, G_TYPE_NONE, 1,
233 | G_TYPE_BOOLEAN);
234 | signals[SLEEP_SIGNAL] = g_signal_new("sleep",
235 | type, G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, G_TYPE_NONE, 1,
236 | G_TYPE_BOOLEAN);
237 | signals[SHUTDOWN_SIGNAL] = g_signal_new("shutdown",
238 | type, G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, G_TYPE_NONE, 1,
239 | G_TYPE_BOOLEAN);
240 | signals[APPEAR_SIGNAL] = g_signal_new("appear",
241 | type, G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, G_TYPE_NONE, 0);
242 | signals[VANISH_SIGNAL] = g_signal_new("vanish",
243 | type, G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, G_TYPE_NONE, 0);
244 | }
245 |
246 | static void
247 | logind_context_init(LogindContext *self)
248 | {
249 | self->logind_watcher = g_bus_watch_name(
250 | G_BUS_TYPE_SYSTEM, LOGIND_NAME, G_BUS_NAME_WATCHER_FLAGS_NONE,
251 | logind_on_appear, logind_on_vanish, self, NULL);
252 | }
253 |
254 | void
255 | logind_set_idle_hint(LogindContext *c, gboolean state)
256 | {
257 | if (!c->logind_session) {
258 | g_warning("Cannot set IdleHint: %s does not exist", LOGIND_NAME);
259 | return;
260 | }
261 |
262 | GError *err = NULL;
263 | g_dbus_proxy_call_sync(c->logind_session, "SetIdleHint",
264 | g_variant_new("(b)", state), G_DBUS_CALL_FLAGS_NONE,
265 | -1, NULL, &err);
266 |
267 | if (err) {
268 | g_warning("%s", err->message);
269 | g_error_free(err);
270 | } else {
271 | g_debug("IdleHint set to %s", BOOLSTR(state));
272 | }
273 | }
274 |
275 | /* Locking session automatically updates logind LockedHint. */
276 | void
277 | logind_lock_session(LogindContext *c, gboolean state)
278 | {
279 | #define STR(b) ((b) ? "Lock" : "Unlock")
280 |
281 | if (!c->logind_session) {
282 | g_warning("Cannot %s session: %s does not exist", STR(state),
283 | LOGIND_NAME);
284 | return;
285 | }
286 |
287 | GError *err = NULL;
288 | g_dbus_proxy_call_sync(c->logind_session, STR(state), NULL,
289 | G_DBUS_CALL_FLAGS_NONE, -1, NULL, &err);
290 |
291 | if (err) {
292 | g_warning("%s", err->message);
293 | g_error_free(err);
294 | } else {
295 | g_debug("%sed session", STR(state));
296 | }
297 |
298 | #undef STR
299 | }
300 |
301 | /* Set LockedHint manually when responding to Lock signal. */
302 | void
303 | logind_set_locked_hint(LogindContext *c, gboolean state)
304 | {
305 | if (!c->logind_session) {
306 | g_warning("Cannot set LockedHint: %s does not exist", LOGIND_NAME);
307 | return;
308 | }
309 |
310 | GError *err = NULL;
311 | g_dbus_proxy_call_sync(c->logind_session, "SetLockedHint",
312 | g_variant_new("(b)", state), G_DBUS_CALL_FLAGS_NONE,
313 | -1, NULL, &err);
314 |
315 | if (err) {
316 | g_warning("%s", err->message);
317 | g_error_free(err);
318 | } else {
319 | g_debug("LockedHint set to %s", BOOLSTR(state));
320 | }
321 | }
322 |
323 | gboolean
324 | logind_set_brightness(LogindContext *c, const char *sys, const char *name,
325 | guint32 v)
326 | {
327 | if (!c->logind_session) {
328 | g_warning("Cannot set brightness: %s does not exist", LOGIND_NAME);
329 | return FALSE;
330 | }
331 |
332 | GError *err = NULL;
333 | g_dbus_proxy_call_sync(c->logind_session, "SetBrightness",
334 | g_variant_new("(ssu)", sys, name, v),
335 | G_DBUS_CALL_FLAGS_NONE, -1, NULL, &err);
336 | if (err) {
337 | g_error_free(err);
338 | return FALSE;
339 | }
340 |
341 | return TRUE;
342 | }
343 |
344 | LogindContext *
345 | logind_context_new(void)
346 | {
347 | return g_object_new(LOGIND_TYPE_CONTEXT, NULL);
348 | }
349 |
350 | void
351 | logind_context_free(LogindContext *c)
352 | {
353 | if (!c)
354 | return;
355 | if (c->session_id)
356 | g_free(c->session_id);
357 | if (c->logind_watcher) {
358 | g_bus_unwatch_name(c->logind_watcher);
359 | c->logind_watcher = 0;
360 | }
361 | if (c->logind_session) {
362 | g_object_unref(c->logind_session);
363 | c->logind_session = NULL;
364 | }
365 | if (c->logind_manager) {
366 | g_object_unref(c->logind_manager);
367 | c->logind_manager = NULL;
368 | }
369 | g_object_unref(c);
370 | }
371 |
--------------------------------------------------------------------------------
/src/dbus-logind.h:
--------------------------------------------------------------------------------
1 | /*
2 | sessiond - standalone X session manager
3 | Copyright (C) 2018-2020 James Reed
4 |
5 | This program is free software: you can redistribute it and/or modify it under
6 | the terms of the GNU General Public License as published by the Free Software
7 | Foundation, either version 3 of the License, or (at your option) any later
8 | version.
9 |
10 | This program is distributed in the hope that it will be useful, but WITHOUT ANY
11 | WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
12 | PARTICULAR PURPOSE. See the GNU General Public License for more details.
13 |
14 | You should have received a copy of the GNU General Public License along with
15 | this program. If not, see .
16 | */
17 |
18 | #pragma once
19 |
20 | #include
21 | #include
22 |
23 | #define LOGIND_TYPE_CONTEXT logind_context_get_type()
24 | G_DECLARE_FINAL_TYPE(LogindContext, logind_context, LOGIND, CONTEXT, GObject);
25 |
26 | struct _LogindContext {
27 | GObject parent;
28 | gchar *session_id;
29 | guint logind_watcher;
30 | GDBusProxy *logind_session;
31 | GDBusProxy *logind_manager;
32 | };
33 |
34 | extern void
35 | logind_set_idle_hint(LogindContext *c, gboolean state);
36 | extern void
37 | logind_lock_session(LogindContext *c, gboolean state);
38 | extern void
39 | logind_set_locked_hint(LogindContext *c, gboolean state);
40 | extern gboolean
41 | logind_get_locked_hint(LogindContext *c);
42 | extern gboolean
43 | logind_get_idle_hint(LogindContext *c);
44 | extern guint64
45 | logind_get_idle_since_hint(LogindContext *c);
46 | extern guint64
47 | logind_get_idle_since_hint_monotonic(LogindContext *c);
48 | extern gboolean
49 | logind_set_brightness(LogindContext *c, const char *sys, const char *name,
50 | guint32 v);
51 | extern LogindContext *
52 | logind_context_new(void);
53 | extern void
54 | logind_context_free(LogindContext *c);
55 |
--------------------------------------------------------------------------------
/src/dbus-server.h:
--------------------------------------------------------------------------------
1 | /*
2 | sessiond - standalone X session manager
3 | Copyright (C) 2019-2020 James Reed
4 |
5 | This program is free software: you can redistribute it and/or modify it under
6 | the terms of the GNU General Public License as published by the Free Software
7 | Foundation, either version 3 of the License, or (at your option) any later
8 | version.
9 |
10 | This program is distributed in the hope that it will be useful, but WITHOUT ANY
11 | WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
12 | PARTICULAR PURPOSE. See the GNU General Public License for more details.
13 |
14 | You should have received a copy of the GNU General Public License along with
15 | this program. If not, see .
16 | */
17 |
18 | #pragma once
19 |
20 | #include "dbus-logind.h"
21 | #include "dbus-gen.h"
22 | #include "backlight.h"
23 |
24 | #ifdef WIREPLUMBER
25 | #include "wireplumber.h"
26 | #endif /* WIREPLUMBER */
27 |
28 | #include
29 |
30 | #define DBUS_NAME "org.sessiond.session1"
31 | #define DBUS_SESSION_ERROR DBUS_NAME ".Session.Error"
32 | #define DBUS_PATH "/org/sessiond/session1"
33 |
34 | #define DBUS_TYPE_SERVER dbus_server_get_type()
35 | G_DECLARE_FINAL_TYPE(DBusServer, dbus_server, DBUS, SERVER, GObject);
36 |
37 | struct _DBusServer {
38 | GObject parent;
39 | guint bus_id;
40 | GDBusConnection *conn;
41 | gboolean name_acquired;
42 | DBusSession *session;
43 | LogindContext *ctx;
44 | GHashTable *inhibitors;
45 | GHashTable *backlights;
46 | GHashTable *bl_devices;
47 |
48 | #ifdef WIREPLUMBER
49 | WpConn *wp_conn;
50 | GHashTable *audiosinks;
51 | #endif /* WIREPLUMBER */
52 | };
53 |
54 | extern void
55 | dbus_server_free(DBusServer *s);
56 | extern void
57 | dbus_server_emit_active(DBusServer *s);
58 | extern void
59 | dbus_server_emit_inactive(DBusServer *s, guint i);
60 | extern DBusServer *
61 | dbus_server_new(LogindContext *c);
62 |
--------------------------------------------------------------------------------
/src/dbus-systemd.c:
--------------------------------------------------------------------------------
1 | /*
2 | sessiond - standalone X session manager
3 | Copyright (C) 2018-2020 James Reed
4 |
5 | This program is free software: you can redistribute it and/or modify it under
6 | the terms of the GNU General Public License as published by the Free Software
7 | Foundation, either version 3 of the License, or (at your option) any later
8 | version.
9 |
10 | This program is distributed in the hope that it will be useful, but WITHOUT ANY
11 | WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
12 | PARTICULAR PURPOSE. See the GNU General Public License for more details.
13 |
14 | You should have received a copy of the GNU General Public License along with
15 | this program. If not, see .
16 | */
17 |
18 | #define G_LOG_DOMAIN "sessiond"
19 |
20 | #include "dbus-systemd.h"
21 | #include "common.h"
22 | #include "config.h"
23 |
24 | #include
25 | #include
26 |
27 | #define SYSTEMD_NAME "org.freedesktop.systemd1"
28 | #define SYSTEMD_MANAGER_IFACE SYSTEMD_NAME ".Manager"
29 | #define SYSTEMD_PATH "/org/freedesktop/systemd1"
30 |
31 | static void
32 | systemd_on_appear(GDBusConnection *conn, const gchar *name, const gchar *owner,
33 | gpointer user_data)
34 | {
35 | g_debug("%s appeared (owned by %s)", name, owner);
36 |
37 | SystemdContext *c = (SystemdContext *)user_data;
38 | GError *err = NULL;
39 |
40 | c->systemd_manager = g_dbus_proxy_new_sync(
41 | conn, G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES |
42 | G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS, NULL, SYSTEMD_NAME,
43 | SYSTEMD_PATH, SYSTEMD_MANAGER_IFACE, NULL, &err);
44 |
45 | if (err) {
46 | g_warning("%s", err->message);
47 | g_error_free(err);
48 | }
49 | }
50 |
51 | static void
52 | systemd_on_vanish(UNUSED GDBusConnection *conn, const gchar *name,
53 | gpointer user_data)
54 | {
55 | g_debug("%s vanished", name);
56 | systemd_context_free((SystemdContext *)user_data);
57 | }
58 |
59 | void
60 | systemd_start_unit(SystemdContext *c, const gchar *name)
61 | {
62 | if (!c->systemd_manager) {
63 | g_warning("Cannot start unit %s: %s does not exist", name,
64 | SYSTEMD_NAME);
65 | return;
66 | }
67 |
68 | GError *err = NULL;
69 | g_dbus_proxy_call_sync(c->systemd_manager, "StartUnit",
70 | g_variant_new("(ss)", name, "replace"),
71 | G_DBUS_CALL_FLAGS_NONE, -1, NULL, &err);
72 |
73 | if (err) {
74 | g_warning("%s", err->message);
75 | g_error_free(err);
76 | } else {
77 | g_debug("Started unit %s", name);
78 | }
79 | }
80 |
81 | SystemdContext *
82 | systemd_context_new(void)
83 | {
84 | SystemdContext *c = g_malloc0(sizeof(SystemdContext));
85 |
86 | c->systemd_watcher = g_bus_watch_name(
87 | G_BUS_TYPE_SESSION, SYSTEMD_NAME, G_BUS_NAME_WATCHER_FLAGS_NONE,
88 | systemd_on_appear, systemd_on_vanish, c, NULL);
89 |
90 | return c;
91 | }
92 |
93 | void
94 | systemd_context_free(SystemdContext *c)
95 | {
96 | if (!c)
97 | return;
98 | if (c->systemd_watcher) {
99 | g_bus_unwatch_name(c->systemd_watcher);
100 | c->systemd_watcher = 0;
101 | }
102 | if (c->systemd_manager) {
103 | g_object_unref(c->systemd_manager);
104 | c->systemd_manager = NULL;
105 | }
106 | g_free(c);
107 | }
108 |
--------------------------------------------------------------------------------
/src/dbus-systemd.h:
--------------------------------------------------------------------------------
1 | /*
2 | sessiond - standalone X session manager
3 | Copyright (C) 2018-2020 James Reed
4 |
5 | This program is free software: you can redistribute it and/or modify it under
6 | the terms of the GNU General Public License as published by the Free Software
7 | Foundation, either version 3 of the License, or (at your option) any later
8 | version.
9 |
10 | This program is distributed in the hope that it will be useful, but WITHOUT ANY
11 | WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
12 | PARTICULAR PURPOSE. See the GNU General Public License for more details.
13 |
14 | You should have received a copy of the GNU General Public License along with
15 | this program. If not, see .
16 | */
17 |
18 | #pragma once
19 |
20 | #include
21 | #include
22 |
23 | typedef struct {
24 | guint systemd_watcher;
25 | GDBusProxy *systemd_manager;
26 | } SystemdContext;
27 |
28 | extern void
29 | systemd_start_unit(SystemdContext *c, const gchar *name);
30 | extern SystemdContext *
31 | systemd_context_new(void);
32 | extern void
33 | systemd_context_free(SystemdContext *c);
34 |
--------------------------------------------------------------------------------
/src/helper/sessiond-sysfs-writer.c:
--------------------------------------------------------------------------------
1 | /*
2 | sessiond - standalone X session manager
3 | Copyright (C) 2018-2020 James Reed
4 |
5 | This program is free software: you can redistribute it and/or modify it under
6 | the terms of the GNU General Public License as published by the Free Software
7 | Foundation, either version 3 of the License, or (at your option) any later
8 | version.
9 |
10 | This program is distributed in the hope that it will be useful, but WITHOUT ANY
11 | WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
12 | PARTICULAR PURPOSE. See the GNU General Public License for more details.
13 |
14 | You should have received a copy of the GNU General Public License along with
15 | this program. If not, see .
16 | */
17 |
18 | #include
19 | #include
20 | #include
21 | #include
22 |
23 | int
24 | main(int argc, char *argv[])
25 | {
26 | char path[MAXPATHLEN];
27 | FILE *f;
28 |
29 | if (argc != 3) {
30 | fprintf(stderr, "Expected 2 arguments: PATH VALUE\n");
31 | return EXIT_FAILURE;
32 | }
33 |
34 | if (strncmp("/sys", argv[1], 4) == 0) {
35 | f = fopen(argv[1], "w");
36 | } else {
37 | snprintf(path, sizeof(path), "/sys%s", argv[1]);
38 | f = fopen(path, "w");
39 | }
40 |
41 | if (!f) {
42 | perror("Failed to open path");
43 | return EXIT_FAILURE;
44 | }
45 |
46 | if (fprintf(f, "%s", argv[2]) < 0) {
47 | fclose(f);
48 | fprintf(stderr, "Failed to write to path\n");
49 | return EXIT_FAILURE;
50 | }
51 |
52 | fclose(f);
53 | }
54 |
--------------------------------------------------------------------------------
/src/hooks.c:
--------------------------------------------------------------------------------
1 | /*
2 | sessiond - standalone X session manager
3 | Copyright (C) 2018-2020 James Reed
4 |
5 | This program is free software: you can redistribute it and/or modify it under
6 | the terms of the GNU General Public License as published by the Free Software
7 | Foundation, either version 3 of the License, or (at your option) any later
8 | version.
9 |
10 | This program is distributed in the hope that it will be useful, but WITHOUT ANY
11 | WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
12 | PARTICULAR PURPOSE. See the GNU General Public License for more details.
13 |
14 | You should have received a copy of the GNU General Public License along with
15 | this program. If not, see .
16 | */
17 |
18 | #include "hooks.h"
19 | #include "common.h"
20 | #include "timeline.h"
21 |
22 | #include
23 |
24 | static void
25 | run_hooks_timeout(GPtrArray *hooks, HookTrigger trigger, gboolean state,
26 | guint timeout)
27 | {
28 | for (guint i = 0; i < hooks->len; i++) {
29 | struct Hook *h = g_ptr_array_index(hooks, i);
30 | if (h->trigger != trigger
31 | || (trigger == HOOK_TRIGGER_INACTIVE
32 | && h->inactive_sec != timeout))
33 | continue;
34 | if (state && h->exec_start)
35 | spawn_exec(h->exec_start);
36 | else if (!state && h->exec_stop)
37 | spawn_exec(h->exec_stop);
38 | }
39 | }
40 |
41 | void
42 | hooks_add_timeouts(GPtrArray *hooks, Timeline *tl)
43 | {
44 | for (guint i = 0; i < hooks->len; i++) {
45 | struct Hook *h = g_ptr_array_index(hooks, i);
46 | if (h->trigger == HOOK_TRIGGER_INACTIVE)
47 | timeline_add_timeout(tl, h->inactive_sec);
48 | }
49 | }
50 |
51 | void
52 | hooks_run(GPtrArray *hooks, HookTrigger trigger, gboolean state)
53 | {
54 | if (trigger == HOOK_TRIGGER_INACTIVE)
55 | return;
56 | run_hooks_timeout(hooks, trigger, state, 0);
57 | }
58 |
59 | void
60 | hooks_on_timeout(GPtrArray *hooks, guint timeout, gboolean state)
61 | {
62 | run_hooks_timeout(hooks, HOOK_TRIGGER_INACTIVE, state, timeout);
63 | }
64 |
--------------------------------------------------------------------------------
/src/hooks.h:
--------------------------------------------------------------------------------
1 | /*
2 | sessiond - standalone X session manager
3 | Copyright (C) 2018-2020 James Reed
4 |
5 | This program is free software: you can redistribute it and/or modify it under
6 | the terms of the GNU General Public License as published by the Free Software
7 | Foundation, either version 3 of the License, or (at your option) any later
8 | version.
9 |
10 | This program is distributed in the hope that it will be useful, but WITHOUT ANY
11 | WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
12 | PARTICULAR PURPOSE. See the GNU General Public License for more details.
13 |
14 | You should have received a copy of the GNU General Public License along with
15 | this program. If not, see .
16 | */
17 |
18 | #pragma once
19 |
20 | #include "timeline.h"
21 |
22 | #include
23 |
24 | #define HOOKS_TABLE_LIST \
25 | X("Trigger", trigger, trigger) \
26 | X("InactiveSec", uint, inactive_sec) \
27 | X("ExecStart", exec, exec_start) \
28 | X("ExecStop", exec, exec_stop)
29 |
30 | #define HOOK_TRIGGER_LIST \
31 | X(LOCK, "Lock") \
32 | X(IDLE, "Idle") \
33 | X(SLEEP, "Sleep") \
34 | X(SHUTDOWN, "Shutdown") \
35 | X(INACTIVE, "Inactive")
36 |
37 | typedef enum {
38 | HOOK_TRIGGER_NONE,
39 | #define X(trigger, _) HOOK_TRIGGER_##trigger,
40 | HOOK_TRIGGER_LIST
41 | #undef X
42 | } HookTrigger;
43 |
44 | struct Hook {
45 | HookTrigger trigger;
46 | guint inactive_sec;
47 | gchar **exec_start;
48 | gchar **exec_stop;
49 | };
50 |
51 | extern void
52 | hooks_add_timeouts(GPtrArray *hooks, Timeline *tl);
53 | extern void
54 | hooks_run(GPtrArray *hooks, HookTrigger trigger, gboolean state);
55 | extern void
56 | hooks_on_timeout(GPtrArray *hooks, guint timeout, gboolean state);
57 |
--------------------------------------------------------------------------------
/src/timeline.c:
--------------------------------------------------------------------------------
1 | /*
2 | sessiond - standalone X session manager
3 | Copyright (C) 2018-2020 James Reed
4 |
5 | This program is free software: you can redistribute it and/or modify it under
6 | the terms of the GNU General Public License as published by the Free Software
7 | Foundation, either version 3 of the License, or (at your option) any later
8 | version.
9 |
10 | This program is distributed in the hope that it will be useful, but WITHOUT ANY
11 | WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
12 | PARTICULAR PURPOSE. See the GNU General Public License for more details.
13 |
14 | You should have received a copy of the GNU General Public License along with
15 | this program. If not, see .
16 | */
17 |
18 | #define G_LOG_DOMAIN "sessiond-activity"
19 |
20 | #include "timeline.h"
21 |
22 | #include
23 |
24 | #define INACTIVE_SEC(tl) \
25 | ((g_get_monotonic_time() - tl->inactive_since) / 1000000)
26 |
27 | static void
28 | add_timeout(Timeline *tl, guint timeout);
29 |
30 | static gint
31 | compare_timeouts(gconstpointer a, gconstpointer b)
32 | {
33 | guint time_a = *(guint *)a;
34 | guint time_b = *(guint *)b;
35 |
36 | if (time_a < time_b)
37 | return -1;
38 | if (time_a > time_b)
39 | return 1;
40 |
41 | return 0;
42 | }
43 |
44 | static guint
45 | get_timeout(Timeline *tl)
46 | {
47 | return g_array_index(tl->timeouts, guint, tl->index);
48 | }
49 |
50 | static gboolean
51 | on_timeout(gpointer user_data)
52 | {
53 | Timeline *tl = (Timeline *)user_data;
54 |
55 | if (!tl || !tl->running)
56 | return G_SOURCE_REMOVE;
57 |
58 | guint inactive = INACTIVE_SEC(tl);
59 | guint timeout = get_timeout(tl);
60 |
61 | if (inactive >= timeout) {
62 | tl->index++;
63 | tl->func(timeout, TRUE, tl->user_data);
64 | if (tl->index < tl->timeouts->len)
65 | add_timeout(tl, get_timeout(tl) - inactive);
66 | else
67 | timeline_stop(tl);
68 | } else {
69 | add_timeout(tl, timeout - inactive);
70 | }
71 |
72 | return G_SOURCE_REMOVE;
73 | }
74 |
75 | static void
76 | remove_source(Timeline *tl)
77 | {
78 | if (!tl->source)
79 | return;
80 | g_debug("timeline: Removed timeout source");
81 | g_source_destroy(tl->source);
82 | g_source_unref(tl->source);
83 | tl->source = NULL;
84 | }
85 |
86 | static void
87 | add_timeout(Timeline *tl, guint timeout)
88 | {
89 | remove_source(tl);
90 |
91 | tl->source = g_timeout_source_new_seconds(timeout);
92 | g_source_set_callback(tl->source, on_timeout, tl, NULL);
93 | g_source_attach(tl->source, tl->ctx);
94 |
95 | g_debug("timeline: Added %us timeout source", timeout);
96 | }
97 |
98 | Timeline
99 | timeline_new(GMainContext *ctx, TimelineFunc func, gconstpointer user_data)
100 | {
101 | Timeline tl;
102 | tl.running = FALSE;
103 | tl.ctx = ctx;
104 | tl.source = NULL;
105 | tl.timeouts = g_array_new(FALSE, FALSE, sizeof(guint));
106 | tl.index = 0;
107 | tl.inactive_since = -1;
108 | tl.func = func;
109 | tl.user_data = user_data;
110 | return tl;
111 | }
112 |
113 | gboolean
114 | timeline_add_timeout(Timeline *tl, guint timeout)
115 | {
116 | for (guint i = 0; i < tl->timeouts->len; i++)
117 | if (g_array_index(tl->timeouts, guint, i) == timeout)
118 | return FALSE;
119 |
120 | g_array_append_val(tl->timeouts, timeout);
121 | g_debug("timeline: Added %us timeout", timeout);
122 |
123 | if (tl->running) {
124 | remove_source(tl);
125 | g_array_sort(tl->timeouts, compare_timeouts);
126 | tl->index = 0;
127 | guint inactive = INACTIVE_SEC(tl);
128 | for (guint i = 0; i < tl->timeouts->len; i++) {
129 | guint t = g_array_index(tl->timeouts, guint, i);
130 | if (t <= inactive)
131 | tl->index++;
132 | }
133 | if (inactive >= timeout)
134 | tl->func(timeout, TRUE, tl->user_data);
135 | add_timeout(tl, get_timeout(tl) - inactive);
136 | }
137 |
138 | return TRUE;
139 | }
140 |
141 | gboolean
142 | timeline_remove_timeout(Timeline *tl, guint timeout)
143 | {
144 | for (guint i = 0; i < tl->timeouts->len; i++)
145 | if (g_array_index(tl->timeouts, guint, i) == timeout) {
146 | g_array_remove_index(tl->timeouts, i);
147 | if (i <= tl->index)
148 | tl->index--;
149 | return TRUE;
150 | }
151 |
152 | return FALSE;
153 | }
154 |
155 | void
156 | timeline_start(Timeline *tl)
157 | {
158 | if (tl->running) {
159 | while (tl->index) {
160 | tl->index--;
161 | tl->func(get_timeout(tl), FALSE, tl->user_data);
162 | }
163 | } else if (tl->timeouts->len > 0) {
164 | g_array_sort(tl->timeouts, compare_timeouts);
165 | tl->running = TRUE;
166 | } else {
167 | return;
168 | }
169 |
170 | tl->index = 0;
171 | add_timeout(tl, get_timeout(tl));
172 | tl->inactive_since = g_get_monotonic_time();
173 | }
174 |
175 | void
176 | timeline_stop(Timeline *tl)
177 | {
178 | if (!tl->running)
179 | return;
180 | if (!tl->index)
181 | tl->running = FALSE;
182 | tl->inactive_since = -1;
183 | remove_source(tl);
184 | }
185 |
186 | guint
187 | timeline_pending_timeouts(Timeline *tl)
188 | {
189 | return tl->timeouts->len - tl->index;
190 | }
191 |
192 | void
193 | timeline_free(Timeline *tl)
194 | {
195 | if (!tl)
196 | return;
197 | timeline_stop(tl);
198 | if (tl->timeouts)
199 | g_array_free(tl->timeouts, TRUE);
200 | }
201 |
--------------------------------------------------------------------------------
/src/timeline.h:
--------------------------------------------------------------------------------
1 | /*
2 | sessiond - standalone X session manager
3 | Copyright (C) 2018-2020 James Reed
4 |
5 | This program is free software: you can redistribute it and/or modify it under
6 | the terms of the GNU General Public License as published by the Free Software
7 | Foundation, either version 3 of the License, or (at your option) any later
8 | version.
9 |
10 | This program is distributed in the hope that it will be useful, but WITHOUT ANY
11 | WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
12 | PARTICULAR PURPOSE. See the GNU General Public License for more details.
13 |
14 | You should have received a copy of the GNU General Public License along with
15 | this program. If not, see .
16 | */
17 |
18 | #pragma once
19 |
20 | #include
21 |
22 | typedef void (*TimelineFunc)(guint timeout, gboolean state,
23 | gconstpointer user_data);
24 |
25 | typedef struct {
26 | gboolean running;
27 | GMainContext *ctx;
28 | GSource *source;
29 | GArray *timeouts;
30 | guint index;
31 | gint64 inactive_since;
32 | TimelineFunc func;
33 | gconstpointer user_data;
34 | } Timeline;
35 |
36 | extern Timeline
37 | timeline_new(GMainContext *ctx, TimelineFunc func, gconstpointer user_data);
38 | extern gboolean
39 | timeline_add_timeout(Timeline *tl, guint timeout);
40 | extern gboolean
41 | timeline_remove_timeout(Timeline *tl, guint timeout);
42 | extern void
43 | timeline_start(Timeline *tl);
44 | extern void
45 | timeline_stop(Timeline *tl);
46 | extern guint
47 | timeline_pending_timeouts(Timeline *tl);
48 | extern void
49 | timeline_free(Timeline *tl);
50 |
--------------------------------------------------------------------------------
/src/toml/.gitignore:
--------------------------------------------------------------------------------
1 | *~
2 |
3 | # Object files
4 | *.o
5 | *.ko
6 | *.obj
7 | *.elf
8 |
9 | # Precompiled Headers
10 | *.gch
11 | *.pch
12 |
13 | # Libraries
14 | *.lib
15 | *.a
16 | *.la
17 | *.lo
18 |
19 | # Shared objects (inc. Windows DLLs)
20 | *.dll
21 | *.so
22 | *.so.*
23 | *.dylib
24 |
25 | # Executables
26 | *.exe
27 | *.out
28 | *.app
29 | *.i*86
30 | *.x86_64
31 | *.hex
32 | toml_cat
33 | toml_json
34 |
35 | # Debug files
36 | *.dSYM/
37 | *.su
38 |
--------------------------------------------------------------------------------
/src/toml/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2017 CK Tan
4 | https://github.com/cktan/tomlc99
5 |
6 | Permission is hereby granted, free of charge, to any person obtaining a copy
7 | of this software and associated documentation files (the "Software"), to deal
8 | in the Software without restriction, including without limitation the rights
9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | copies of the Software, and to permit persons to whom the Software is
11 | furnished to do so, subject to the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be included in all
14 | copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | SOFTWARE.
23 |
--------------------------------------------------------------------------------
/src/toml/Makefile:
--------------------------------------------------------------------------------
1 | CFILES = toml.c
2 |
3 | CFLAGS = -std=c99 -Wall -Wextra -fpic
4 | # to compile for debug: make DEBUG=1
5 | # to compile for no debug: make
6 | ifdef DEBUG
7 | CFLAGS += -O0 -g
8 | else
9 | CFLAGS += -O2 -DNDEBUG
10 | endif
11 |
12 | EXEC = toml_json toml_cat
13 |
14 | LIB = libtoml.a
15 | LIB_SHARED = libtoml.so
16 |
17 | all: $(LIB) $(LIB_SHARED) $(EXEC)
18 |
19 |
20 | libtoml.a: toml.o
21 | ar -rcs $@ $^
22 |
23 | libtoml.so: toml.o
24 | $(CC) -shared -o $@ $^
25 |
26 | toml_json: toml_json.c $(LIB)
27 |
28 | toml_cat: toml_cat.c $(LIB)
29 |
30 | prefix ?= /usr/local
31 |
32 | install: all
33 | install -d ${prefix}/include ${prefix}/lib
34 | install toml.h ${prefix}/include
35 | install $(LIB) ${prefix}/lib
36 | install $(LIB_SHARED) ${prefix}/lib
37 |
38 | clean:
39 | rm -f *.o $(EXEC) $(LIB) $(LIB_SHARED)
40 |
--------------------------------------------------------------------------------
/src/toml/README.md:
--------------------------------------------------------------------------------
1 | # tomlc99
2 | TOML in c99; v0.5.0 compliant.
3 |
4 |
5 | # Usage
6 |
7 | Please see the `toml.h` file for details. What follows is a simple example that
8 | parses this config file:
9 |
10 | ```
11 | [server]
12 | host = "www.example.com"
13 | port = 80
14 | ```
15 |
16 | For each config param, the code first extracts a raw value and then
17 | convert it to a string or integer depending on context.
18 |
19 | ```
20 |
21 | FILE* fp;
22 | toml_table_t* conf;
23 | toml_table_t* server;
24 | const char* raw;
25 | char* host;
26 | int64_t port;
27 | char errbuf[200];
28 |
29 | /* open file and parse */
30 | if (0 == (fp = fopen(FNAME, "r"))) {
31 | return handle_error();
32 | }
33 | conf = toml_parse_file(fp, errbuf, sizeof(errbuf));
34 | fclose(fp);
35 | if (0 == conf) {
36 | return handle_error();
37 | }
38 |
39 | /* locate the [server] table */
40 | if (0 == (server = toml_table_in(conf, "server"))) {
41 | return handle_error();
42 | }
43 |
44 | /* extract host config value */
45 | if (0 == (raw = toml_raw_in(server, "host"))) {
46 | return handle_error();
47 | }
48 | if (toml_rtos(raw, &host)) {
49 | return handle_error();
50 | }
51 |
52 | /* extract port config value */
53 | if (0 == (raw = toml_raw_in(server, "port"))) {
54 | return handle_error();
55 | }
56 | if (toml_rtoi(raw, &port)) {
57 | return handle_error();
58 | }
59 |
60 | /* done with conf */
61 | toml_free(conf);
62 |
63 | /* use host and port */
64 | do_work(host, port);
65 |
66 | /* clean up */
67 | free(host);
68 | ```
69 |
70 |
71 | # Building
72 |
73 | A normal *make* suffices. Alternately, you can also simply include the
74 | `toml.c` and `toml.h` files in your project.
75 |
76 | # Testing
77 |
78 | To test against the standard test set provided by BurntSushi/toml-test:
79 |
80 | ```
81 | % make
82 | % cd test1
83 | % bash build.sh # do this once
84 | % bash run.sh # this will run the test suite
85 | ```
86 |
87 |
88 | To test against the standard test set provided by iarna/toml:
89 |
90 | ```
91 | % make
92 | % cd test2
93 | % bash build.sh # do this once
94 | % bash run.sh # this will run the test suite
95 | ```
96 |
--------------------------------------------------------------------------------
/src/toml/test1/.gitignore:
--------------------------------------------------------------------------------
1 | /goworkspace
2 | /toml-test
3 | /toml-test-decoder
4 |
--------------------------------------------------------------------------------
/src/toml/test1/README.md:
--------------------------------------------------------------------------------
1 | How to run the tests
2 | ===
3 |
4 | ```
5 | % bash build.sh
6 | % bash run.sh
7 | 77 passed, 0 failed
8 | ```
9 |
10 |
--------------------------------------------------------------------------------
/src/toml/test1/build.sh:
--------------------------------------------------------------------------------
1 |
2 | DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
3 |
4 | mkdir -p $DIR/goworkspace
5 | export GOPATH=$DIR/goworkspace
6 | go get github.com/BurntSushi/toml-test # install test suite
7 | go get github.com/BurntSushi/toml/cmd/toml-test-decoder # e.g., install my parser
8 | cp $GOPATH/bin/* .
9 |
10 |
--------------------------------------------------------------------------------
/src/toml/test1/extra/array_of_tables.toml:
--------------------------------------------------------------------------------
1 | x = [ {'a'= 1}, {'a'= 2} ]
2 |
--------------------------------------------------------------------------------
/src/toml/test1/extra/inline_array.toml:
--------------------------------------------------------------------------------
1 | x = [1,2,3]
2 |
--------------------------------------------------------------------------------
/src/toml/test1/extra/inline_table.toml:
--------------------------------------------------------------------------------
1 | x = {'a'= 1, 'b'= 2 }
2 |
--------------------------------------------------------------------------------
/src/toml/test1/run.sh:
--------------------------------------------------------------------------------
1 | DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
2 | export GOPATH=$DIR/goworkspace # if it isn't already set
3 | # $GOPATH/bin/toml-test $GOPATH/bin/toml-test-decoder # e.g., run tests on my parser
4 | $GOPATH/bin/toml-test ../toml_json
5 |
--------------------------------------------------------------------------------
/src/toml/test2/.gitignore:
--------------------------------------------------------------------------------
1 | /toml-spec-tests
--------------------------------------------------------------------------------
/src/toml/test2/build.sh:
--------------------------------------------------------------------------------
1 | set -e
2 |
3 | DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
4 |
5 | [ -d toml-spec-tests ] || git clone https://github.com/cktan/toml-spec-tests.git
6 |
7 |
--------------------------------------------------------------------------------
/src/toml/test2/run.sh:
--------------------------------------------------------------------------------
1 | if ! (which jq >& /dev/null); then
2 | echo "ERROR: please install the 'jq' utility"
3 | exit 1
4 | fi
5 |
6 | #
7 | # POSITIVE tests
8 | #
9 | for i in toml-spec-tests/values/*.toml; do
10 | fname="$i"
11 | ext="${fname##*.}"
12 | fname="${fname%.*}"
13 | echo -n $fname ' '
14 | res='[OK]'
15 | if (../toml_json $fname.toml >& $fname.json.out); then
16 | jq -S . $fname.json.out > t.json
17 | mv t.json $fname.json.out
18 | if [ -f $fname.json ]; then
19 | if ! (diff $fname.json $fname.json.out >& /dev/null); then
20 | res='[FAILED]'
21 | else
22 | rm -f $fname.json.out
23 | fi
24 | else
25 | res='[??]'
26 | fi
27 | fi
28 | echo ... $res
29 | done
30 |
31 |
32 | #
33 | # NEGATIVE tests
34 | #
35 | for i in toml-spec-tests/errors/*.toml; do
36 | echo -n $i ' '
37 | res='[OK]'
38 | if (../toml_json $i >& $i.json.out); then
39 | res='[FAILED]'
40 | fi
41 | echo ... $res
42 | done
43 |
--------------------------------------------------------------------------------
/src/toml/toml.h:
--------------------------------------------------------------------------------
1 | /*
2 | MIT License
3 |
4 | Copyright (c) 2017 - 2019 CK Tan
5 | https://github.com/cktan/tomlc99
6 |
7 | Permission is hereby granted, free of charge, to any person obtaining a copy
8 | of this software and associated documentation files (the "Software"), to deal
9 | in the Software without restriction, including without limitation the rights
10 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11 | copies of the Software, and to permit persons to whom the Software is
12 | furnished to do so, subject to the following conditions:
13 |
14 | The above copyright notice and this permission notice shall be included in all
15 | copies or substantial portions of the Software.
16 |
17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23 | SOFTWARE.
24 | */
25 | #ifndef TOML_H
26 | #define TOML_H
27 |
28 |
29 | #include
30 | #include
31 |
32 |
33 | #ifdef __cplusplus
34 | #define TOML_EXTERN extern "C"
35 | #else
36 | #define TOML_EXTERN extern
37 | #endif
38 |
39 | typedef struct toml_table_t toml_table_t;
40 | typedef struct toml_array_t toml_array_t;
41 |
42 | /* Parse a file. Return a table on success, or 0 otherwise.
43 | * Caller must toml_free(the-return-value) after use.
44 | */
45 | TOML_EXTERN toml_table_t* toml_parse_file(FILE* fp,
46 | char* errbuf,
47 | int errbufsz);
48 |
49 | /* Parse a string containing the full config.
50 | * Return a table on success, or 0 otherwise.
51 | * Caller must toml_free(the-return-value) after use.
52 | */
53 | TOML_EXTERN toml_table_t* toml_parse(char* conf, /* NUL terminated, please. */
54 | char* errbuf,
55 | int errbufsz);
56 |
57 | /* Free the table returned by toml_parse() or toml_parse_file(). */
58 | TOML_EXTERN void toml_free(toml_table_t* tab);
59 |
60 | /* Retrieve the key in table at keyidx. Return 0 if out of range. */
61 | TOML_EXTERN const char* toml_key_in(toml_table_t* tab, int keyidx);
62 |
63 | /* Lookup table by key. Return the element or 0 if not found. */
64 | TOML_EXTERN const char* toml_raw_in(toml_table_t* tab, const char* key);
65 | TOML_EXTERN toml_array_t* toml_array_in(toml_table_t* tab, const char* key);
66 | TOML_EXTERN toml_table_t* toml_table_in(toml_table_t* tab, const char* key);
67 |
68 | /* Return the array kind: 't'able, 'a'rray, 'v'alue */
69 | TOML_EXTERN char toml_array_kind(toml_array_t* arr);
70 |
71 | /* For array kind 'v'alue, return the type of values
72 | i:int, d:double, b:bool, s:string, t:time, D:date, T:timestamp
73 | 0 if unknown
74 | */
75 | TOML_EXTERN char toml_array_type(toml_array_t* arr);
76 |
77 |
78 | /* Return the number of elements in the array */
79 | TOML_EXTERN int toml_array_nelem(toml_array_t* arr);
80 |
81 | /* Return the key of an array */
82 | TOML_EXTERN const char* toml_array_key(toml_array_t* arr);
83 |
84 | /* Return the number of key-values in a table */
85 | TOML_EXTERN int toml_table_nkval(toml_table_t* tab);
86 |
87 | /* Return the number of arrays in a table */
88 | TOML_EXTERN int toml_table_narr(toml_table_t* tab);
89 |
90 | /* Return the number of sub-tables in a table */
91 | TOML_EXTERN int toml_table_ntab(toml_table_t* tab);
92 |
93 | /* Return the key of a table*/
94 | TOML_EXTERN const char* toml_table_key(toml_table_t* tab);
95 |
96 | /* Deref array by index. Return the element at idx or 0 if out of range. */
97 | TOML_EXTERN const char* toml_raw_at(toml_array_t* arr, int idx);
98 | TOML_EXTERN toml_array_t* toml_array_at(toml_array_t* arr, int idx);
99 | TOML_EXTERN toml_table_t* toml_table_at(toml_array_t* arr, int idx);
100 |
101 |
102 | /* Raw to String. Caller must call free(ret) after use.
103 | * Return 0 on success, -1 otherwise.
104 | */
105 | TOML_EXTERN int toml_rtos(const char* s, char** ret);
106 |
107 | /* Raw to Boolean. Return 0 on success, -1 otherwise. */
108 | TOML_EXTERN int toml_rtob(const char* s, int* ret);
109 |
110 | /* Raw to Integer. Return 0 on success, -1 otherwise. */
111 | TOML_EXTERN int toml_rtoi(const char* s, int64_t* ret);
112 |
113 | /* Raw to Double. Return 0 on success, -1 otherwise. */
114 | TOML_EXTERN int toml_rtod(const char* s, double* ret);
115 | /* Same as toml_rtod, but return the sanitized double in string form as well */
116 | TOML_EXTERN int toml_rtod_ex(const char* s, double* ret, char* buf, int buflen);
117 |
118 | /* Timestamp types. The year, month, day, hour, minute, second, z
119 | * fields may be NULL if they are not relevant. e.g. In a DATE
120 | * type, the hour, minute, second and z fields will be NULLs.
121 | */
122 | typedef struct toml_timestamp_t toml_timestamp_t;
123 | struct toml_timestamp_t {
124 | struct { /* internal. do not use. */
125 | int year, month, day;
126 | int hour, minute, second, millisec;
127 | char z[10];
128 | } __buffer;
129 | int *year, *month, *day;
130 | int *hour, *minute, *second, *millisec;
131 | char* z;
132 | };
133 |
134 | /* Raw to Timestamp. Return 0 on success, -1 otherwise. */
135 | TOML_EXTERN int toml_rtots(const char* s, toml_timestamp_t* ret);
136 |
137 | /* misc */
138 | TOML_EXTERN int toml_utf8_to_ucs(const char* orig, int len, int64_t* ret);
139 | TOML_EXTERN int toml_ucs_to_utf8(int64_t code, char buf[6]);
140 | TOML_EXTERN void toml_set_memutil(void* (*xxmalloc)(size_t),
141 | void (*xxfree)(void*),
142 | void* (*xxcalloc)(size_t, size_t),
143 | void* (*xxrealloc)(void*, size_t));
144 |
145 | #endif /* TOML_H */
146 |
--------------------------------------------------------------------------------
/src/toml/toml_cat.c:
--------------------------------------------------------------------------------
1 | /*
2 | MIT License
3 |
4 | Copyright (c) 2017 CK Tan
5 | https://github.com/cktan/tomlc99
6 |
7 | Permission is hereby granted, free of charge, to any person obtaining a copy
8 | of this software and associated documentation files (the "Software"), to deal
9 | in the Software without restriction, including without limitation the rights
10 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11 | copies of the Software, and to permit persons to whom the Software is
12 | furnished to do so, subject to the following conditions:
13 |
14 | The above copyright notice and this permission notice shall be included in all
15 | copies or substantial portions of the Software.
16 |
17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23 | SOFTWARE.
24 | */
25 |
26 | #ifdef NDEBUG
27 | #undef NDEBUG
28 | #endif
29 |
30 | #include
31 | #include
32 | #include
33 | #include
34 | #include
35 | #include
36 | #include "toml.h"
37 |
38 | typedef struct node_t node_t;
39 | struct node_t {
40 | const char* key;
41 | toml_table_t* tab;
42 | };
43 |
44 | node_t stack[20];
45 | int stacktop = 0;
46 |
47 |
48 | static void print_table_title(const char* arrname)
49 | {
50 | int i;
51 | printf("%s", arrname ? "[[" : "[");
52 | for (i = 1; i < stacktop; i++) {
53 | printf("%s", stack[i].key);
54 | if (i + 1 < stacktop)
55 | printf(".");
56 | }
57 | if (arrname)
58 | printf(".%s]]\n", arrname);
59 | else
60 | printf("]\n");
61 | }
62 |
63 |
64 | static void print_array_of_tables(toml_array_t* arr, const char* key);
65 | static void print_array(toml_array_t* arr);
66 |
67 |
68 | static void print_table(toml_table_t* curtab)
69 | {
70 | int i;
71 | const char* key;
72 | const char* raw;
73 | toml_array_t* arr;
74 | toml_table_t* tab;
75 |
76 |
77 | for (i = 0; 0 != (key = toml_key_in(curtab, i)); i++) {
78 | if (0 != (raw = toml_raw_in(curtab, key))) {
79 | printf("%s = %s\n", key, raw);
80 | } else if (0 != (arr = toml_array_in(curtab, key))) {
81 | if (toml_array_kind(arr) == 't') {
82 | print_array_of_tables(arr, key);
83 | }
84 | else {
85 | printf("%s = [\n", key);
86 | print_array(arr);
87 | printf(" ]\n");
88 | }
89 | } else if (0 != (tab = toml_table_in(curtab, key))) {
90 | stack[stacktop].key = key;
91 | stack[stacktop].tab = tab;
92 | stacktop++;
93 | print_table_title(0);
94 | print_table(tab);
95 | stacktop--;
96 | } else {
97 | abort();
98 | }
99 | }
100 | }
101 |
102 | static void print_array_of_tables(toml_array_t* arr, const char* key)
103 | {
104 | int i;
105 | toml_table_t* tab;
106 | printf("\n");
107 | for (i = 0; 0 != (tab = toml_table_at(arr, i)); i++) {
108 | print_table_title(key);
109 | print_table(tab);
110 | printf("\n");
111 | }
112 | }
113 |
114 |
115 | static void print_array(toml_array_t* curarr)
116 | {
117 | toml_array_t* arr;
118 | const char* raw;
119 | toml_table_t* tab;
120 | int i;
121 |
122 | switch (toml_array_kind(curarr)) {
123 |
124 | case 'v':
125 | for (i = 0; 0 != (raw = toml_raw_at(curarr, i)); i++) {
126 | printf(" %d: %s,\n", i, raw);
127 | }
128 | break;
129 |
130 | case 'a':
131 | for (i = 0; 0 != (arr = toml_array_at(curarr, i)); i++) {
132 | printf(" %d: \n", i);
133 | print_array(arr);
134 | }
135 | break;
136 |
137 | case 't':
138 | for (i = 0; 0 != (tab = toml_table_at(curarr, i)); i++) {
139 | print_table(tab);
140 | }
141 | printf("\n");
142 | break;
143 |
144 | case '\0':
145 | break;
146 |
147 | default:
148 | abort();
149 | }
150 | }
151 |
152 |
153 |
154 | static void cat(FILE* fp)
155 | {
156 | char errbuf[200];
157 |
158 | toml_table_t* tab = toml_parse_file(fp, errbuf, sizeof(errbuf));
159 | if (!tab) {
160 | fprintf(stderr, "ERROR: %s\n", errbuf);
161 | return;
162 | }
163 |
164 | stack[stacktop].tab = tab;
165 | stack[stacktop].key = "";
166 | stacktop++;
167 | print_table(tab);
168 | stacktop--;
169 |
170 | toml_free(tab);
171 | }
172 |
173 |
174 | int main(int argc, const char* argv[])
175 | {
176 | int i;
177 | if (argc == 1) {
178 | cat(stdin);
179 | } else {
180 | for (i = 1; i < argc; i++) {
181 |
182 | FILE* fp = fopen(argv[i], "r");
183 | if (!fp) {
184 | fprintf(stderr, "ERROR: cannot open %s: %s\n",
185 | argv[i], strerror(errno));
186 | exit(1);
187 | }
188 | cat(fp);
189 | fclose(fp);
190 | }
191 | }
192 | return 0;
193 | }
194 |
--------------------------------------------------------------------------------
/src/toml/toml_json.c:
--------------------------------------------------------------------------------
1 | /*
2 | MIT License
3 |
4 | Copyright (c) 2017 CK Tan
5 | https://github.com/cktan/tomlc99
6 |
7 | Permission is hereby granted, free of charge, to any person obtaining a copy
8 | of this software and associated documentation files (the "Software"), to deal
9 | in the Software without restriction, including without limitation the rights
10 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11 | copies of the Software, and to permit persons to whom the Software is
12 | furnished to do so, subject to the following conditions:
13 |
14 | The above copyright notice and this permission notice shall be included in all
15 | copies or substantial portions of the Software.
16 |
17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23 | SOFTWARE.
24 | */
25 | #ifdef NDEBUG
26 | #undef NDEBUG
27 | #endif
28 |
29 | #include
30 | #include