Compare commits

..

239 Commits

Author SHA1 Message Date
Emmanuel Lepage Vallée 45c47f7d30
Update lib/awful/wallpaper.lua
Co-authored-by: Aire-One <aireone@aireone.xyz>
2021-09-25 12:49:28 -07:00
Emmanuel Lepage Vallée aa01c408ca
Update lib/awful/wallpaper.lua
Co-authored-by: Aire-One <aireone@aireone.xyz>
2021-09-25 12:49:19 -07:00
Emmanuel Lepage Vallee 546ac6aec9 container.tile: Correctly transfer the Cairo source to the tile. 2021-09-24 08:42:42 -07:00
Emmanuel Lepage Vallée 01ae508899 Update lib/awful/wallpaper.lua
Co-authored-by: Aire-One <aireone@aireone.xyz>
2021-09-24 08:42:42 -07:00
Emmanuel Lepage Vallée 7b8426bd24 Update lib/wibox/widget/imagebox.lua
Co-authored-by: Aire-One <aireone@aireone.xyz>
2021-09-24 08:42:42 -07:00
Emmanuel Lepage Vallée 7806763667 Update docs/05-awesomerc.md.lua
Co-authored-by: Aire-One <aireone@aireone.xyz>
2021-09-24 08:42:42 -07:00
Emmanuel Lepage Vallee 70cd293839 doc: Integrate `awful.wallpaper` into the guides. 2021-09-24 08:42:42 -07:00
Emmanuel Lepage Vallee 4a0645e942 doc: Correctly render --DOC_NO_USAGE + --DOC_GEN_OUTPUT.
It was previously merged into a single block.
2021-09-24 08:42:42 -07:00
Emmanuel Lepage Vallee 1ff860454e theme: Use a SVG wallpaper for the xresources theme.
It uses a stylesheet for the colors. This is not "as good" as the
old one because it only supports 16:9 aspect ratio. However, the
old wallpaper function doesn't fit great in the new model. The
widget doesn't know the screen, so it cannot pass it to a function.
It's possible to stretch the wallpaper.
2021-09-24 08:42:42 -07:00
Emmanuel Lepage Vallee bbf1c9270f tests: Test the imagebox DPI and CSS stylesheet. 2021-09-24 08:42:42 -07:00
Emmanuel Lepage Vallee d8bc791818 imagebox: Add DPI and CSS Stylesheet properties for SVG images.
This exposes the `librsvg` DPI and Stylesheet properties. This
is a groundwork commit to port the xresources theme to use a SVG
wallpaper instead of hardcoded Cairo code to generate the colorfar
logo.
2021-09-24 08:42:42 -07:00
Emmanuel Lepage Vallee 883cdb7f41 doc: Fix the rendering of double quotes and semicolons
CMake uses quotes and semicolon in its internal list datatype.

Previously, all double quotes were converted to single quotes to
avoid this problem. Semicolors were interpreted as newlines in
ldoc.

With this commit, both of them render fine. This was required
because a new example uses CSS and XML where those symbols have
a specific meanning.
2021-09-24 08:42:42 -07:00
Emmanuel Lepage Vallee ddccddb6dc imagebox: Allow to load SVG data without saving it. 2021-09-24 08:42:42 -07:00
Emmanuel Lepage Vallee 3f2db184ad gears.wallpaper: Deprecate. 2021-09-24 08:42:42 -07:00
Emmanuel Lepage Vallee e55ea2b0d5 doc: Support multiple screens in the "default look" template. 2021-09-24 08:42:42 -07:00
Emmanuel Lepage Vallee 6b7ecc51f8 tests: Add awful.wallpaper tests 2021-09-24 08:42:42 -07:00
Emmanuel Lepage Vallee 580f16b7ff tests: Draw the wallpaper in the awful template 2021-09-24 08:42:42 -07:00
Emmanuel Lepage Vallee dcdc6a1089 shims: Add awesome._get_key_name 2021-09-24 08:42:42 -07:00
Emmanuel Lepage Vallee 8481af90c5 shims: Add a wallpaper setter. 2021-09-24 08:42:42 -07:00
Emmanuel Lepage Vallee b9971a5acc root: Do not end up in an infinite loop if the wallpaper is `nil`.
If `root.wallpaper` was called with `nil` during initialization,
AwesomeWM would get into an infinite restart loop.
2021-09-24 08:42:42 -07:00
Emmanuel Lepage Vallee b0a2d82d8f compat: Do not require to pass pattern._native to the wallpaper.
It makes the shims impossible to implement without a double free,
a memory leak or a crash. Using `capi` should not require to
destroy the LGI wrappers.

Another example, not fixed in this commit, are the client shapes.
2021-09-24 08:42:42 -07:00
Emmanuel Lepage Vallee 82f54ab95c rc.lua: Switch to awful.wallpaper 2021-09-24 08:42:42 -07:00
Emmanuel Lepage Vallee 98dd78b777 awful: Add a wallpaper module.
`gears.wallpaper` is a flat API (that doesn't even belong in gears) and
is neither well integrated with the other AwesomeWM concepts, nor well
documented or easy to understand for newcomers.

This module adds an object oriented, declarative, module with properties
for the most common wallpaper types. It also integrates with
`awful.placement` and the `wibox` module.

The design attempts to make the wallpaper a "wibox like" object like
the titlebars. It is non-interactive, but still allows the widgets. Note
that this is slow and should be avoided for dynamic content. It is why
the widgets are never updated unless manually reloaded. The objects also
attempt to be disposable rather than persistent. Thus they are immutable
by default to prevent accidental abuse.

Fix #3428 #2596
2021-09-24 08:42:42 -07:00
Emmanuel Lepage Vallee aa998db626 tests: Test the imagebox upscale and downscale properties. 2021-09-24 08:38:13 -07:00
Emmanuel Lepage Vallee 5212f0634a imagebox: Add a `downscale` and `upscale` property.
Previously, it was really hard to get the image to do one *or* the
other. The `resize` property set both at the same time.

While it could, this does not deprecate the old `resize` property
since 99% of the user wan't to lock both.
2021-09-24 08:38:13 -07:00
Emmanuel Lepage Vallee cefd4f843e gears.filesystem: Improve get_random_file_from_dir.
Previously, only a filename was returned. Getting the path was
inconvinient when used within a declarative construct.

It was also misbehaving then the directory didn't exist. Finally,
the extention list now accept extension names starting with a dot.
2021-09-24 08:38:13 -07:00
Emmanuel Lepage Vallée eddabebac4
Merge pull request #3436 from Elv13/fix_3428
Fix setting the screen in a notification preset.
2021-09-24 07:53:54 -07:00
Emmanuel Lepage Vallee 64d190546d core: Always add a search path when `-c` is passed to the cli.
This way modular config load with just `-c` and don't require the
extra `-s`.
2021-09-22 19:17:02 -07:00
Emmanuel Lepage Vallee b038463e22 naughty.legacy: Handle broken configs betters.
If a config with naughty.layouts.box crashes at startup and the
fallback config uses naughty.layouts.legaxy, it is possible their
will be some lookup for an object which isn't tracked by the legacy
module.
2021-09-22 19:17:02 -07:00
Emmanuel Lepage Vallee 24fc1043ee tests: Remove a flackyness in test-input-binding 2021-09-22 19:17:02 -07:00
Emmanuel Lepage Vallee ccbe116877 tests: Try to mitigate a flacky test with luajit 5.1.
Give more attempt for the wibars to be GCed. This test fails ~5% of
the time since moving from Travis CI to GitHub actions.
2021-09-22 19:17:02 -07:00
Emmanuel Lepage Vallee bd8f2f936b naughty: Fix a memory leak related to suspended notifications. 2021-09-22 19:17:02 -07:00
Emmanuel Lepage Vallee 552b2a22d1 tests: Test the notification presets.
Make sure they behave the same in legacy and ruled modes.
2021-09-22 19:17:02 -07:00
Emmanuel Lepage Vallee 9336b62f80 ruled.notification: Handle legacy presets.
This is deprecated, but some modules like `lain` use them, so it
must still minimally work for backward compatibility.
2021-09-22 19:17:02 -07:00
Emmanuel Lepage Vallee e752943b98 notification: Correctly handle `screen` set in the presets.
It was setting directly to `_private`, which both didn't work
and caused a memory leak.

Fix #3428
2021-09-22 19:17:02 -07:00
Emmanuel Lepage Vallee 21a111d154 naughty: Allow to set the message from the presets again.
This was never really supported and is really not what the presets
were event meant to be used for. But it worked and `lain` used it.
2021-09-22 19:17:02 -07:00
Emmanuel Lepage Vallee 89c84caee4 naughty.legacy: Explicitly remove the screen.
Do not rely on the GC. It isn't reliable with Luajit.
2021-09-22 13:39:58 -07:00
poisson-aerohead 87e09f81ab
Correction of error message in awful.keygrabber.run_with_keybindings (#3445)
Two calls to gdebug.print_warning() in run_with_keybindings use an
argument that concatenates a fixed string with the result returned by a
call to gdebug.dump(). gdebug.dump() is the debug library function for
immediate printing. It has no return value. This causes the correct
message in print_warning to not be printed, and also causes a
naughty.notify box to appear warning of an attempt to concatenate to an
empty variable.

The call to gdebug.print_warning should have an argument made by
concatenating to gdebug.dump_return(). Incidentally, this is in fact the
function used internally by gdebug.print().

Signed off by: Brian Sobulefsky <brian.sobulefsky@protonmail.com>
2021-09-21 08:34:46 -07:00
Emmanuel Lepage Vallée 5ca16ae8a0
Merge pull request #3364 from Aire-One/fix/awful.key-constructor
[doc] awful.key contructor and general improvements
2021-09-20 11:39:41 -07:00
martin f. krafft abac464384
Replace /usr/bin/which with command -v (#3439)
The `which` utility is being phased out since POSIX has mandated
`command -v` for the same task, so let's use that in the awesome-client
script.

Signed-off-by: martin f. krafft <madduck@madduck.net>
2021-09-20 10:21:43 -07:00
Emmanuel Lepage Vallée d9bfc9c37e
Merge pull request #3442 from sclu1034/feature/github_actions
Fix "test previous commit" workflow task with only one commit
2021-09-19 22:17:11 -07:00
Emmanuel Lepage Vallée c3a508a886
Merge pull request #3386 from Elv13/awful_wibar
Fix some wibar bugs, polish the API a bit and add some doc.
2021-09-19 13:05:11 -07:00
Lucas Schwiderski 4b6d7fd241
bug(github_actions): Fix logging failed commits
Signed-off-by: Lucas Schwiderski <lucas@lschwiderski.de>
2021-09-19 15:11:26 +02:00
Lucas Schwiderski c12715e11f
bug(github_actions): Skip testing single previous commit
Due to the default of executing bash with `-e -o pipefail` this didn't
fail gracefully anymore. But an explicit check makes more sense anyways.
2021-09-19 15:02:46 +02:00
Emmanuel Lepage Vallée edf21742b8
Merge pull request #3438 from Elv13/fix_3384
Fix 3384
2021-09-17 13:10:58 -07:00
Emmanuel Lepage Vallee e27b908ca0 doc: Document the naughty.action "invoked" signal. 2021-09-16 17:25:46 -07:00
Emmanuel Lepage Vallee 17bd5fb036 naughty: Pass the notification object to the action "invoked" signal.
Fix #3384
2021-09-16 17:22:33 -07:00
Emmanuel Lepage Vallée 73e908ed95 Apply suggestions from code review
Co-authored-by: Lucas Schwiderski <4508454+sclu1034@users.noreply.github.com>
2021-09-12 01:59:48 -07:00
Emmanuel Lepage Vallée 6ad693eff0 Apply suggestions from code review
Co-authored-by: Aire-One <Aire-One@users.noreply.github.com>
2021-09-12 01:59:48 -07:00
Emmanuel Lepage Vallee d5d74e44de doc: Document the wibar module. 2021-09-12 01:59:48 -07:00
Emmanuel Lepage Vallee b2368c54a8 tests: Fix the `screen` template rendering with multiple screens.
It only worked for a single screen. It still isn't perfect, but
much better than it was.
2021-09-12 01:59:48 -07:00
Emmanuel Lepage Vallee dd6163ffe6 shims: Fix the screen workarea value.
It didn't work with multiple screens.
2021-09-12 01:59:48 -07:00
Emmanuel Lepage Vallee a94a4beb6f doc: Add a missing DOC_HIDE which damaged the rendering. 2021-09-12 01:59:48 -07:00
Emmanuel Lepage Vallee b883855272 wibar: Allow to configure how conflicts between wibars are resolved.
Previously, the horizontal wibars would always get priority. Now,
this can be configured.
2021-09-12 01:59:48 -07:00
Emmanuel Lepage Vallee 529a6c2a8d wibar: Expose update_workarea.
It is now possible to create wibars which are not changing the
tiled area.
2021-09-12 01:59:48 -07:00
Emmanuel Lepage Vallee 0828c20a55 rc.lua: Be consistent about how the wibar is created.
The syntax was a leftover from ancient time where the wibar
constructor could not forward the properties to the wibox and
when the wibox didn't take a `widget` argument.

I make this change mostly to be consistent with the documentation
examples.
2021-09-12 01:59:48 -07:00
Emmanuel Lepage Vallee 67dc363437 wibar: Modernize the module structure.
Move the burdensome legacy code into local function so the normal
`gears.table.crush` module setup can be used. This fixes a couple
monior issues where `args` would be ignored.

This also makes a minor change to the logic. Changing the position
always moves to wibar to the end of the stack. Previously, there
was a minor case where it would not. There was also the case when
setting the same position twice would move the wibar, which was a bug.
2021-09-12 01:59:48 -07:00
Emmanuel Lepage Vallee dab767af3e wibar: Restore the ability to align a wibar.
This was lost in 3.5->4.0 update, but still had some references in
the code and doc. At the time, the plan was to add it back
"shortly after" based on the `awful.placement` code, but it was
never merged.
2021-09-12 01:59:48 -07:00
Emmanuel Lepage Vallee b62f343409 wibar: Add margins.
This is a feature request from Reddit. With this commit, the
awful.placement margins are exposed to the wibar API.
2021-09-12 01:59:48 -07:00
Emmanuel Lepage Vallee ab977b2358 placement: Fix the struts size when margins are present.
Previously, it only added 1 of the 2 sides of the relevant margins
to the struct size. For example, if the position was "top", then
only the top margin was added, not the bottom one.
2021-09-12 01:59:48 -07:00
Emmanuel Lepage Vallee b4afd0206b tests: Make sure wibar resize works.
Also add some garbage collection tests. This was my original
theory about why resizing was broken, but it turned out something
in `awful.placement` leaked, not the wibar references.
2021-09-12 01:59:48 -07:00
Emmanuel Lepage Vallee d9f27bdf4b placement: Do not leak the composed placement "override" in the args.
Without this change, if a composed placement was used, it would leak
the "override_geometry" in the args, which would be kept cached during
the next composed node. If that node had no override of its own, it
would use the previous one by accident.

In practice, it means it was impossible to resize a `wibar` manually.
The `wibar` would always restore itself to the height (or width, for
vertical ones) it previously had.
2021-09-12 01:59:48 -07:00
Emmanuel Lepage Vallee 4b606fb3d7 notification: Fix rendering during early screen creation.
If the problem happens early enough, it was possible that the
screen arrway wasn't initialized yet. In that case, the notification
would fail to render.
2021-09-12 01:59:48 -07:00
Emmanuel Lepage Vallée 2a8c17daea
Merge pull request #3434 from sclu1034/test/github_actions
Fix GitHub Actions
2021-09-11 14:47:10 -07:00
Lucas Schwiderski f39e8d261b
Fall back to GitHub's token for coveralls
Signed-off-by: Lucas Schwiderski <lucas@lschwiderski.de>
2021-09-11 14:03:11 +02:00
Lucas Schwiderski 86ec4888b3
Fix newline printing
Signed-off-by: Lucas Schwiderski <lucas@lschwiderski.de>
2021-09-11 13:49:47 +02:00
Lucas Schwiderski cc882585de
Fix test for previous commits
Signed-off-by: Lucas Schwiderski <lucas@lschwiderski.de>
2021-09-11 13:47:48 +02:00
Emmanuel Lepage Vallée ee3c42652e
Merge pull request #3292 from sclu1034/feature/github_actions
Migrate CI to GitHub Actions
2021-09-10 14:25:54 -07:00
mergify[bot] 7ae6d26363
Merge pull request #3387 from mattplm/master
Stop using fixed row number in calendars
2021-09-07 23:51:38 +00:00
mergify[bot] c88dcf5405
Merge pull request #3409 from vicious-widgets/vicious-async
Remove Vicious as async counterexample
2021-09-07 21:25:04 +00:00
mattplm 97726e8f38 Stop using fixed row number 2021-08-28 23:10:40 +02:00
ViSaturn 13cd20780e
spelling & grammar issues (#3410)
replace request to requsts & replace futur to future
2021-08-14 23:38:01 -07:00
Nguyễn Gia Phong be45b40cdb
Remove Vicious as async counterexample
Vicious has stopped using io.popen since October 2019.
2021-08-12 21:12:33 +07:00
aflorea-2k 8a81745d4d
doc(index.html): fixed broken links (#3402)
Fixes #3393, where links to guides were hard-coded, and pointed to wrong
places in the git version.
2021-08-04 11:07:57 -07:00
aflorea-2k 260f6dbe97
doc(wibox.widget.textbox.html): fixed pango links (#3401)
Links to gnome's pango webpage
(https://developer.gnome.org/pango/stable/PangoMarkupFormat.html) were
broken. They now all point to docs.gtk.org .
2021-08-03 20:02:25 +02:00
Emmanuel Lepage Vallée 4b494952da
Merge pull request #3400 from aflorea-2k/06-appearance.md_duplicates-fix
06 appearance.md duplicates fix (fixes #3328)
2021-08-02 18:13:35 -07:00
mergify[bot] fcd5918bd9
Merge pull request #3399 from sclu1034/feature/docs
Various doc improvements nr. 4
2021-08-03 01:12:11 +00:00
aflorea-2k ddb88ed945
List of values in table renders as HTML list
Co-authored-by: Aire-One <Aire-One@users.noreply.github.com>
2021-08-02 14:48:50 +02:00
Andrei Florea 5f5b1dcb2b
Removed dupicate computation of values 2021-08-02 14:46:20 +02:00
Andrei Florea b73b885c1e
06-appearance.md no longer mentions duplicates. (fixes #3328)
The table entries that were duplicated are now side by side.
There are no longer duplicates in the sample file.
2021-07-31 17:29:56 +02:00
Lucas Schwiderski 0a68341c12
doc(w.c.radialprogressbar): Show example code
Adds the code snippet for the example shown in the module description.

Signed-off-by: Lucas Schwiderski <lucas@lschwiderski.de>
2021-07-31 15:05:10 +02:00
Lucas Schwiderski c689af64d5
doc(w.w.textbox): Improve ellipsize documentation
The list of possible values was missing one. Additionally, this adds an
example to visualize the available options.

Signed-off-by: Lucas Schwiderski <lucas@lschwiderski.de>
2021-07-31 15:05:09 +02:00
Lucas Schwiderski 05ca439ed5
doc(mousegrabber): Improve cursor list
Utilize screen space better with a flex list, compared to a
single-column table.

Signed-off-by: Lucas Schwiderski <lucas@lschwiderski.de>
2021-07-31 15:04:54 +02:00
Andrei Florea 66bb09718b
docs/_parser.lua: parse_files() doesn't produce duplicate entries
anymore.

TODO: Handle duplicate's links.
2021-07-31 11:42:38 +02:00
Lucas Schwiderski 8fab5aa703
doc(w.w.imagebox): Improve various bits
Signed-off-by: Lucas Schwiderski <lucas@lschwiderski.de>
2021-07-14 23:17:49 +02:00
Lucas Schwiderski 4096c19223
doc(w.l.flex): Add module description
Signed-off-by: Lucas Schwiderski <lucas@lschwiderski.de>
2021-07-14 23:00:31 +02:00
Lucas Schwiderski f214ef16a7
doc(w.l.fixed): Improve docs for common layout methods
Signed-off-by: Lucas Schwiderski <lucas@lschwiderski.de>
2021-07-14 23:00:30 +02:00
Lucas Schwiderski 4b30158176
doc(w.l.fixed): Improve documentation for spacing properties
Signed-off-by: Lucas Schwiderski <lucas@lschwiderski.de>
2021-07-14 23:00:29 +02:00
Lucas Schwiderski f2c66b006d
doc(w.l.fixed): Add module description
Signed-off-by: Lucas Schwiderski <lucas@lschwiderski.de>
2021-07-13 22:44:50 +02:00
Lucas Schwiderski 08c893fff9
refactor(w.l.fixed): Fix line length
Signed-off-by: Lucas Schwiderski <lucas@lschwiderski.de>
2021-07-13 22:33:32 +02:00
Lucas Schwiderski a938a1b807
refactor(w.l.fixed): Rename abbreviated variables
Signed-off-by: Lucas Schwiderski <lucas@lschwiderski.de>
2021-07-13 22:30:52 +02:00
Lucas Schwiderski c1a3f02c88
doc(w.l.align): Add module description
Signed-off-by: Lucas Schwiderski <lucas@lschwiderski.de>
2021-07-13 20:30:22 +02:00
mergify[bot] 832483dd60
Merge pull request #3362 from actionless/prompt-multibyte-hack
Fix the hack for multibyte characters in prompt (fixes #3308)
2021-07-12 06:07:03 +00:00
basaran f473639c5d
Update focus.lua (#3381)
missing stacked option for global_bydirection
2021-07-06 01:56:34 -07:00
mergify[bot] d1c3394e40
Merge pull request #3375 from Aire-One/fix/3371
[Fix/3371] wibox.widget.base set opacity emit "widget::redraw_needed"
2021-06-28 05:15:05 +00:00
Uli Schlachter d25ca02493
Merge pull request #3377 from sclu1034/issue/fix-qa
Fix unused variable in example
2021-06-26 18:06:24 +02:00
Aire-One 53a880454f fix(w.widget): signal typo and unit test
This is a combination of 2 commits.
* fix(w.widget): emit signal typo
* add(w.widget.base_spec): Unit test for setters signals emittion
2021-06-26 17:26:21 +02:00
Lucas Schwiderski 741efd4171
bug: Fix unused variable in example
Signed-off-by: Lucas Schwiderski <lucas@lschwiderski.de>
2021-06-26 15:34:39 +02:00
Lucas Schwiderski ab6df1280f
bug(github_actions): Skip unreliable test
Signed-off-by: Lucas Schwiderski <lucas@lschwiderski.de>
2021-06-26 14:50:40 +02:00
Emmanuel Lepage Vallée 906dc543e4
Merge pull request #3370 from sclu1034/feature/docs
Improve documentation
2021-06-21 10:14:27 -07:00
Lucas Schwiderski 1a2037758b
doc(tag): Add test coverage
Signed-off-by: Lucas Schwiderski <lucas@lschwiderski.de>
2021-06-21 08:46:56 +02:00
Aire-One 4188d1df1e doc: Move inline usage to example files 2021-06-20 20:50:02 +02:00
Aire-One a3609146aa doc(awful.key) Improve module description 2021-06-20 20:50:02 +02:00
Lucas Schwiderski 4520f33309
doc(client): Improve signal wording
Signed-off-by: Lucas Schwiderski <lucas@lschwiderski.de>
2021-06-20 13:49:23 +02:00
Lucas Schwiderski fcae67cc03
doc: Move inline usage to example files
Signed-off-by: Lucas Schwiderski <lucas@lschwiderski.de>
2021-06-20 13:47:27 +02:00
Lucas Schwiderski dc0d5df4da
doc: Implement review suggetions
Co-authored-by: Aire-One <Aire-One@users.noreply.github.com>
2021-06-20 12:25:11 +02:00
Lucas Schwiderski e0e8f3fd72
doc: Improve hiding lines in examples
This implements variants of `--DOC_HIDE` that allow hiding any amount of
lines with just a start and end marker.

Signed-off-by: Lucas Schwiderski <lucas@lschwiderski.de>
2021-06-17 15:44:41 +02:00
Lucas Schwiderski bf4ad3310d
doc(client): Improve signal documentation
Signed-off-by: Lucas Schwiderski <lucas@lschwiderski.de>
2021-06-17 15:37:35 +02:00
Lucas Schwiderski fd59806392
doc(w.w.graph): Fix example titles
Signed-off-by: Lucas Schwiderski <lucas@lschwiderski.de>
2021-06-17 15:37:34 +02:00
Lucas Schwiderski 1cd89e7de5
doc(tag): Add example for request::default_layouts
Signed-off-by: Lucas Schwiderski <lucas@lschwiderski.de>
2021-06-17 15:37:33 +02:00
Lucas Schwiderski a6864a3e59
doc(tag): Reword signals documentation
Signed-off-by: Lucas Schwiderski <lucas@lschwiderski.de>
2021-06-17 15:37:32 +02:00
Lucas Schwiderski 7838e89d7f
doc(client): Fix incorrect documentation
Signed-off-by: Lucas Schwiderski <lucas@lschwiderski.de>
2021-06-17 15:37:31 +02:00
Lucas Schwiderski 7591d5cde3
doc(a.mouse): Fix static field documentation
Fixed incorrect field names and added missing parameter types.

Signed-off-by: Lucas Schwiderski <lucas@lschwiderski.de>
2021-06-17 15:37:30 +02:00
Lucas Schwiderski 3cefcfffe3
doc(g.timer): Improve documentation for helpers
Clarifies behaviour for the convenience constructor functions.

Signed-off-by: Lucas Schwiderski <lucas@lschwiderski.de>
2021-06-17 15:37:29 +02:00
Lucas Schwiderski 33b2fdfbf6
doc: Fix typos
Signed-off-by: Lucas Schwiderski <lucas@lschwiderski.de>
2021-06-17 15:37:28 +02:00
Lucas Schwiderski 16df93370f
doc(g.table): Improve documentation
Signed-off-by: Lucas Schwiderski <lucas@lschwiderski.de>
2021-06-17 15:37:27 +02:00
Lucas Schwiderski b94cb51770
doc(a.titlebar): Improve titlebar documentation
The constructor's documentation was worded as if it was a getter, which
lead to unexpected behaviour where subsequent calls for the same client
replaced previous titlebars.

Signed-off-by: Lucas Schwiderski <lucas@lschwiderski.de>
2021-06-17 15:37:27 +02:00
Aire-One 6b97ec3307 doc(ltp) Change author mention 2021-06-14 17:03:26 +02:00
mergify[bot] f4fb055512
Merge pull request #3363 from sclu1034/issue/keygrabber_timer
Stop timer when stopping keygrabber
2021-06-14 08:13:21 +00:00
Aire-One 8085a508d1 add(awful.key) keygroup enum style table
Replace the `awful.keygroup` property by an table that can be used
as an enum to point to the `awful.keygroups` definitions.

The previous field for `awful.keygroup` was empty (even no-existant),
and was here only to appear in the documentation as a definition for
the valid values to target keygroups.

With this new enum style table, the user can easily refere to keygroups
the same way we did for mouse buttons.
2021-06-12 16:39:24 +02:00
Aire-One 948506cde1 doc(awful.key) Declarative constructor parameters 2021-06-12 14:38:18 +02:00
Lucas Schwiderski 1182552783
bug(a.keygrabber): Stop timer when stopping keygrabber
When stopping a keygrabber with a timeout manually or through the stop
key, the timer would continue and call the stop callback again some time
later.

The error message in `gears.timer:stop` is removed, since there actually
is no harm in just returning immediately. And the timer implementation
itself calls `:stop` in certain places without checking for `.started`,
which lead to a situation where the internal call to `stop` triggered
the error message.

Signed-off-by: Lucas Schwiderski <lucas@lschwiderski.de>
2021-06-11 08:59:58 +02:00
actionless 87fb3d7553 test(spec: prompt): add for backspace, ^h, ^f, ^b and fix shim :wlen() implementation 2021-06-11 04:54:46 +02:00
actionless bbaccb05bc fix(prompt): handle multibyte character in Backspace, ^h, ^b and ^f 2021-06-11 03:20:32 +02:00
mergify[bot] b63399f656
Merge pull request #3356 from Elv13/fix_3351
fixed: Try to handle zero sized widgets again.
2021-06-04 21:25:59 +00:00
mergify[bot] 149d18e0e7
Merge pull request #3355 from Elv13/fix_3352
Fix the mouse button properties
2021-05-31 17:03:14 +00:00
Emmanuel Lepage Vallee 83c31f948b fixed: Try to handle zero sized widgets again.
The newly changed code doesn't handle this well:

    local w = wibox.widget {
        {
            --add anything here
            widget = wibox.layout.fixed.horizontal
        },
        widget = wibox.layout.fixed.horizontal,
    }

This will cause the "inner" fixed layout to have the minimum size
it supports. In that case, if the last widget has "no size" because
it supports up to 0x0, then it isn't added to the layout.

This was done "on purpose" because if there is a spacing, then `:fit`
would have returned a size "too small" because the last spacing area
would be (correctly) missing.

But if the zero sized widget isn't added to the layout, then it's size
isn't tracker. So if it emits a layout_changed signal, nothing catches
it.

The "fix" is rather hacky and probably a little incorrect. It rely
on the behavior of `:fit()` to avoid adding the "wrong" widgets to
the layout, which is fragile.

However, I don't have a better idea.
2021-05-31 02:27:36 -07:00
Emmanuel Lepage Vallee 82bd644ea1 tests: Regression test for #3352 2021-05-30 23:56:42 -07:00
Emmanuel Lepage Vallee 5e5f587bea mouse: Fix the "is button X pressed" properties.
Apparently it never worked.

Fix #3352
2021-05-30 23:56:42 -07:00
Emmanuel Lepage Vallée fedc7dc69d
Merge pull request #3226 from actionless/recompute-menubar-widths-again-lol
fix(menubar: init: get_current_page): list_spacing depends on presence of icon; also take cursor block width into consideration
2021-05-30 22:47:27 -07:00
actionless b91a033141 test(menubar): don't try to cover deprecated methods (calling any of them will make the warning-check fail in tests) 2021-05-28 00:40:24 +02:00
actionless fa7648e6d6 test(menubar): enable the test back on lua 5.3
and disable it back but only for specific lgi version
2021-05-28 00:39:43 +02:00
actionless 2249dc3c81 refactor(menubar, textbox): replace menubar.utils.compute_text_width() with textbox.get_markup_geometry()
feat(menubar): allow setting beautiful.menubar_font

fix(menubar: init: get_current_page): label return value

Update lib/wibox/widget/textbox.lua

Co-authored-by: Lucas Schwiderski <4508454+sclu1034@users.noreply.github.com>

Update lib/wibox/widget/textbox.lua

Co-authored-by: Lucas Schwiderski <4508454+sclu1034@users.noreply.github.com>

revert textbox changes

fix(menubar: init: show): fix font height detection

Update lib/menubar/init.lua

fix(menubar: init: get_current_page): list_spacing depends on presence of icon; also take cursor block width into consideration

fix(menubar: init: label): force icons to be square

fix(menubar: init: get_current_page): also take border_width into consideration when computing available space

merge conflict
2021-05-26 14:18:10 +02:00
Yauhen Kirylau b65025ef62
feat(awesome: main: check): print config path, and do it to stdout (#2901)
feat(awesome: main: check): combine two error messages together

feat(awesome: main: check): print newline before ERROR

chore(awesome: check): change message

Co-Authored-By: Uli Schlachter <psychon@users.noreply.github.com>

Co-authored-by: Uli Schlachter <psychon@users.noreply.github.com>
2021-05-23 21:55:04 -07:00
Yauhen Kirylau 59789bc2cf
test(spec: beautiful: get_font): convert user object to string before comparison (#3348) 2021-05-23 21:23:33 -07:00
mergify[bot] 4c8ac65d4c
Merge pull request #3347 from sclu1034/feature/docs
API doc improvements
2021-05-24 04:21:13 +00:00
Lucas Schwiderski 8c422d4f68
docs(faq): Add tip for missing .desktop file
Re-implements #3282.

Co-authored-by: HolySuicidalTurkey <63883308+HolySuicidalTurkey@users.noreply.github.com>
2021-05-22 15:55:58 +02:00
Lucas Schwiderski 1b49a20e0d
doc(awful.client): Document special signal semantics
While the documentation already specified the signal via `@emits`, it
did not make it clear that this signal is emitted on a `tag` object,
rather than the `client`.

Signed-off-by: Lucas Schwiderski <lucas@lschwiderski.de>
2021-05-22 01:24:07 +02:00
Lucas Schwiderski e0dea455c1
doc(client): Document special case for border color
Apparently transparent client borders only work when the border color is
set to `#00000000` specifically.

Signed-off-by: Lucas Schwiderski <lucas@lschwiderski.de>
2021-05-22 01:18:16 +02:00
Lucas Schwiderski 4f1b308e2b
doc(awful.client): Improve documentation for c:to_selected_tags
The behaviour of `c:to_selected_tags()` does not match what one would
have expected from its short description.
The behaviour also doesn't really match the method's name, but since
this is already in use, I won't change functionality or names here.

Instead this extends the method's documentation to accurately reflect
its implementation and also point users to the functionality that they
were likely looking for based on the method's name.

Signed-off-by: Lucas Schwiderski <lucas@lschwiderski.de>
2021-05-22 01:08:55 +02:00
Lucas Schwiderski 9a3ff8eb95
docs(w.l.fixed): Fix incorrect wording
Signed-off-by: Lucas Schwiderski <lucas@lschwiderski.de>
2021-05-21 19:54:49 +02:00
Lucas Schwiderski e0244e60da
docs(tutorial): Add missing theme
Signed-off-by: Lucas Schwiderski <lucas@lschwiderski.de>
2021-05-21 19:54:48 +02:00
mergify[bot] 9c5149d4e3
Merge pull request #3326 from albel727/graph-widget-overhaul
Graph widget overhaul
2021-05-20 16:24:55 +00:00
Alex Belykh 0d0647848b graph: complete widget overhaul and bug fixes
Squashed commit of the following:

commit 69821f51fe1e8652715658543e50631ece495090
Author: Alex Belykh <albel727@ngs.ru>
Date:   Mon May 10 19:17:55 2021 +0700

    Refactor property handling in wibox.widget.graph

    In addition, baseline_value, step_width and
    step_spacing properties are all set to their default
    non-nil numeric values during instance creation now.

commit 842eb429bf5df4b2ba46b2e6f7646afe89b1a3c3
Author: Alex Belykh <albel727@ngs.ru>
Date:   Sun May 9 06:18:45 2021 +0700

    Make graph:draw_values() private

    There's no documentation nor confidence that it's a good API,
    so I'm hiding it for now.

commit 720746780574e4bad14a71fc4174955139c2eb50
Author: Alex Belykh <albel727@ngs.ru>
Date:   Fri May 7 04:04:13 2021 +0700

    Document graph_should_draw_data_group() for posterity

commit 7c73f3754ce6ba86b118f1170a6f9e406ab4841d
Author: Alex Belykh <albel727@ngs.ru>
Date:   Fri May 7 03:59:21 2021 +0700

    Make graph:should_draw_data_group() private

    There's no confidence whether it's a good API,
    so I'm hiding it for now.

commit b3539e20adb6423118af726ae4f8751ef864b5d2
Author: Alex Belykh <albel727@ngs.ru>
Date:   Fri May 7 03:36:44 2021 +0700

    Make graph:preprocess_values() private

    There's no confidence whether it's a good API,
    so I'm hiding it for now.

commit 2df4400e780a6260f8d0ab1c60734fbb11bf3c4f
Author: Alex Belykh <albel727@ngs.ru>
Date:   Thu May 6 10:05:23 2021 +0700

    Add spec/wibox/widget/graph_spec.lua

commit ce01a9771ab439b8df841f1565aa4aa8a44a5e0d
Author: Alex Belykh <albel727@ngs.ru>
Date:   Wed May 5 08:16:18 2021 +0700

    Improve graph.capacity documentation

commit e28fade8f655802f5bf89154169f68d256b8e332
Author: Alex Belykh <albel727@ngs.ru>
Date:   Wed May 5 07:36:01 2021 +0700

    Document graph:compute_drawn_values_num()

commit dd68332292465422d08b48e9f0229f143beccbc6
Author: Alex Belykh <albel727@ngs.ru>
Date:   Wed May 5 05:24:23 2021 +0700

    Document graph:pick_data_group_color() method

commit ca6dc55f77aca88a6e9cc90bb6fd0045739f41f5
Author: Alex Belykh <albel727@ngs.ru>
Date:   Sun May 2 04:13:58 2021 +0700

    Guard against setting data groups sparsely in graph:add_value()

commit cdea5f126f7984e72924f6dffbd312c4fe3b032f
Author: Alex Belykh <albel727@ngs.ru>
Date:   Sun May 2 02:36:12 2021 +0700

    Refactor scaling and baseline choice out of graph:draw()

commit 10135deafe8a9390ba66ab657ebb1fb41d549e8c
Author: Alex Belykh <albel727@ngs.ru>
Date:   Sat May 1 21:34:48 2021 +0700

    Document graph.nan_color and graph.stack properties

commit 0d73bb84ac66b87e16aee472bd1f299549f66a1c
Author: Alex Belykh <albel727@ngs.ru>
Date:   Sat May 1 20:49:24 2021 +0700

    Use a better example in docs for the graph.stack property

commit c27d160a0826d033d49d6141608c40aa8a0642fa
Author: Alex Belykh <albel727@ngs.ru>
Date:   Sat May 1 16:05:38 2021 +0700

    Document graph.baseline_value property

commit 459bc8176c13d4ea32af0dcdc2399d3b929b1c6a
Author: Alex Belykh <albel727@ngs.ru>
Date:   Sat May 1 15:53:47 2021 +0700

    Document graph.clamp_bars property

commit e732ee0b3b007d213e689b98cc00ae7cb141ae08
Author: Alex Belykh <albel727@ngs.ru>
Date:   Sat May 1 15:28:34 2021 +0700

    Improve graph:add_value() and :clear() documentation

commit ae73e3aa9f8d60409cbe4cb4da9612f31af4f38e
Author: Alex Belykh <albel727@ngs.ru>
Date:   Sat May 1 15:28:11 2021 +0700

    Improve graph min_value/max_value/scale documentation

commit a7350996a9def773b91b1631e6c0507e33d8f93b
Author: Alex Belykh <albel727@ngs.ru>
Date:   Sat May 1 15:27:36 2021 +0700

    Cleanup graph examples to show only relevant things in docs

commit 30740c26f4f41c16f4c8a3c339bcb7bd299e14cd
Author: Alex Belykh <albel727@ngs.ru>
Date:   Sat May 1 14:04:36 2021 +0700

    Rename graph.stack_colors property to `group_colors`

    The colors are used for stacked and non-stacked graphs alike,
    so the name didn't make sense.

    Also document and clarify some color stuff.

commit 1ea01bfd410971cf0d0c25fd74cd764d5eb7279d
Author: Alex Belykh <albel727@ngs.ru>
Date:   Fri Apr 30 21:25:41 2021 +0700

    Add an example file for code coverage of some fringe graph cases

commit eae68e39553f88a6275a0c65bf040b5d4c262b54
Author: Alex Belykh <albel727@ngs.ru>
Date:   Fri Apr 30 20:23:32 2021 +0700

    Add step_shape to the graph/nan_color example for code coverage

commit 8027147bc2eab34a97b49a428fb8cef27f122bed
Author: Alex Belykh <albel727@ngs.ru>
Date:   Fri Apr 30 19:55:18 2021 +0700

    Add example files for graph.nan_color/nan_indication properties

commit 89a25a165c52b2c15b6c4d95ecb777a9067e66f9
Author: Alex Belykh <albel727@ngs.ru>
Date:   Fri Apr 30 19:33:07 2021 +0700

    Make widget graph:add_value(v) default v to NaN

    0 as a default makes no sense and silences the error
    that is failing to specify a value, to which the user
    should rather be alerted. The graph widget has sane
    NaN handling now and will do just that.

commit 2835d552440be48d356a7f7b1d64963696dd550b
Author: Alex Belykh <albel727@ngs.ru>
Date:   Fri Apr 30 19:20:48 2021 +0700

    Implement graph.nan_color and nan_indication properties

    Now, whenever there's a NaN among values and
    graph.nan_indication is set, the corresponding area,
    where the value bar should have been drawn, is filled
    with the nan_color from top to bottom.

    The painting is done after all data is rendered,
    to make sure that it won't be overpainted and go unnoticed.

    NaNs among graph's values are inevitably a sign of
    some error and it is arguably not a sane default behavior
    to simply not render them at all. The user should take
    immediate note of any errors by default, instead of
    wondering at a mysteriously empty graph widget, which
    is why nan_indication is set to true by default.
    If the user wants to silence NaNs, they can always simply
    set it to false.

    nan_color if unset, defaults to a noticeable yellow-black
    pattern.

    As a direct and planned consequence, stacked graphs
    with negative values in them trigger the NaN handling too.
    But silencing NaN handling for those graphs is also possible
    and semi-sane, because then NaNs and negatives are ignored
    and the graph behaves (almost) like they were zeros.

commit 9c1f8f08b400e01e19c534f3810c9c9ab485760c
Author: Alex Belykh <albel727@ngs.ru>
Date:   Mon Apr 26 08:10:19 2021 +0700

    Refactor out graph_map_value_to_widget_coordinates() function

commit 1905991ddecabdae3e15ffb8ee5b18a1bee1a711
Author: Alex Belykh <albel727@ngs.ru>
Date:   Wed Apr 28 13:39:29 2021 +0700

    Add an example file for graph.baseline_value property

commit b31b39d66f4c037a9484b71a3f910703ae01fac0
Author: Alex Belykh <albel727@ngs.ru>
Date:   Thu Apr 22 07:03:49 2021 +0700

    Add an example file for graph.clamp_bars=false/true comparison

commit a1fc850e3047f9ee2c9ddeffacfbabf3ded34171
Author: Alex Belykh <albel727@ngs.ru>
Date:   Thu Apr 22 04:06:45 2021 +0700

    Add an example file for graphs with negative values and shapes

commit 2f012f8ff1bb5094c14e724a61e59a2a3a296bc8
Author: Alex Belykh <albel727@ngs.ru>
Date:   Thu Apr 22 01:06:05 2021 +0700

    Add an example file for a graph displaying negative values

commit 7b14fbd59a4c4c51962fd3110a1a900dd5f4eb98
Author: Alex Belykh <albel727@ngs.ru>
Date:   Wed Apr 21 11:18:26 2021 +0700

    Add an example file for graphs with step_width < 1

commit dfd9a54544b637e2fbe3d1325f9cc24b3f4d2313
Author: Alex Belykh <albel727@ngs.ru>
Date:   Wed Apr 21 10:17:38 2021 +0700

    Add an example file for graph.stack=false/true comparison

    Should also demonstrate that multiple data groups
    are possible for non-stacked graphs too.

commit 5b532106c3fd255f9b66a66cfb88d977b489ab5d
Author: Alex Belykh <albel727@ngs.ru>
Date:   Wed Apr 21 09:34:14 2021 +0700

    Add an example file for disabling graph data groups with nil colors

commit ac6f1083c02fcb428f089a616f9b0ebaa5496b6c
Author: Alex Belykh <albel727@ngs.ru>
Date:   Wed Apr 21 08:57:25 2021 +0700

    Add an example file for step_shape/width/spacing on stacked graphs

commit b3703db6a5139e73e72c1cfd198ece2cd9be7df2
Author: Alex Belykh <albel727@ngs.ru>
Date:   Wed Apr 21 08:43:56 2021 +0700

    Add an example file for scale=false/true on stacked graph

commit b000aed4a12cb44fb501e1c8432a71a14b2f8f3f
Author: Alex Belykh <albel727@ngs.ru>
Date:   Tue Apr 20 08:22:19 2021 +0700

    Document deprecation of graph's width and height

commit cb0ad617bd6c83a499896797318e3b0b588c3566
Author: Alex Belykh <albel727@ngs.ru>
Date:   Mon Apr 19 07:53:37 2021 +0700

    Deprecate width and height properties of wibox.widget.graph

commit 8c64e14ad91a074b784166a7054e53444cccb9d2
Author: Alex Belykh <albel727@ngs.ru>
Date:   Mon Apr 19 04:37:59 2021 +0700

    Refactor graph:draw() to separate concerns

    Firstly, summation of group values for stacked graphs
    is extracted into graph:preprocess_values().

    This makes graph:draw() smaller and exposes the fact, that
    stacked graphs are almost entirely just a form of
    a more general concept: pre-render data manipulation.
    It's something a user might want to customize,
    and now they can do it by monkey-patching the method.

    Secondly, the logic for deciding, if and with which color
    a data group should be drawn, is extracted into
    graph:should_draw_data_group() and get_data_group_color()
    methods respectively.

    This makes it customizable too and makes more obvious
    the fact, that it is a general thing that has nothing
    to do with stacked graphs in particular.

    With this there are only 5 lines remaining in graph:draw(),
    that could be said to be specific to stacked graphs,
    all pertaining to the `prev_y` variable, which merely
    preserves the bar coordinates from a previous data group.

commit 2a32b6305d893f413303728a0a3da1dcd9a5591e
Author: Alex Belykh <albel727@ngs.ru>
Date:   Fri Apr 16 22:52:37 2021 +0700

    Pointlessly optimize graph:add_value()

    1) Purge old values first, then insert the new one, if needed.
    Since Lua grows arrays in power of two steps, and our
    guess_capacity() also takes care to return even integers,
    it would be a shame to overstep the capacity by one by
    inserting first and thus getting the table size doubled
    only to never use the most of it.

    2) Use a removal procedure that doesn't call
    the slow length operator at all. Previous code called it
    twice (once in table.remove) for every removed value.

commit 680ea8f5d81e9040afb4662b3229e937e4b89e02
Author: Alex Belykh <albel727@ngs.ru>
Date:   Fri Apr 16 16:23:38 2021 +0700

    Set graph data series color before the painting loop

commit b970f0f221ae76250f2a94433ff79d653caa6e21
Author: Alex Belykh <albel727@ngs.ru>
Date:   Fri Apr 16 16:05:37 2021 +0700

    Drop a large no longer necessary `if` in graph:draw_values()

    This commit is almost purely a whitespace change.

commit 6b4f0541c70ec99d7f6e276bae64939f337120d6
Author: Alex Belykh <albel727@ngs.ru>
Date:   Fri Apr 16 16:00:50 2021 +0700

    Extract values rendering code into graph:draw_values() method

    This is to avoid ungodly if nesting levels, which are needed
    to bail out of drawing values early and skip to drawing the border.

    The next commit will get rid of the if.

commit e0950e4f99d1ea1c4b8cea2cc67629fa40030c2a
Author: Alex Belykh <albel727@ngs.ru>
Date:   Fri Apr 16 14:50:20 2021 +0700

    Track graph draw() usage to guess necessary array capacity

    The last step of divorcing add_values() from width calculations,
    while ensuring that user doesn't observe a lack of data to draw.

    The widget now tracks an approximate number of values that
    it has been asked to draw() in _private.last_drawn_values_num,
    and truncates its values array based on that
    (if not overridden by the capacity property).

commit 2e3d542282f35e7c0fed454da0f2eada0fcf3b78
Author: Alex Belykh <albel727@ngs.ru>
Date:   Fri Apr 16 13:35:12 2021 +0700

    Add `capacity` property to graph widget

    This is the second step of divorcing add_values()
    from calculations involving widths.

    How many values are stored and how many values are drawn
    are distinct concepts. There isn't even a single definitive
    "how many values are drawn" because the widget could
    be rendered in several places with distinct widths simultaneously,
    and in every such place the data must be drawn appropriately,
    e.g. with auto-scaling, that takes only the visible data points
    into account. And the draw() method can do that now.

    The default capacity value of nil uses heuristics
    based on widget's _private.width to guess some capacity
    that would tolerate some dynamic widget resizing.

    If the user finds the heuristic failing them, they
    can set the capacity property to the desired maximum number
    of values that will be kept in the _private.values array
    (in each data group).

commit 08d4a80686f7fe363551c35589a460839f60853e
Author: Alex Belykh <albel727@ngs.ru>
Date:   Fri Apr 16 03:27:18 2021 +0700

    Extract values size computation into compute_drawn_values_num()

    This is the first step of making add_values() not
    concern itself (directly) with widget dimensions.

    The user can monkey-patch the method to override
    how many values are (logically) drawn, because
    it affects graph auto-scaling and can be useful for rendering
    of step_shape-s that paint outside of their step_width.

    I didn't make it a full-blown function-valued property,
    because it seems a fringe functionality,
    which I'm too lazy to document.

commit 3c5617c98ab1147618ed22e8414a77fc56b45f6e
Author: Alex Belykh <albel727@ngs.ru>
Date:   Fri Apr 16 02:32:29 2021 +0700

    Copy graph widget prototype methods over in bulk

commit 169538837ece41a03d02ba8fbc0e4097062b67a3
Author: Alex Belykh <albel727@ngs.ru>
Date:   Sun Apr 25 05:58:01 2021 +0700

    Remove graph.baseline_y property

    After giving it some thought I've concluded, that it is unlikely
    to be useful as it is now. I'd better reserve the name for
    some more sensible functionality, like fixing the baseline_value
    defined axis at the given widget coordinate, adjusting the scaling
    accordingly, so min_value/max_value won't generally correspond
    to widget top and bottom anymore.

commit 202ffaa9a0b820c7c471e1121d01b91f8f76cd38
Author: Alex Belykh <albel727@ngs.ru>
Date:   Thu Apr 15 12:06:55 2021 +0700

    Add baseline_y and baseline_value properties to graph widget

    Now values in graph have a *baseline*, tunable with
    baseline_y and baseline_value properties.

    It is the (for now invisible) horizontal line from which
    all bars in the graph grow. I call it a "baseline"
    instead of "axis", because it doesn't actually have to correspond
    to the 0 value. But it can, and actually does now by default.

    baseline_y and baseline_value properties determine
    the vertical placement of the baseline in the widget.

    baseline_y is the position of the baseline in normalized
    widget coordinates, e.g. 0 corresponds to widget's top,
    1 - to widget's bottom, and 0.5 - to widget's middle.
    So if e.g. one sets baseline_y = 0, then no matter how the widget
    is resized and how its values are scaled, bars in it will be drawn
    starting from the top.

    baseline_value is the position of the baseline in
    value coordinates.
    So if one sets baseline_value = 10, then no matter what happens,
    bars for values greater than 10 will be drawn as growing up from
    this line, and bars for values smaller as growing down.
    Where and if it will be seen in the widget, depends on the scaling.
    baseline_value = 0 is the familiar zero axis.

    baseline_y is unset by default and has priority, i.e.
    if it's set, then baseline_value is ignored.
    baseline_value defaults to 0.

    Prior to this commit, bars were always drawn from the bottom edge
    of the widget, or, in the new terms, the widget had baseline_y = 1.
    But it was indistinguishable from baseline_value = 0, because
    the widget clamped all negative values to 0, so the difference
    between the two could never be observed. baseline_value = 0 is
    clearly more useful and familiar to people, so now it is in force
    by default.

commit df7e19abaebf0a871b5ca217b4cc3365533267a6
Author: Alex Belykh <albel727@ngs.ru>
Date:   Thu Apr 15 11:18:12 2021 +0700

    Move value clamping from graph:add_value() into draw()

    Previously all values were forcibly clamped between
    0 (not tunable at all) and max_value (1 by default).

    Thus graphs with negative values were not an option.
    NaN values were conflated with zeros, even though
    it may be useful to maintain the distinction and render
    them differently. And if user happened to dynamically
    change the max_value property, they discovered,
    that their data got truncated.

    Preserving user data as it was given is more flexible and
    makes things easier to debug. This commit does exactly that.

    So what happens now to the newly allowed values during draw()?
    There are 3 new cases to consider:
    1) NaNs
    2) Negatives
    3) Values outside of min_value..max_value range

    NaNs are presently simply not drawn. An empty place is reserved
    where the value should've been, but nothing else is done.
    This is distinct from how a zero is handled,
    e.g. if step_shape is set, it is called with the height
    parameter = 0, which can still end up drawing something,
    depending on the shape.
    One could also consider addition of nan_color/nan_shape
    properties which could be drawn to alert user
    to a gap in their data.

    Negative values necessitate introduction of the concept of
    the zero axis, from which they will be drawn DOWN, in contrast to
    positive values. Where should this axis be drawn?
    How should step_shape-s with inherent direction, e.g. arrows,
    be drawn for negatives? This will be addressed in the next commit.

    Values outside of min/max_value range can imply shapes that
    go beyond widget dimensions. An arrow shape that goes beyond
    the roof is visibly different from an arrow that saturates and
    merely touches the widget top, and the user may reasonably desire
    either behavior. Therefore this commit introduces
    a boolean clamp_bars property, which defaults to true
    for reasons of robustness and backward compatibility with
    the behaviour prior to this commit. The property of course affects
    only the display of the values, the actual user data stays intact.

commit c9c24fb1ab741cd6bb8074971d568e0646c51532
Author: Alex Belykh <albel727@ngs.ru>
Date:   Thu Apr 15 09:29:01 2021 +0700

    Use rectangles for drawing graph bars of all sizes

    I have benchmarked various bar drawing strategies
    and graph sizes and found out that drawing graph bars
    with rectangles is as fast or even marginally faster
    than using fat lines.

    Thus the code that tries to special-case 1px bars
    as lines seems to be not worth the additional complexity
    it introduces, and this commit removes it.

    Incidentally, I've also established, that rectangles
    are more robust. If one draws graph bars and feeds
    cairo very large coordinates (~2^62), then corresponding
    rectangles fail to render, but all the other rectangles
    in the same series get rendered successfully,
    whereas a single large coordinate in a move_to/line_to()
    sequence is enough to cause all lines in the batch
    to fail to render.

commit bc7f4be5e06405b5aa7fb443a5ace2b9b9575b1f
Author: Alex Belykh <albel727@ngs.ru>
Date:   Thu Apr 15 09:01:42 2021 +0700

    Cache cairo methods used in the inner loop of graph:draw()

    I have benchmarked the draw() method, both
    on cairo xcb and on argb32 image surfaces, and have discovered
    that cairo context method calls are unnaturally slow,
    to the point, that almost nothing else in the function matters.

    For example, drawing bars with vertical lines made
    the whole draw() function almost 2 times slower
    than doing the same with rectangles, merely because
    the former requires two method calls (move_to/line_to),
    and the latter only one.

    Simply caching hotpath cairo methods in local variables,
    reduced the difference between the drawing methods
    to a negligible one and made the whole function
    4 times faster on a typical 100-bar graph,
    (1.4ms to 0.33ms on my laptop) thus bringing closer
    my secret dream of drawing live audio data with
    awesome graphs (kidding).

    I guess, LGI does something not very optimal there,
    like gobject-introspecting the method every time,
    or something. There might be some untapped optimization
    potential, possibly in upstream, like a method cache,
    from which everything in awesome could benefit,
    even though I imagine not many things are so
    call-intensive as the graph widget, to really feel
    the difference.

commit 538df25bdf02b53d0bbe81809eef2e6fdb05ff6f
Author: Alex Belykh <albel727@ngs.ru>
Date:   Thu Apr 15 07:32:37 2021 +0700

    Use 1-based index in the inner graph draw loop

    It's used there only once for a calculation and
    then several times to index some tables,
    and index 0 is bad for lua tables performance-wise.

commit 7412546916cd0a0a490130a120255ffb719398b8
Author: Alex Belykh <albel727@ngs.ru>
Date:   Tue Apr 13 21:55:12 2021 +0700

    Leave graph.max_value uninitialized at construction

    This is a user-visible change, but the previous behavior
    was probably not what user expected anyway, namely
    with graph.scale set the graph mysteriously couldn't
    zoom in onto values that are smaller than 1.

commit eb81d7929a161e79c13262cfa73ac2e6a820e8ed
Author: Alex Belykh <albel727@ngs.ru>
Date:   Tue Apr 13 21:48:29 2021 +0700

    Allow graph.max_value be nil

    This makes its behave symmetrically to min_value.
    Now it defaults to 1, when graph.scale is not set,
    and to the maximum value in the graph data, when graph.scale is set.

commit f622a8430cbfde7007b58d7f4c9c76b08c91e997
Author: Alex Belykh <albel727@ngs.ru>
Date:   Tue Apr 13 12:01:13 2021 +0700

    Calculate max values num in graph:add_value() more precisely

    At most so many values were kept in the widget values array,
    as there are pixels of usable widget width (i.e. sans border).

    This is exactly right for the surely most common case
    of graphs with 1px-wide bars, but is more than needed for
    graphs with step_width+step_spacing>1, and not enough for
    graphs with subpixel bars.

    Moreover this made graph autoscaling behave weirdly in
    graphs with thicker bars, because
    old values survived longer in the array and
    kept affecting min_value/max_value calculations
    even after they were shifted off-screen.

    The maximum number of kept values is now calculated exactly
    as the number of values that can be displayed at once,
    taking step_width and step_spacing into account.

    It might be worth adding a max_values_num property to the graph
    to let user override this calculation, in case they draw
    shapes in step_shape() outside of its supposed rectangle,
    so our assumptions of visibility are wrong, or
    the widget often gets resized, resulting in the annoyance of
    visibly truncated series.

commit 0aa63249a18641251a1bf77423739164f0eab39f
Author: Alex Belykh <albel727@ngs.ru>
Date:   Tue Apr 13 02:30:13 2021 +0700

    Fix border_color/border_width bugs in graph widget

    add_value() wrongly assumed that border_width is always set,
    when border_color is, so if user set only the latter,
    an error occurred.

    add_value() assumed that border_width is always < width
    which led to awesome hanging up in an infinite loop,
    should that fail to be the case.

    The graph border_width property was driven
    by the border_color property, instead of the other way round.

    If border_color wasn't set, the border wasn't drawn all,
    even if user sets non-zero border_width, which is
    a confusing behavior, in contrast to all other widgets,
    which simply use a plethora of fallback colors
    to draw the border, when needed.

    This also meant that, despite being documented,
    the beautiful.graph_border_color fallback color
    could not and was not ever used.

    If the border_color was set though, an unset
    border_width was immediately assumed to be 1 instead of 0,
    also contrary to what other widgets do.

    Theme colors shouldn't influence widget layout,
    so this commit fixes this behavior to:

    1) border_width always defaults to 0, if unset.
    2) border_color falls back to
       beautiful.graph_border_color or white.

    Maybe one should also add a beautiful.graph_border_width
    theme variable, like some widgets do.

commit dad4a3253b8fef971fb82720d2a407fcc0a879ad
Author: Alex Belykh <albel727@ngs.ru>
Date:   Wed Apr 14 03:11:27 2021 +0700

    Implement multiple data series drawing for non-stacked graph

    The widget can already hold and draw multiple data groups,
    so it makes little sense to limit drawing only to the first group
    when graph.stack = false, probably leading to user's confusion.

    Now user can call add_value(v, group) with group > 1 and
    the data series will be drawn (over groups with lesser id-s)
    with graph.stack_colors[group] color, if stack_colors is set, or
    with graph.color, if stack_colors isn't set.

    Thus nothing visibly changes for all current users
    of non-stacked graphs with only single data series,
    but the new functionality is now there for those who need it.

    One can't fully emulate this functionality, e.g. with
    multiple graphs widgets in a wibox.layout.stack, because
    data series in a single widget can enjoy a common auto-scaling.

commit 3e645c976184ab5f50d625271c25062db9ab7ed9
Author: Alex Belykh <albel727@ngs.ru>
Date:   Tue Apr 13 01:59:12 2021 +0700

    Don't draw a stacked graph data group if there's no color for it

    If stack_colors[idx] is nil for the group `idx`,
    don't draw it at all (as opposed, e.g. to the effect
    of drawing it with a transparent color, or as though
    it had all values == 0, which both produce different results).

    This code is still subtly different from prior behavior,
    because now user can have a non-contiguous stack_colors table
    and turn the display of any data group on and off at will,
    whereas the code two commits earlier would just stop
    rendering as soon as ipairs(stack_colors) does, i.e.
    no data series were drawn past the first nil hole.

    This commit only looks large, but is mostly a whitespace change
    due to nesting from a pair of wrapping ifs.

commit 8c0273786de1ade66bdd977699f98b588da9f487
Author: Alex Belykh <albel727@ngs.ru>
Date:   Tue Apr 13 01:32:42 2021 +0700

    Unify stacked and non-stacked graph rendering

    Now stacked graphs support everything the nonstacked do,
    like step_width ~= 1 and custom step_shape-s.

    Technical notes:

    This code no longer calls cr:stroke() for every single value
    in a stacked graph, but rather paints a whole series and
    then cr:stroke/fill()-s it at all once with its color,
    improving performance considerably.

    Additionally the graph no longer relies on the presence
    of the stack_colors property to render series.
    It draws all data that it has, and, if there's no corresponding
    color in stack_colors, falls back to graph.color.

    This will be changed in the next commit to
    the prior behavior of only drawing those series
    for which there's a color in the stack_colors table,
    but that would've made this commit's diff much larger due to
    required nesting ifs over large code chunks.

    This commit also takes care to properly handle NaNs
    and negative values.

    Those are currently (almost) guaranteed to not occur in
    the values array due to clipping in graph:add_value(),
    but I intend to eventually drop the clipping, because it
    does nothing that draw() really relies upon,
    and allowing unclipped and NaN values is more flexible and less
    surprising, e.g. when user dynamically toggles graph.scale.

    Eventually I intend to implement drawing negative values
    in non-stacked graphs and possibly even in the stacked ones.

commit 38c6589c51b2aa057d7c2f40182ecf32b5b2ff5e
Author: Alex Belykh <albel727@ngs.ru>
Date:   Fri Apr 16 10:04:15 2021 +0700

    Convert graph:draw() to use `self`

commit f21f1b2550426df1c4ea8abde079b1ccef84b394
Author: Alex Belykh <albel727@ngs.ru>
Date:   Mon Apr 12 12:27:26 2021 +0700

    Fix scaling for stacked graphs

    Stacked graphs have cumulative heights,
    values from all groups that end up in the same graph bar are
    effectively summed. Previous code didn't take that into account
    when calculating min/max_value for the purposes of rescaling, but
    simply examined every value separately.

    This code adds up all values up front and finds min/max among those,
    to determine proper scaling that would fit the graph exactly.

commit 1ce4c7bf63bdd6c99c195d668347bec49355e921
Author: Alex Belykh <albel727@ngs.ru>
Date:   Mon Apr 12 09:49:35 2021 +0700

    Draw shaped graph bars only up to the baseline

    Prior to this commit, every step_shape bar was,
    for some reason, drawn with height = full widget's height,
    regardless of the value it represents.

    So for the most bars their lower end was far outside the box
    and therefore clipped off. So e.g. one couldn't see the
    lower round edges of rounded rectangles.

    This commit fixes that by appropriately varying the height
    that is passed into step_shape(), so that lower edges of the bars
    always exactly touch the lower edge of the graph.

    If the user wants to restore the prior behavior, they can simply
    ignore the passed-in height in step_shape() and
    just always draw their shape with height = full widget height.

    But they couldn't replicate the effect of this commit in
    their rc.lua without essentially writing their own graph widget.

commit e25185b08da23745b5815f85f6e9e43222ed2294
Author: Alex Belykh <albel727@ngs.ru>
Date:   Mon Apr 12 08:42:54 2021 +0700

    Avoid incremental transform shifting during graph bar rendering

    Instead of letting addition errors and cognitive load accumulate
    simply use the same offset calculation that is used for lines and
    reset the transform back to the top-left corner after every drawn bar.

    It's conceptually simpler, and will be useful later for
    stacked graphs and more.

    It also fixes a discrepancy between plain and step_shape rendering.
    Prior to this commit, bar coloring occurred with a disturbed transform,
    which was centered at the last drawn bar, wherever it happened to be.
    This meant that nontrivial colors, like gradients and images,
    were arbitrarily offset when step_shape was used.

commit f3e98866d8583c35d0b557c09f529299edc9ba2c
Author: Alex Belykh <albel727@ngs.ru>
Date:   Mon Apr 12 06:45:29 2021 +0700

    Refactor out graph bar height expression into a variable

commit f54528d6892f5aaa4595917853c9e2130f743fdb
Author: Alex Belykh <albel727@ngs.ru>
Date:   Mon Apr 12 06:20:25 2021 +0700

    Draw graph bars with lines only when step_width == 1

    Before this commit graph bars of step_width < 1
    were drawn with lines of width 1, but that's a bit unfortunate,
    because it led to much overpainting, while still ending up
    with a graphically incorrect result.

    Let's draw such bars with rectangles and let cairo
    do things with subpixels, getting more graphically
    accurate result, while still keeping the common case
    of step_width == 1 (supposedly) fast-drawn with lines.

commit b6515dca67c18b78eb5657378131a62ab43d4cfc
Author: Alex Belykh <albel727@ngs.ru>
Date:   Mon Apr 12 05:54:07 2021 +0700

    Fix unsharp edges when drawing graph bars with rectangles

    The 0.5 offset is only helpful for 1-width strokes, not for fills.

commit 7f3db8f07e82f821bcd1dc418d66564f537609d6
Author: Alex Belykh <albel727@ngs.ru>
Date:   Mon Apr 12 05:21:01 2021 +0700

    Do coordinate transformations even if the value isn't drawn

    The graph presently doesn't draw values < 0, but there was
    a discrepancy between drawing with and without step_shape.

    The default line/rectangle drawing reserves an empty place
    where the skipped value should've been,
    but no such empty place is reserved, when step_shape is used.

    This commit fixes that.
    Skipped values always have their empty space now.

commit 7b731ebfe96af7a9dba9c57371da6761cf22c00c
Author: Alex Belykh <albel727@ngs.ru>
Date:   Mon Apr 12 05:03:30 2021 +0700

    Fix graph bar scaling when min_value is not 0

    Previous calculation lead to underscaling of values
    so that even value = max_value wasn't drawn to full height.

commit dca59fd8b7fff0e7e646f2dfc6bd92047028e5a8
Author: Alex Belykh <albel727@ngs.ru>
Date:   Mon Apr 12 04:18:40 2021 +0700

    Calculate the horizontal graph bar offset only when needed

commit 940ddf4b9825e9c3a91d276208c089d8c74e49db
Author: Alex Belykh <albel727@ngs.ru>
Date:   Mon Apr 12 04:17:23 2021 +0700

    Call cr:move_to() only when drawing the graph with lines

    It's redundant otherwise and might even interfere
    with user's drawing in step_shape().

commit b5d5cd10ca29fb29cd751faaa95137c2fe89ff63
Author: Alex Belykh <albel727@ngs.ru>
Date:   Mon Apr 12 04:04:06 2021 +0700

    Fix wrong horizontal offset calculations in widget.graph

    Indices in the loop are zero-based, so
    things like i-1 and i>1 aren't necessary and
    actually wouldn't have been correct even in a 1-based loop.

    step_shape = gears.shape.rectangle and
    step_shape = nil, which is supposed to default to rectangle
    according to docs, were drawn differently due
    to mismatched offset calculations, which led to problems
    especially noticeable when step_spacing is not zero, e.g:
    1) whole graph shifted left too much, drawn partially off-screen.
    2) whole graph shifted right too much,
       and the first bar placed closer to the rest than others.

commit dc369fa198281e000afc1bd17326f3a264adf7d1
Author: Alex Belykh <albel727@ngs.ru>
Date:   Mon Apr 12 02:09:21 2021 +0700

    Fix the "border_width affects graph bar width" bug

    Graph bars were drawn with lines of border_width thickness
    instead of 1, i.e. possibly too fat to be readable.

commit 1ba73378cf29ed27a6ecd18716394779ceb543d9
Author: Alex Belykh <albel727@ngs.ru>
Date:   Wed Apr 14 00:13:03 2021 +0700

    Simplify internal values representation in graph even more

    New values in graph._private.values were pushed to the end,
    and old values were removed from the beginning of the table.

    In draw() however newer values are drawn first,
    at the left edge of the graph, necessitating
    annoying idx = #values - idx conversions everywhere.

    In particular in stacked graph that means that
    values that are drawn together may have different indices.

    One has to shift the values array in add_value()
    either way, so one might as well insert
    new values at the beginning of the array
    and simplify the rest of the code.

    This doesn't seem like much yet, but it will prove
    to be much more convenient and less error-prone in
    what I'm going to do later.

commit f70523ee68dbd6b67050ace4b82903a9b22440c7
Author: Alex Belykh <albel727@ngs.ru>
Date:   Tue Apr 13 13:35:10 2021 +0700

    Simplify internal values representation in graph widget

    graph._private.values was either a table of values or
    a table of tables of values, depending on whether
    "stack" property is enabled, ... or a mix of both
    if user forgot to pass the second "group" parameter
    to graph:add_value().

    This resulted in pretty fragile and confusing behavior,
    e.g. leaving user to wonder, why they see nothing more
    after setting stack=true for a graph in a previously working
    rc.lua, and demanding a very careful use of
    add_value() from the user, or else some values or
    even entire stacks would mysteriously vanish
    and errors in draw() get triggered,
    sometimes after a period of seemingly normal functioning.

    Now `values` is always a table of tables and
    the group parameter in add_value() defaults to 1,
    thus making the non-stacked graph a trivial variant
    of the stacked graph.
2021-05-20 22:16:17 +07:00
Lucas Schwiderski 8d88f1fa52
feat(github_actions): Revert HOME back to /dev/null
Turns out this bit was never actually needed to fix the issue that they
were expected to fix.

Signed-off-by: Lucas Schwiderski <lucas@lschwiderski.de>
2021-05-08 22:45:29 +02:00
Lucas Schwiderski cfe7da8526
feat(github_actions): Simplify timeout calculation
Signed-off-by: Lucas Schwiderski <lucas@lschwiderski.de>
2021-05-08 22:10:06 +02:00
Lucas Schwiderski 4d83228f00
bug(github_actions): Fix date for apidoc examples.
Signed-off-by: Lucas Schwiderski <lucas@lschwiderski.de>
2021-05-08 22:10:05 +02:00
Lucas Schwiderski 1867ab057c
feat(github_actions): Use tag to pin xcb-errors
Signed-off-by: Lucas Schwiderski <lucas@lschwiderski.de>
2021-05-08 22:10:04 +02:00
Lucas Schwiderski dc8108eff7
feat(github_actions): Update Mergify rule
Signed-off-by: Lucas Schwiderski <lucas@lschwiderski.de>
2021-05-06 20:10:19 +02:00
Lucas Schwiderski bccaac9b2f
fix(github_actions): Skip failed boilerplate commit
This ports the change from #3317.

Signed-off-by: Lucas Schwiderski <lucas@lschwiderski.de>
2021-05-06 20:05:07 +02:00
Lucas Schwiderski 6a3713a090
feat(github_actions): Install lua-discount
Signed-off-by: Lucas Schwiderski <lucas@lschwiderski.de>
2021-05-06 20:05:05 +02:00
Lucas Schwiderski d9711c3f48
feat(github_actions): Improve test
Signed-off-by: Lucas Schwiderski <lucas@lschwiderski.de>
2021-05-06 20:05:04 +02:00
Lucas Schwiderski 857b7199d6
feat(github_actions): Improve readability
Signed-off-by: Lucas Schwiderski <lucas@lschwiderski.de>
2021-05-06 20:05:03 +02:00
Lucas Schwiderski e7239840f4
docs(github_actions): Remove Travis references
Signed-off-by: Lucas Schwiderski <lucas@lschwiderski.de>
2021-05-06 20:05:02 +02:00
Lucas Schwiderski 8334f9c1b1
feat(github_actions): Improve logs
Signed-off-by: Lucas Schwiderski <lucas@lschwiderski.de>
2021-05-06 20:05:01 +02:00
Lucas Schwiderski 717d09aa94
feat(github_actions): Move xcb-errors commit ID to envvar
Signed-off-by: Lucas Schwiderski <lucas@lschwiderski.de>
2021-05-06 20:05:00 +02:00
Lucas Schwiderski 645156b3c0
feat(github_actions): Consolidate apt cache directory
Signed-off-by: Lucas Schwiderski <lucas@lschwiderski.de>
2021-05-06 20:04:59 +02:00
Lucas Schwiderski 9bc8c28c90
chore(github_actions): Clean up variables and expressions
Signed-off-by: Lucas Schwiderski <lucas@lschwiderski.de>
2021-05-06 20:04:58 +02:00
Lucas Schwiderski 33ee8c09ba
fix(github_actions): Remove redundant job matrix definitions
Signed-off-by: Lucas Schwiderski <lucas@lschwiderski.de>
2021-05-06 20:04:57 +02:00
Lucas Schwiderski 87b717495f
fix(github_actions): Add modelines
Signed-off-by: Lucas Schwiderski <lucas@lschwiderski.de>
2021-05-06 20:04:56 +02:00
Lucas Schwiderski 4b37eb6b50
chore(github_actions): Remove Travis files
Signed-off-by: Lucas Schwiderski <lucas@lschwiderski.de>
2021-05-06 20:04:56 +02:00
Lucas Schwiderski f74a838506
feat(github_actions): Add workflow to update API docs
Signed-off-by: Lucas Schwiderski <lucas@lschwiderski.de>
2021-05-06 20:04:55 +02:00
Lucas Schwiderski 674ee00437
feat(github_actions): Unify Lua code coverage uploads
From testing it seems that uploading after individual tests has no
benefit over one upload at the end.

Signed-off-by: Lucas Schwiderski <lucas@lschwiderski.de>
2021-05-06 20:04:54 +02:00
Lucas Schwiderski 3fbcfc8553
feat(github_actions): Drop support for old LGI version
Signed-off-by: Lucas Schwiderski <lucas@lschwiderski.de>
2021-05-06 20:04:53 +02:00
Lucas Schwiderski 5c3b739c25
fix(github_actions): Fix clients lingering into subsequent tests
On GitHub Actions, clients created in some tests take too long to close
and still exist when the next test has started. This can affect certain
tests that rely on a specific client count or simply produce a race
condition within `scan` in `awesome.c`.

Signed-off-by: Lucas Schwiderski <lucas@lschwiderski.de>
2021-05-06 20:04:48 +02:00
Lucas Schwiderski 8f39fb61bd
feat(github_actions): Add initial workflow
Signed-off-by: Lucas Schwiderski <lucas@lschwiderski.de>
2021-05-06 20:01:21 +02:00
mergify[bot] a4572b9b52
Merge pull request #3238 from ShayAgros/master
Fixed wrong handling of negative spacing in layout.fixed
2021-04-29 06:45:50 +00:00
mergify[bot] 4c46f6dbf3
Merge pull request #3335 from Elv13/imagebox
Extend wibox.widget.imagebox to encompass the required wallpaper API features
2021-04-28 18:39:03 +00:00
Emmanuel Lepage Vallee c355ce7d96 tests: Test the imagebox 2021-04-28 00:14:17 -07:00
Emmanuel Lepage Vallee 5cfcbac959 imagebox: Extend to add enough knobs for the wallpaper API.
* horizontal/vertical scaling/stretch
 * max scaling factor
 * vertical/horizontal align instead of hardcoded top-left
 * scaling_quality, if Cairo cooperates

It also fixes the clip_shape when resize = false.
2021-04-28 00:06:08 -07:00
mergify[bot] 42d241c707
Merge pull request #3332 from Elv13/tile_container2
Tile container
2021-04-26 11:37:20 +00:00
mergify[bot] 35e0acbccb
Merge pull request #3185 from actionless/hotkeys-popup-better-split-key-labels
Hotkeys popup: better split key labels for multiple keys per one action
2021-04-26 09:15:58 +00:00
Uli Schlachter d2dc428e56
Fix _NET_CURRENT_DESKTOP ClientMessage handling (#3237)
This was broken in commit c5202a4870 by adding an extra string
to the Lua stack. Hence, the offset -1 now referred to the string and
not the tag.

Fixes: https://github.com/awesomeWM/awesome/issues/3236
Signed-off-by: Uli Schlachter <psychon@znc.in>
2021-04-25 15:33:39 -07:00
actionless 5bd0ff82a6 fix(wibox: textbox): remove ldoc tags added by merge mistake 2021-04-25 20:55:44 +02:00
Emmanuel Lepage Vallee ee0d793efe doc: Document the place container. 2021-04-24 23:28:18 -07:00
Emmanuel Lepage Vallee dd26b8180d doc: Document the tile container. 2021-04-24 23:28:18 -07:00
Emmanuel Lepage Vallee 738e2e0467 container: Add a tile container.
It will be used for the new awful.wallpaper module. The idea is to
first close the gap with `gears.wallpaper` before it gets deprecated.
2021-04-24 23:28:18 -07:00
Emmanuel Lepage Vallee d373bf2d05 widget: Emit "property::widget"
For consistency.
2021-04-24 23:28:18 -07:00
Emmanuel Lepage Vallee 78b8756068 place: Split internal logic to be reusable.
This will become the base class of a new "tile" container.

I also fixed a bug which could cause the output to be blurry for
some widget sizes.
2021-04-24 23:28:18 -07:00
Emmanuel Lepage Vallee 6a4e555ae1 slider: Fix an aliasing issue.
There was a bunch of `math.floor` missing. Some slider size were blurry.
2021-04-24 23:28:18 -07:00
mergify[bot] c8d3de3ff4
Merge pull request #3325 from ShayAgros/fail_lua_54
Fail awesome compilation for Lua5.4 or newer (#3324)
2021-04-23 07:00:28 +00:00
actionless 6b93661048 fix(awful: hotkeys_popup: insert_keys): handle case when user actually binded some key like `<` or `>` which require xml escape for pango 2021-04-23 07:52:17 +02:00
Yauhen Kirylau a35acea61a
Merge branch 'master' into hotkeys-popup-better-split-key-labels 2021-04-23 07:23:26 +02:00
Emmanuel Lepage Vallée 47fd7797b4
Update luaa.h
Co-authored-by: ShayAgros <37025016+ShayAgros@users.noreply.github.com>
2021-04-21 22:52:35 -07:00
ShayAgros ba923cb80d
Remove word duplication
Co-authored-by: Lucas Schwiderski <4508454+sclu1034@users.noreply.github.com>
2021-04-21 10:49:44 +03:00
Shay Agroskin 1916677df1 List what Lua versions supported instead of which aren't
When skimming over the source code, it might be easier for coders to see
what lua versions are supported, instead aren't. This doesn't have any
functional impact

Signed-off-by: Shay Agroskin <agrosshay@gmail.com>
2021-04-21 10:04:18 +03:00
Shay Agroskin 3f4b1bd0cc Fail awesome compilation for Lua5.4 or newer (#3324)
Awesome doesn't support these versions. Preventing the project from
compiling with it saves the users some very frustrating and hard to
debug bugs.

The cmake version check is redundant with the version check in luaa.h.
Nevertheless, adding it allows to fail the project build sooner and
provides better user experience.

Fixes: #3324

Signed-off-by: Shay Agroskin <agrosshay@gmail.com>
2021-04-21 10:04:18 +03:00
mergify[bot] fda950d186
Merge pull request #3263 from Aire-One/doc_textclock
[doc] Improve wibox.widget.textclock format documentation
2021-04-07 09:59:08 +00:00
Emmanuel Lepage Vallee e3ab76b872 debug: Print *why* the build fails. 2021-04-07 01:38:53 -07:00
Aire-One 6226742f72 doc(wibox.widget.textclock): Widget definition. 2021-04-07 01:38:53 -07:00
Aire-One 9807ccd5e0
fix(ldoc.ltp): extra-header tag arrangement (#3318) 2021-04-05 09:42:15 -07:00
Emmanuel Lepage Vallée 022be39a3f
doc: Revert the last few commits. (#3317)
Hopefully, if the boilerplate commit is empty, the script will not
crash.
2021-04-05 01:11:05 -07:00
Emmanuel Lepage Vallée 702db23ee0
Debug doc issue (#3316)
* doc: More debug messages

The previous commit was inconclusive

REVERT ME

* doc: Another attempt at narrowing the boilerplate commit issue
2021-04-05 00:56:03 -07:00
Emmanuel Lepage Vallée 8e8525dae9
doc: More debug messages (#3315)
The previous commit was inconclusive

REVERT ME
2021-04-05 00:16:26 -07:00
Emmanuel Lepage Vallée 747b14142f
doc: Debug an issue where the documentation isn't generated properly (#3314)
This is a test commit, it will be reverted partially or fully.
2021-04-04 23:27:40 -07:00
Emmanuel Lepage Vallée 3dddc2ba78
Merge pull request #3288 from Aire-One/feature/ldoc-inheritance-diagram
[doc] Add ldoc @supermodule tag to draw inheritance diagram and manage inherited properties
2021-04-04 22:26:55 -07:00
Aire-One b184f95195 doc(layout.flex): Hide `fill_space` property doc
Add an explicite `@property` tag to the `wibox.layout.flex` doc
comments to override the `fill_space` property from
`wibox.layout.fixed` and mark it as hidden thanks to `@hidden`.
2021-04-02 20:02:39 +02:00
Emmanuel Lepage Vallee 25f4cfcb05 doc(ltp): Implement module `extra-header` section
Co-authored-by: Lucas Schwiderski <lucas@lschwiderski.de>
Co-authored-by: Aire-One <Aire-One@users.noreply.github.com>
2021-04-02 20:00:46 +02:00
Emmanuel Lepage Vallee 90c4c60d2a Add more @supermodule and @hidden 2021-04-02 19:24:07 +02:00
Aire-One bc8a5b0230 fix(ldoc.ltp): item.inherited can be wrongly true
For some even more strange reasons than commit 047729ae1, it seems
we can also have the error that `item.inherited` can be sets to `true`
on items that are not inherited...

I just had this issue with `wibox.widget.imagebox` where properties
were marked as inherited from `wibox.widget.imagebox`. Best way to fix
it is to only trust the check on `item.baseclass`, and completly
dismiss its `inherited` property.
2021-04-02 19:24:07 +02:00
Aire-One 0ae0add45f doc(imagebox): Fix @method tags 2021-04-02 19:24:07 +02:00
Aire-One b706aa9bd0 doc(@hidden): Add to wibox.widget.base module 2021-04-02 19:24:07 +02:00
Aire-One af3b194a31 doc(@hidden;ldoc.ltp): Add ldoc tag to hide item
Add a new ldoc tag `@hidden`. This tag allows us to keep documentation
for magic methods (e.g. `wibox.widget.base:get_visible`) but prevent
it from being part of the documentation used by final users.
2021-04-02 19:24:07 +02:00
Aire-One 20a8d902c5 doc(@supermodule): Add to awful.widget modules 2021-04-02 19:24:07 +02:00
Aire-One 3969ad8819 doc(@supermodule): Add to wibox.layout modules 2021-04-02 19:24:07 +02:00
Aire-One 3b12a8d19a doc(@supermodule): Add to wibox.container modules 2021-04-02 19:24:07 +02:00
Aire-One 3ed2fc8500 doc(@supermodule): Add to wibox.widget modules 2021-04-02 19:24:07 +02:00
Aire-One 5baa1c97cd fix(ldoc.ltp): Identify inherited items by name 2021-04-02 19:24:07 +02:00
Aire-One 93e9361280 fix(ldoc.ltp): item.inherited can be wrongly false
For some reasons, sometime `item.inherited` is `false` even if the
item was added to the `all_module_kinds` table by the "hierarchy
lookup" for-loop, and we already force the `inherited` property to be
sets to `true` at this moment.

With this commit, we add a second fail-check condition based on the
`item.baseclass` property to determine if the item is inherited or not
when we do the render.
2021-04-02 19:24:07 +02:00
Aire-One 12662d4cb1 doc(@supermodule): wibox.widget.base doc 2021-04-02 19:24:07 +02:00
Aire-One 3a6fa10754 doc(@supermodule): gears.object inheritable doc 2021-04-02 19:24:07 +02:00
Aire-One b82d2a690f doc(@supermodule;ldoc.ltp): Find inherited members
This commit uses the `@supermodule` tag to recursively find all the
properties from supermodules and add them to the current module
documentation.
2021-04-02 19:24:07 +02:00
Aire-One 4dd689f181 doc(@supermodule;ldoc.ltp): Draw hierarchy tree
This commit adds a new ldoc custom tag `@supermodule`. It has to be
used at the module level. It should refer to the module
supermodules.

This tag can be used multiple time by the same module, but we ignore
other calls (for now?) as (AFAIK) we only use one way inheritance.

This tag is used in the ldoc template to find modules hierarchy and
draw the inheritance tree. It makes it easy to find and navigate to
parents modules.
2021-04-02 19:24:07 +02:00
mergify[bot] 7a8fa9d27a
Merge pull request #3294 from sclu1034/feature/count_keys
Add utility to count table keys
2021-04-01 07:42:25 +00:00
mergify[bot] 972a194b01
Merge pull request #3307 from sclu1034/feature/docs
Various docs improvements
2021-04-01 07:42:11 +00:00
Lucas Schwiderski 112dc8054e
docs(tutorial): Fix line wrap
Signed-off-by: Lucas Schwiderski <lucas@lschwiderski.de>
2021-03-31 08:29:40 +02:00
Lucas Schwiderski 8907f5bfbb
docs(tutorial): Improve instructions to edit theme
Fixes #3306.

Signed-off-by: Lucas Schwiderski <lucas@lschwiderski.de>
2021-03-31 08:29:39 +02:00
Lucas Schwiderski f68939bfc1
chore(g.table): Add missing modeline 2021-03-31 08:29:00 +02:00
Lucas Schwiderski ec3788bf73
docs(g.table): Improve parameter descriptions 2021-03-31 08:27:08 +02:00
mergify[bot] 95558ac919
Merge pull request #3297 from actionless/dont-crash-when-gtk4-installed
fix(beautiful: gtk): don't crash when both gtk3 and gtk4 are installed
2021-03-29 15:49:37 +00:00
Emmanuel Lepage Vallée 13e8408562
Merge pull request #3284 from sclu1034/issue/3213
Fix composite widgets with top level container
2021-03-28 16:00:16 -07:00
Lucas Schwiderski c0380e3080
Remove nonexistent property from example (#3302)
See
<https://www.reddit.com/r/awesomewm/comments/lzgecn/what_intrusive_true_means_in_the_properties_of/>.

Signed-off-by: Lucas Schwiderski <lucas@lschwiderski.de>
2021-03-28 15:36:06 -07:00
Emmanuel Lepage Vallée 7050ba083f
Merge pull request #3301 from Elv13/graph_doc
Graph doc
2021-03-28 15:34:01 -07:00
Emmanuel Lepage Vallee 743b327348 Revert "Another ugly hack to get Travis to show output"
This reverts commit 97a60818c4.
2021-03-28 02:38:51 -07:00
Emmanuel Lepage Vallee a5c4377901 Revert "I want to see the error message!!!"
This reverts commit 1792780cf3.
2021-03-28 02:38:43 -07:00
Emmanuel Lepage Vallee c48e138b01 doc: Add examples for the graph. 2021-03-28 02:37:25 -07:00
Lucas Schwiderski e7d55567a6
Remove redundant assignment
Signed-off-by: Lucas Schwiderski <lucas@lschwiderski.de>
2021-03-27 14:08:47 +01:00
Shay Agroskin 62850476d2 layout/fixed: Prevent overloading widgets with negative spacing
For each widget, the layout function checks whether placing it would
make the function exceed the allowed geometry.
If not, the function places both the widget and a spacing widget.
This check ignores the size of the spacing widget itself, this can cause
overloading of widgets on top of each other.

For example, the following scenario with these widgets:
    widgets: widget1 { width = 10, height = 10  }
	     widget2 { width = 10, height = 10  }
	     widget3 { width = 10, height = 10  }
and a call to horizontal layout with the
{ width = 10, height = 10, spacing = -5 } parameters.

The function would layout the widgets the following way:
{
    widget1: { x = 0, y = 0, width = 10, height = 10 }
    spacing: { x = 5, y = 0, width = 5, height = 10  }
    widget2: { x = 5, y = 0, width = 5, height = 10  }
    spacing: { x = 5, y = 0, width = 5, height = 10 }
    widget3: { x = 5, y = 0, width = 5, height = 10  }
}

This behaviour would be the same for any number of widgets for negative
layout.

This patch changes the layout function to check whether the current
widget uses up the whole space.
It also removes 'pos' variable. Its purpose isn't intuitive in the
presence of x and y. This helps to understand where each widget is
placed now that x, y don't hold the end location of the widget in the
previous loop iteration.

The result of the previous example becomes:
{
    widget1: { x = 0, y = 0, width = 10, height = 10 }
}

While this might not be the wanted behaviour exactly, distinguishing
between the scenario where 2 widgets are drawn and a scenario where 3
are drawn might complicate the layout function too much.

This patch also adds unit testing that catches the described behaviour.

Signed-off-by: Shay Agroskin <agrosshay@gmail.com>
2021-03-26 16:11:01 +03:00
Shay Agroskin a18e3508f6 layout/fixed: Fix wrong space calculation in fit
The fit function is called twice in row.

- The first time it gets the maximum available width, and returns how
  much of it it needs (with 0 spacing it would be 477)

- The second time the available width it gets is the same as it returned
  last phase (and probably is expected to return the same result again)

The width fit requests is the total width of all widgets together + the
spacing (e.g. if each tag widget is 53 px and spacing is -10 then the
requested width 53 * 9 - 80).

The function tries to first fit all its widgets (the tag numbers) in the
amount of width it received, and only then adds the spacing to it. This
is problematic because in the second phase the widgets need to fit
themselves in the same width they requested earlier minus the spacing
(in case of negative spacing). This is of course impossible and so some
widgets are just not being drawn correctly.

This patch makes fit function take into account the spacing while
placing the widgets and not afterwards.

Also add unit-testing that test the bug described.

Signed-off-by: Shay Agroskin <agrosshay@gmail.com>
2021-03-26 16:11:01 +03:00
Shay Agroskin 749422100e layout/fixed: Remove code duplication
The function has several expressions of the form
    if self._private.dir == "y" then
This patch stores the result of
    self._private.dir == "y"
to avoid code duplication.

Also remove the 'used_in_dir' and 'in_dir' variables since their values
can be calculated using other variables in the function and updating
them individually is error prone.

This patch doesn't do any functional changes.

Signed-off-by: Shay Agroskin <agrosshay@gmail.com>
2021-03-26 16:11:01 +03:00
actionless e833da0303 fix(beautiful: gtk): don't crash when both gtk3 and gtk4 are installed 2021-03-25 00:21:04 +01:00
Lucas Schwiderski afced71a9a
Add example for gears.table.count_keys
Signed-off-by: Lucas Schwiderski <lucas@lschwiderski.de>
2021-03-22 20:55:40 +01:00
Lucas Schwiderski 058190a3c0
Add missing modelines
Signed-off-by: Lucas Schwiderski <lucas@lschwiderski.de>
2021-03-22 20:24:56 +01:00
Lucas Schwiderski 07df24f7d0
Add utility to count table keys
See #3293.

Signed-off-by: Lucas Schwiderski <lucas@lschwiderski.de>
2021-03-22 20:24:55 +01:00
Lucas Schwiderski 504bf53b8c
Add unit test for wrapping a margin container
This adds a test case where a `wibox.container.margin` with a
`wibox.widget.imagebox` as child is wrapped by a simple function call.

Check against regression in #3213.

Signed-off-by: Lucas Schwiderski <lucas@lschwiderski.de>
2021-03-22 09:21:23 +01:00
Lucas Schwiderski 7bc3ec4c35
Fix composite widgets with top level container
When wrapping container widgets to create reusable composite widgets,
`drill` will be called twice on the same widget definition. The first
call happens within the wrapping function and applies the children
widgets fine. The second call happens when the composite widget is used,
but since there are no children widgets defined, the call to
`set_children` sets the existing child to `nil` instead.

Fixes #3213.

Signed-off-by: Lucas Schwiderski <lucas@lschwiderski.de>
2021-03-11 12:32:28 +01:00
actionless a2674c2d14 feat(awful: hotkeys_poup): better visually split multiple keys 2020-09-26 04:43:12 +02:00
actionless 20a79ed448 fix(awful: hotkeys_popup: widget: create_column): correct max label width detection 2020-09-04 23:54:57 +02:00
actionless 901bb3d88e fix(awful: hotkeys_popup: widget: create_group_columns): correct max label height detection 2020-09-04 23:54:57 +02:00
271 changed files with 12019 additions and 7482 deletions

View File

@ -1,6 +1,6 @@
#!/usr/bin/env bash
#
# Process (API) docs after a successful build on Travis (via ../.travis.yml).
# Process (API) docs after a successful build on GitHub Actions.
#
# Updated/changed documentation for "master" is pushed to gh-pages.
# In case of pull requests or other branches, it will get added to a separate branch.
@ -9,48 +9,49 @@
# NOTE: stdout/stderr might/should be discarded to not leak sensitive information.
echo "Post-processing (API) documentation."
echo "TRAVIS_PULL_REQUEST: $TRAVIS_PULL_REQUEST"
echo "TRAVIS_BRANCH: $TRAVIS_BRANCH"
echo "PR Number: $PR_NUMBER"
echo "GITHUB_HEAD_REF: $GITHUB_HEAD_REF"
if [ -z "$GH_APIDOC_TOKEN" ]; then
echo "No GH_APIDOC_TOKEN available. Skipping."
exit
if [ -z "$APIDOC_TOKEN" ]; then
echo "No APIDOC_TOKEN available. Skipping."
exit
fi
# NOTE: DO NOT USE "set -x", or anything else that would reveal GH_APIDOC_TOKEN!
# NOTE: DO NOT USE "set -x", or anything else that would reveal APIDOC_TOKEN!
# GitHub Actions does filter secrets, but extra caution won't hurt
set -e
set +x
# Display exit code in term of failure (probably due to 'set -x').
# Display exit code in case of failure (probably due to 'set -x').
trap '[ "$?" = 0 ] || echo "EXIT CODE: $?"' EXIT
REPO_APIDOC="https://${GH_APIDOC_TOKEN}@github.com/awesomeWM/apidoc"
REPO_APIDOC="https://${APIDOC_TOKEN}@github.com/awesomeWM/apidoc"
REPO_DIR="$PWD"
export GIT_AUTHOR_NAME="awesome-robot on Travis CI"
export GIT_AUTHOR_NAME="awesome-robot on GitHub Actions"
export GIT_AUTHOR_EMAIL="awesome-robot@users.noreply.github.com"
export GIT_COMMITTER_NAME="$GIT_AUTHOR_NAME"
export GIT_COMMITTER_EMAIL="$GIT_AUTHOR_EMAIL"
git clone --depth 1 --branch gh-pages "$REPO_APIDOC" build/apidoc \
2>&1 | sed "s/$GH_APIDOC_TOKEN/GH_APIDOC_TOKEN/g"
2>&1 | sed "s/$APIDOC_TOKEN/APIDOC_TOKEN/g"
cd build/apidoc
# This will re-use already existing branches (updated PR).
if [ "$TRAVIS_PULL_REQUEST" != false ]; then
BRANCH="pr-$TRAVIS_PULL_REQUEST"
elif [ "$TRAVIS_BRANCH" != master ]; then
# Use merge-base of master in branch name, to keep different branches with
# the same name apart.
# shellcheck disable=SC2015
BRANCH="$TRAVIS_BRANCH-$(cd "$REPO_DIR" \
&& git fetch --unshallow origin master \
&& git rev-parse --short "$(git merge-base HEAD FETCH_HEAD || true)" || true)"
if [ "$PR_NUMBER" != false ]; then
BRANCH="pr-$PR_NUMBER"
elif [ "$GITHUB_HEAD_REF" != master ]; then
# Use merge-base of master in branch name, to keep different branches with
# the same name apart.
# shellcheck disable=SC2015
BRANCH="$GITHUB_HEAD_REF-$(cd "$REPO_DIR" \
&& git fetch --unshallow origin master \
&& git rev-parse --short "$(git merge-base HEAD FETCH_HEAD || true)" || true)"
else
BRANCH="gh-pages"
fi
if [ "$BRANCH" != "gh-pages" ]; then
git checkout -b "$BRANCH" "origin/${BRANCH}" || git checkout -b "$BRANCH"
git checkout -b "$BRANCH" "origin/${BRANCH}" 2> /dev/null || git checkout -b "$BRANCH"
fi
# Use a temporary branch for the two commits, which allows for a better UI.
@ -62,7 +63,7 @@ cat > ../doc/README.md <<END
This repository contains the built API documentation for the
[awesome](https://github.com/awesomeWM/awesome) window manager. It is
automatically updated via Travis when the master branch changes. Hence:
automatically updated via GitHub Actions when the master branch changes. Hence:
## Do NOT send pull requests here
@ -72,47 +73,48 @@ END
# Create a patch without irrelevant changes (version / timestamp).
diff -Nur . ../doc -I "Last updated" -I "<strong>Release</strong>:" \
-I "<h2>API documentation for awesome, a highly configurable X window manager (version .*)\.</h2>" \
-x .git | patch -p1
-I "<h2>API documentation for awesome, a highly configurable X window manager (version .*)\.</h2>" \
-x .git | patch -p1
git add --all .
if git diff --cached --exit-code --quiet; then
echo "Documentation has not changed."
exit
echo "Documentation has not changed."
exit
fi
LAST_COMMIT_MSG="$(cd "$REPO_DIR" && git log -1 --pretty=format:%s)"
LAST_COMMIT="$(cd "$REPO_DIR" && git rev-parse --short HEAD)"
# Commit the relevant changes.
COMMIT_MSG="Update docs for $AWESOME_VERSION via Travis
COMMIT_MSG="Update docs for $AWESOME_VERSION via Github Actions
Last commit message:
$LAST_COMMIT_MSG
Commits: https://github.com/awesomeWM/awesome/compare/${TRAVIS_COMMIT_RANGE/.../..}
Build URL: https://travis-ci.com/awesomeWM/awesome/builds/${TRAVIS_BUILD_ID}"
Commits: https://github.com/awesomeWM/awesome/compare/${GITHUB_BASE_REF}..${GITHUB_HEAD_REF}
Build URL: $GITHUB_SERVER_URL/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID"
git commit -m "[relevant] $COMMIT_MSG"
# Commit the irrelevant changes.
mv .git ../doc
cd ../doc
git add --all .
git commit -m "[boilerplate] $COMMIT_MSG"
# Reorder/swap commits, to have "relevant" after "boilerplate".
# This makes it show up earlier in the Github interface etc.
git tag _old
git reset --hard HEAD~2
git cherry-pick _old _old~1
RELEVANT_REV="$(git rev-parse --short HEAD)"
git tag -d _old
if git commit -m "[boilerplate] $COMMIT_MSG"; then
# Reorder/swap commits, to have "relevant" after "boilerplate".
# This makes it show up earlier in the Github interface etc.
git tag _old
git reset --hard HEAD~2
git cherry-pick _old _old~1
RELEVANT_REV="$(git rev-parse --short HEAD)"
git tag -d _old
fi
git checkout "$BRANCH"
OLD_REV="$(git rev-parse --short HEAD)"
if [ "$TRAVIS_PULL_REQUEST" != false ]; then
if [ "$PR_NUMBER" != false ]; then
MERGE_COMMIT_MSG="$COMMIT_MSG
Pull request: https://github.com/awesomeWM/awesome/pull/${TRAVIS_PULL_REQUEST}"
Pull request: https://github.com/awesomeWM/awesome/pull/${PR_NUMBER}"
else
PR_OR_ISSUE="$(echo "$COMMIT_MSG" | head -n 1 | grep -o '#[0-9]\+' || true)"
if [ -n "$PR_OR_ISSUE" ]; then
@ -134,24 +136,24 @@ fi
git merge --no-ff -m "$MERGE_COMMIT_MSG" merged-update
NEW_REV="$(git rev-parse --short HEAD)"
git push origin "$BRANCH" 2>&1 | sed "s/$GH_APIDOC_TOKEN/GH_APIDOC_TOKEN/g"
git push origin "$BRANCH" 2>&1 | sed "s/$APIDOC_TOKEN/APIDOC_TOKEN/g"
# Generate compare view links.
# NOTE: use "\n" for line endings, not real ones for valid json!
COMPARE_LINKS="Compare view: https://github.com/awesomeWM/apidoc/compare/${OLD_REV}...${NEW_REV}"
COMPARE_LINKS="$COMPARE_LINKS\nRelevant changes: https://github.com/awesomeWM/apidoc/commit/${RELEVANT_REV}"
if [ "$BRANCH" != "gh-pages" ]; then
COMPARE_LINKS="$COMPARE_LINKS\nComparison against master (gh-pages): https://github.com/awesomeWM/apidoc/compare/gh-pages...${NEW_REV}"
COMPARE_LINKS="$COMPARE_LINKS\nComparison against master (gh-pages): https://github.com/awesomeWM/apidoc/compare/gh-pages...${NEW_REV}"
fi
# shellcheck disable=SC2028
echo "Compare links:\n$COMPARE_LINKS"
printf %s "Compare links:\n$COMPARE_LINKS"
# Post a comment to the PR.
if [ "$TRAVIS_PULL_REQUEST" != false ]; then
curl -H "Authorization: token $GH_APIDOC_TOKEN" \
-d "{\"body\": \"Documentation has been updated for this PR.\n\n$COMPARE_LINKS\"}" \
"https://api.github.com/repos/awesomeWM/awesome/issues/${TRAVIS_PULL_REQUEST}/comments" \
2>&1 | sed "s/$GH_APIDOC_TOKEN/GH_APIDOC_TOKEN/g"
if [ "$PR_NUMBER" != false ]; then
curl -H "Authorization: token $APIDOC_TOKEN" \
-d "{\"body\": \"Documentation has been updated for this PR.\n\n$COMPARE_LINKS\"}" \
"https://api.github.com/repos/awesomeWM/awesome/issues/${PR_NUMBER}/comments" \
2>&1 | sed "s/$APIDOC_TOKEN/APIDOC_TOKEN/g"
fi
# vim: filetype=sh:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80

156
.github/workflows/apidoc.yml vendored Normal file
View File

@ -0,0 +1,156 @@
name: Update API docs
on:
# Trigger on push to branches `master` and `3.5`.
push:
branches: [ master, 3.5 ]
# Trigger on pull request events for PRs that have `master` as their target branch
pull_request:
branches: [ master ]
# Allow running the workflow manually
workflow_dispatch:
defaults:
run:
# GitHub Actions adds `errexit` and `pipefail` by default, but we add `xtrace`
# to improve debugging some of the longer scripts.
shell: /bin/bash -o errexit -o pipefail -o xtrace {0}
jobs:
main:
runs-on: ubuntu-20.04
env:
# Used for stable dates in documentation examples. See #2070.
SOURCE_DATE_EPOCH: "1893456000"
steps:
# Create a cache invalidation key based on the current year + week.
# This way, packages will be checked for updates once every week.
- name: Get Date
id: get-date
run: echo "::set-output name=date::$(/bin/date -u "+%Y%W")"
- name: Cache apt packages
id: cache-apt
uses: actions/cache@v2
with:
path: /var/cache/apt/archives
# The trailing number serves as a version flag that can be incremented
# to invalidate the cache after changing the list of packages.
key: ${{ github.workflow }}-${{ runner.os }}-${{ steps.get-date.outputs.date }}-apt-3
- name: Download apt packages
if: steps.cache-apt.outputs.cache-hit != 'true'
run: |
sudo apt-get update
sudo apt-get install --download-only -y --no-install-recommends \
asciidoctor \
cmake \
dbus-x11 \
gettext \
gir1.2-gtk-3.0 \
gir1.2-pango-1.0 \
git \
libdbus-1-dev \
libgirepository1.0-dev \
libnotify-bin \
libpango1.0-dev \
libstartup-notification0-dev \
libx11-xcb-dev \
libxcb-cursor-dev \
libxcb-icccm4-dev \
libxcb-keysyms1-dev \
libxcb-randr0-dev \
libxcb-shape0-dev \
libxcb-util0-dev \
libxcb-xfixes0-dev \
libxcb-xinerama0-dev \
libxcb-xkb-dev \
libxcb-xrm-dev \
libxcb-xtest0-dev \
libxdg-basedir-dev \
libxkbcommon-dev \
libxkbcommon-x11-dev \
xutils-dev \
lua-discount \
liblua5.3-dev \
lua5.3
- name: Install downloaded packages
run: |
sudo dpkg -i /var/cache/apt/archives/*.deb
- name: Cache luarocks
id: cache-luarocks
uses: actions/cache@v2
with:
path: /tmp/luarocks
key: ${{ github.workflow }}-${{ runner.os }}-luarocks-3.5.0
- name: Install fresh Luarocks
if: steps.cache-luarocks.outputs.cache-hit != 'true'
run: |
wget -O /tmp/luarocks.tar.gz https://github.com/luarocks/luarocks/archive/v3.5.0.tar.gz
mkdir /tmp/luarocks
tar -xf /tmp/luarocks.tar.gz -C /tmp/luarocks --strip-components=1
cd /tmp/luarocks
./configure
make build
sudo make install
- name: Install cached Luarocks
if: steps.cache-luarocks.outputs.cache-hit == 'true'
run: |
cd /tmp/luarocks
sudo make install
- name: Install rocks
run: |
sudo -H luarocks install lgi
sudo -H luarocks install ldoc
- uses: actions/checkout@v2
- name: Build Awesome version string
run: |
# If this workflow is triggered by a pull request, we get a base branch.
# Otherwise, check if the current commit has a meaningful name.
if [ -n "${{ github.base_ref }}" ]; then
AWESOME_VERSION="${{ github.base_ref }}"
else
AWESOME_VERSION="$(git rev-parse --abbrev-ref HEAD)"
fi
AWESOME_VERSION="${AWESOME_VERSION}-g$(git rev-parse --short HEAD)"
if [ "${{ github.event_name }}" == "pull_request" && -n "${{ matrix.test_prev_commits }}" ]; then
AWESOME_VERSION="${AWESOME_VERSION}-PR${{ github.event.number }}"
elif [ "${{ github.event_name }}" == "pull_request" ]; then
AWESOME_VERSION="v9999-PR${{ github.event.number }}"
fi
echo "AWESOME_VERSION=${AWESOME_VERSION}" >> ${GITHUB_ENV}
- name: Create Build Environment
run: cmake -E make_directory -B "${{ github.workspace }}/build"
- name: Configure CMake
run: |
cmake -S ${{ github.workspace }} -B "${{ github.workspace}}/build" \
-DAWESOME_VERSION=$AWESOME_VERSION \
-DGENERATE_DOC=ON \
-DGENERATE_MANPAGES=ON \
-DDO_COVERAGE=OFF
- name: Build
run: cd "${{ github.workspace }}/build" && make
- name: Run apidoc script
env:
PR_NUMBER: ${{ github.event.number }}
APIDOC_TOKEN: ${{ secrets.APIDOC_TOKEN || github.token }}
run: .github/scripts/apidoc.sh
# vim: filetype=yaml:expandtab:shiftwidth=2:tabstop=2

394
.github/workflows/main.yml vendored Normal file
View File

@ -0,0 +1,394 @@
name: Build & Test
on:
# Trigger on push to branches `master` and `3.5`.
push:
branches: [ master, 3.5 ]
# Trigger on pull request events for PRs that have `master` as their target branch
pull_request:
branches: [ master ]
# Allow running the workflow manually
workflow_dispatch:
defaults:
run:
# GitHub Actions adds `errexit` and `pipefail` by default, but we add `xtrace`
# to improve debugging some of the longer scripts.
shell: /bin/bash -o errexit -o pipefail -o xtrace {0}
jobs:
main:
# The type of runner that the job will run on
runs-on: ubuntu-20.04
name: ${{ matrix.test_name }}
strategy:
# Let other jobs continue even if one of them fails
fail-fast: false
matrix:
include:
- test_name: "codecov-lua5.3"
lua_version: "5.3"
lua_name: "lua5.3"
coverage: "codecov"
manual_screens: true
- test_name: "coveralls-lua5.2"
lua_version: "5.2"
lua_name: "lua5.2"
coverage: "coveralls"
manual_screens: true
- test_name: "lua5.1"
lua_version: "5.1"
lua_name: "lua5.1"
- test_name: "luajit"
lua_version: "5.1"
lua_name: "luajit"
lua_library: "/usr/lib/x86_64-linux-gnu/libluajit-5.1.so"
lua_include: "/usr/include/luajit-2.1"
luarocks_args: "--lua-suffix=jit-2.1.0-beta3"
# Lua 5.2 with fixed lgi version and screen size not divisible by 2.
- test_name: "fixed-lgi-lua5.2"
lua_version: "5.2"
lua_name: "lua5.2"
lgi_version: "0.9.2"
tests_screen_size: "1921x1079"
check_qa: true
empty_theme_while_loading: true
test_prev_commits: true
env:
LUA: ${{ matrix.lua_version }}
LUAINCLUDE: ${{ matrix.lua_include || format('/usr/include/lua{0}', matrix.lua_version) }}
LUALIBRARY: ${{ matrix.lua_library || format('/usr/lib/x86_64-linux-gnu/liblua{0}.so', matrix.lua_version) }}
TESTS_SCREEN_SIZE: ${{ matrix.tests_screen_size }}
TEST_TIMEOUT: '100'
CI: 'true'
steps:
# Create a cache invalidation key based on the current year + week.
# This way, packages will be checked for updates once every week.
- name: Get Date
id: get-date
run: echo "::set-output name=date::$(/bin/date -u "+%Y%W")"
- name: Cache apt packages
id: cache-apt
uses: actions/cache@v2
with:
path: /var/cache/apt/archives
# The trailing number serves as a version flag that can be incremented
# to invalidate the cache after changing the list of packages.
key: ${{ github.workflow }}-${{ runner.os }}-${{ steps.get-date.outputs.date }}-apt-4
- name: Download apt packages
if: steps.cache-apt.outputs.cache-hit != 'true'
run: |
sudo apt-get update
sudo apt-get install --download-only -y --no-install-recommends \
asciidoctor \
cmake \
dbus-x11 \
gettext \
gir1.2-gtk-3.0 \
gir1.2-pango-1.0 \
git \
libdbus-1-dev \
libgirepository1.0-dev \
libnotify-bin \
libpango1.0-dev \
libstartup-notification0-dev \
libx11-xcb-dev \
libxcb-cursor-dev \
libxcb-icccm4-dev \
libxcb-keysyms1-dev \
libxcb-randr0-dev \
libxcb-shape0-dev \
libxcb-util0-dev \
libxcb-xfixes0-dev \
libxcb-xinerama0-dev \
libxcb-xkb-dev \
libxcb-xrm-dev \
libxcb-xtest0-dev \
libxdg-basedir-dev \
libxkbcommon-dev \
libxkbcommon-x11-dev \
wmctrl \
x11-apps \
xcb-proto \
xdotool \
xorg \
xserver-xephyr \
xterm \
xutils-dev \
xvfb \
zsh \
lua-discount
- name: Install downloaded packages
run: sudo dpkg -i /var/cache/apt/archives/*.deb
- name: Install Lua packages
run: |
if [ "${{ matrix.lua_name }}" = "luajit" ]; then
sudo apt-get install libluajit-5.1-dev luajit
fi
sudo apt-get install liblua${{ matrix.lua_version }}-dev lua${{ matrix.lua_version }}
- name: Cache luarocks
id: cache-luarocks
uses: actions/cache@v2
with:
path: /tmp/luarocks
# The build input for luarocks changes per test, so we need separate caches
key: ${{ github.workflow }}-${{ runner.os }}-${{ matrix.test_name }}-luarocks-3.5.0
- name: Install fresh Luarocks
if: steps.cache-luarocks.outputs.cache-hit != 'true'
run: |
wget -O /tmp/luarocks.tar.gz https://github.com/luarocks/luarocks/archive/v3.5.0.tar.gz
mkdir /tmp/luarocks
tar -xf /tmp/luarocks.tar.gz -C /tmp/luarocks --strip-components=1
cd /tmp/luarocks
./configure --lua-version=${{ matrix.lua_version }} --with-lua-include=${LUAINCLUDE} ${{ matrix.luarocks_args }}
make build
sudo make install
- name: Install cached Luarocks
if: steps.cache-luarocks.outputs.cache-hit == 'true'
run: |
cd /tmp/luarocks
sudo make install
- name: Cache xcb-errors
id: cache-xcb-errors
uses: actions/cache@v2
with:
path: /tmp/xcb-errors
key: ${{ github.workflow }}-${{ runner.os }}-xcb-errors-1.0
- name: Install fresh xcb-errors
if: steps.cache-xcb-errors.outputs.cache-hit != 'true'
run: |
git clone --recursive --depth 1 --branch 1.0 https://gitlab.freedesktop.org/xorg/lib/libxcb-errors.git /tmp/xcb-errors
cd /tmp/xcb-errors
./autogen.sh --prefix=/usr
make
sudo make install
- name: Install cached xcb-errors
if: steps.cache-xcb-errors.outputs.cache-hit == 'true'
run: |
cd /tmp/xcb-errors
sudo make install
- name: Install rocks
run: |
sudo -H luarocks install lgi ${{ matrix.lgi_version }}
sudo -H luarocks install ldoc
sudo -H luarocks install busted
- name: Install QA check rocks
if: matrix.check_qa
run: |
sudo -H luarocks install luacheck
sudo -H luarocks install depgraph
- name: Install cluacov rock
if: matrix.coverage
run: sudo -H luarocks install cluacov
- name: Install coveralls rock
if: matrix.coverage == 'coveralls'
run: sudo -H luarocks install luacov-coveralls
- name: Install codecov.io uploader
if: matrix.coverage == 'codecov'
run: wget -O /tmp/codecov-bash https://codecov.io/bash
# Check out repository to ${{ github.workspace }}
# Automatically picks the current branch/PR
- uses: actions/checkout@v2
- name: Create Build Environment
run: cmake -E make_directory -B "${{ github.workspace }}/build"
- name: Build Awesome version string
run: |
# If this workflow is triggered by a pull request, we get a base branch.
# Otherwise, check if the current commit has a meaningful name.
if [ -n "${{ github.base_ref }}" ]; then
AWESOME_VERSION="${{ github.base_ref }}"
else
AWESOME_VERSION="$(git rev-parse --abbrev-ref HEAD)"
fi
AWESOME_VERSION="${AWESOME_VERSION}-g$(git rev-parse --short HEAD)"
if [ "${{ github.event_name }}" == "pull_request" && -n "${{ matrix.test_prev_commits }}" ]; then
AWESOME_VERSION="${AWESOME_VERSION}-PR${{ github.event.number }}"
elif [ "${{ github.event_name }}" == "pull_request" ]; then
AWESOME_VERSION="v9999-PR${{ github.event.number }}"
fi
echo "AWESOME_VERSION=${AWESOME_VERSION}" >> ${GITHUB_ENV}
- name: Configure CMake
run: |
cmake -S ${{ github.workspace }} -B "${{ github.workspace}}/build" \
-DAWESOME_VERSION=$AWESOME_VERSION \
-DLUA_LIBRARY=$LUALIBRARY \
-DLUA_INCLUDE_DIR=$LUAINCLUDE \
-DGENERATE_DOC=OFF \
-DGENERATE_MANPAGES=OFF \
-DDO_COVERAGE=${{ matrix.coverage }} \
-DTEST_MANUAL_SCREENS=${{ matrix.manual_screens }} \
-DSTRICT_TESTS=true \
-DCMAKE_C_FLAGS="-Werror"
# Break beautiful so that trying to access the theme before `beautiful.init()` causes an error
- name: Patch beautiful for an empty default theme
if: matrix.empty_theme_while_loading
run: |
sed -i -e 's/theme = {}/theme = setmetatable({}, { __index = function() error("May not access theme before beautiful.init()") end })/' lib/beautiful/init.lua
grep -q 'May not access' lib/beautiful/init.lua
- name: Build
run: cd "${{ github.workspace }}/build" && make
# Executable needs to be run once to provide coverage results
- name: Install and run
run: |
cd "${{ github.workspace }}/build"
sudo make install
awesome --version
- name: Run integration tests
run: cd "${{ github.workspace }}/build" && make check-integration
- name: Run unit tests
run: cd "${{ github.workspace }}/build" && make check-unit
- name: Run examples tests
if: matrix.coverage
run: cd "${{ github.workspace }}/build" && make check-examples
- name: Run requires tests
if: matrix.coverage
run: cd "${{ github.workspace }}/build" && make check-requires
- name: Run themes tests
run: cd "${{ github.workspace }}/build" && make check-themes
- name: Upload Lua code coverage report
if: matrix.coverage == 'codecov'
run: |
luacov
bash /tmp/codecov-bash -f build/luacov.report.out -X gcov -X coveragepy -F luacov
- name: Upload C code coverage report
if: matrix.coverage == 'codecov'
run: |
# Report coverage for each .gcno file separately.
# gcov will create .gcov files for the same source (e.g. for
# globalconf.h from awesome.c.gcno and event.c.gcno).
i=0
cd "${{ github.workspace }}/build"
find -path "*/lgi-check.dir" -prune -o \( -name '*.gcno' -print \) | while read -r gcno; do
gcov -pb "$gcno"
mkdir gcov.$(( ++i ))
mv *.gcov "gcov.$i"
# Delete any files for /usr.
# They are not relevant and might cause "Invalid path part" errors
# with Code Climate.
find "gcov.$i" -maxdepth 1 -type f -name '#usr#*.gcov' -delete
done
# Upload to Codecov.
bash /tmp/codecov-bash -X gcov -X coveragepy -F gcov
- name: Merge coverage
if: matrix.coverage == 'coveralls'
# Coveralls doesn't support GitHub Actions with `service_name` + `service_job_id` (yet?),
# but `luacov-coveralls` behaves as if they did by default.
# We need to explicitly remove the `service_name`, so that Coveralls uses the
# `repo_token` instead.
# See "Referencing a repository": https://docs.coveralls.io/api-introduction
run: |
luacov-coveralls \
--verbose \
--merge \
--repo-token ${{ secrets.COVERALLS_REPO_TOKEN || github.token }} \
--service-name ""
# `check-qa` is the only test that doesn't get a coverage report, so it has to run after all of that.
- name: Run qa tests
if: matrix.check_qa
run: cd "${{ github.workspace }}/build" && make check-qa
# Check each commit separately (to make git-bisect less annoying).
- name: Test previous commits
if: matrix.test_prev_commits && github.event_name == 'pull_request'
run: |
git remote add fork "${{ github.event.pull_request.head.repo.git_url }}"
# `actions/checkout` creates a shallow repo (`--depth 1`) by default,
# which is fine for everything up until now. But we need individual commits now.
# And we only want to unshallow now, to not slow down the checkout for other jobs.
git fetch --unshallow --all
rev_list="$(git rev-list --bisect-all origin/${{ github.base_ref }}..fork/${{ github.head_ref }})"
# The most recent commit has already been tested. So if that's the
# only commit in the PR, we can stop here.
if [[ $(echo "$rev_list" | wc -l) -lt 2 ]]; then
exit 0
fi
commits="$(echo "$rev_list" | grep -v 'dist=0' | cut -d' ' -f 1)"
n="$(echo "$commits" | wc -l)"
echo "Testing $n commits:"
echo "$commits" | xargs -I{} git log -1 --pretty='%h %s' {}
failed=""
for commit in $commits; do
echo "Testing commit $commit"
# Some files are updated when compiling...
git checkout --force "$commit"
git show --stat --oneline
cmake -S ${{ github.workspace }} -B "${{ github.workspace}}/build" \
-DAWESOME_VERSION=$AWESOME_VERSION \
-DLUA_LIBRARY=$LUALIBRARY \
-DLUA_INCLUDE_DIR=$LUAINCLUDE \
-DGENERATE_DOC=OFF \
-DGENERATE_MANPAGES=OFF \
-DDO_COVERAGE=OFF \
-DTEST_MANUAL_SCREENS=${{ matrix.manual_screens }} \
-DSTRICT_TESTS=true \
-DCMAKE_C_FLAGS="-Werror"
cd "${{ github.workspace}}/build"
if ! ( make && make check-unit ); then
failed="$failed $commit"
fi
done
git checkout --quiet --force fork/${{ github.head_ref }}
if [ -n "$failed" ]; then
echo "Checks failed for these commits:"
for c in $failed; do
git show --no-patch --pretty="%h %s (%an, %ad)" "$c"
done
exit 1
fi
# vim: filetype=yaml:expandtab:shiftwidth=2:tabstop=2

View File

@ -49,7 +49,7 @@ globals = {
-- Enable cache (uses .luacheckcache relative to this rc file).
cache = true
-- Do not enable colors to make the Travis CI output more readable.
-- Do not enable colors to make the CI output more readable.
color = false
-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80

View File

@ -3,7 +3,8 @@ pull_request_rules:
conditions:
- label!=no-mergify
- '#approved-reviews-by>=2'
- status-success=Travis CI - Pull Request
- status-success=Build & Test
- status-success=Update API docs
- status-success=codecov/patch
- status-success=coverage/coveralls
actions:

View File

@ -1,340 +0,0 @@
dist: xenial
language: c
# Build only master and stable branches. Other branches go through PRs.
branches:
only:
- master
- 3.5
addons:
apt:
packages: &BASE_PACKAGES
# Build dependencies.
# See also `apt-cache showsrc awesome | grep -E '^(Version|Build-Depends)'`.
- libnotify-bin
- libcairo2-dev
- gir1.2-gtk-3.0
- libpango1.0-dev
- libxcb-xtest0-dev
- libxcb-icccm4-dev
- libxcb-randr0-dev
- libxcb-keysyms1-dev
- libxcb-xinerama0-dev
- libdbus-1-dev
- libxdg-basedir-dev
- libstartup-notification0-dev
- imagemagick
- libxcb1-dev
- libxcb-shape0-dev
- libxcb-util0-dev
- libx11-xcb-dev
- libxcb-cursor-dev
- libxcb-xkb-dev
- libxcb-xfixes0-dev
- libxkbcommon-dev
- libxkbcommon-x11-dev
# Deps for tests.
- dbus-x11
- xterm
- xdotool
- wmctrl
- xterm
- xvfb
- zsh
- x11-apps
# Need xorg-macros
- xutils-dev
# lgi.
- gir1.2-pango-1.0
- libgirepository1.0-dev
jobs:
include:
- env: LUA=5.2 LUANAME=lua5.2 DO_COVERAGE=coveralls MANUAL_SCREENS=1
addons:
apt:
packages:
- *BASE_PACKAGES
- liblua5.2-dev
- lua5.2
- env: LUA=5.3 LUANAME=lua5.3 DO_COVERAGE=codecov MANUAL_SCREENS=1
addons:
apt:
packages:
- *BASE_PACKAGES
- liblua5.3-dev
- lua5.3
- env: LUA=5.1 LUANAME=lua5.1 BUILD_IN_DIR=/tmp/awesome-build WITH_XCB_ERRORS=yes
addons:
apt:
packages:
- *BASE_PACKAGES
- liblua5.1-dev
- lua5.1
# For xcb-errors
- xcb-proto
- env: LUA=5.1 LUANAME=luajit-2.0 LUALIBRARY=/usr/lib/x86_64-linux-gnu/libluajit-5.1.so LUAROCKS_ARGS=--lua-suffix=jit-2.0.4
addons:
apt:
packages:
- *BASE_PACKAGES
- libluajit-5.1-dev
- luajit
# Lua 5.2 with older lgi and screen size not divisible by 2.
# SOURCE_DATE_EPOCH: used for stable dates during build.
- env: LUA=5.2 LUANAME=lua5.2 LGIVER=0.8.0 TESTS_SCREEN_SIZE=1921x1079 BUILD_APIDOC=true DO_CHECKQA=1 EMPTY_THEME_WHILE_LOADING=1 SOURCE_DATE_EPOCH=1893456000 TEST_PREV_COMMITS=1
addons:
apt:
packages:
- *BASE_PACKAGES
- liblua5.2-dev
- lua5.2
- lua-discount
env:
global:
# Secure GH_APIDOC_TOKEN to push to awesomeWM/apidoc.
- secure: "R/HYDclnws1I1+v9Yjt+RKa4CsFhbBT9tiwE3EfPhEj2KCYX4sFRMxuZvLf5sq0XWdrQaPhQ54fgAZGr3f054JKRXcTB0g9J6nhSHz9kIjPh446gafUhEeDQcZRwM/MeCWiwFIkiZm6smYoDFE9JTWu6quNV+lQ4kcVDOp2ibEc="
before_install:
- if [ "$BUILD_APIDOC" = true ] && [ -n "$DO_COVERAGE" ]; then echo "BUILD_APIDOC and DO_COVERAGE are not meant to be used together." >&2; exit 1; fi
- if [ -z $LUAINCLUDE ]; then LUAINCLUDE=/usr/include/${LUANAME}; fi
- if [ -z $LUALIBRARY ]; then LUALIBRARY=/usr/lib/x86_64-linux-gnu/lib${LUANAME}.so; fi
- cmake --version
install:
- sudo gem install asciidoctor
- |
# Install xcb-util-xrm.
set -e
(git clone --depth 1 --recursive https://github.com/Airblader/xcb-util-xrm.git /tmp/xcb-util-xrm
cd /tmp/xcb-util-xrm
./autogen.sh --prefix=/usr
make && sudo make install)
# Install xcb-errors if needed
- |
set -e
if [[ "$WITH_XCB_ERRORS" == "yes" ]]; then
git clone --depth 1 --recursive https://gitlab.freedesktop.org/xorg/lib/libxcb-errors.git /tmp/xcb-errors
(cd /tmp/xcb-errors
./autogen.sh --prefix=/usr
make && sudo make install)
fi
- |
# Install Lua (per env).
if [[ "$LUANAME" == "luajit-2.0" ]]; then
# "Create" /usr/bin/lua if needed (Yup, this is a bad hack)
if [ ! -e "/usr/bin/lua" ]; then sudo ln -s /usr/bin/luajit /usr/bin/lua; fi
else
# lua5.3 does not install /usr/bin/lua, but lua5.1/lua5.2 do.
if [ ! -e "/usr/bin/lua" ]; then sudo ln -s /usr/bin/${LUANAME} /usr/bin/lua; fi
fi
- lua -v
# Install luarocks (for the selected Lua version).
- |
travis_retry wget https://github.com/luarocks/luarocks/archive/v3.0.4.tar.gz
tar xf v3.0.4.tar.gz -C /tmp
(cd /tmp/luarocks-* \
&& ./configure --lua-version=$LUA --with-lua-include=${LUAINCLUDE} ${LUAROCKS_ARGS} \
&& make build \
&& sudo make install)
- travis_retry sudo luarocks install lgi $LGIVER
# Install busted for "make check-unit".
- travis_retry sudo luarocks install busted
# Install luacheck for "make check-qa".
- if [ "$DO_CHECKQA" = 1 ]; then travis_retry sudo luarocks install luacheck; fi
# Install depgraph for "make check-qa".
- if [ "$DO_CHECKQA" = 1 ]; then travis_retry sudo luarocks install depgraph; fi
# Install ldoc for building docs and check-ldoc-warnings.
- |
if [ "$BUILD_APIDOC" = "true" ] || [ "$DO_CHECKQA" = 1 ]; then
travis_retry sudo luarocks install ldoc
fi
# Install dependencies for code coverage testing.
- if [ -n "$DO_COVERAGE" ]; then sudo luarocks install cluacov; fi
- if [ "$DO_COVERAGE" = "coveralls" ]; then sudo luarocks install luacov-coveralls; fi
# Determine custom version.
- export AWESOME_VERSION="${TRAVIS_BRANCH}-g$(git rev-parse --short HEAD)"
- |
if [ "$TRAVIS_PULL_REQUEST" != false ] && [ "$TEST_PREV_COMMITS" = 1 ]; then
AWESOME_VERSION="${AWESOME_VERSION}-PR${TRAVIS_PULL_REQUEST}"
elif [ "$TRAVIS_PULL_REQUEST" != false ]; then
AWESOME_VERSION="v9999-PR${TRAVIS_PULL_REQUEST}";
fi
# function for codecov support
- if [ "$DO_COVERAGE" = "codecov" ]; then travis_retry wget -O /tmp/codecov-bash https://codecov.io/bash; fi
- |
do_codecov() {
echo "== do_codecov in $PWD: $*: build/luacov.stats.out: =="
if [ "$DO_COVERAGE" = "codecov" ]; then
test -f build/luacov.stats.out || { echo 'build/luacov.stats.out does not exist.'; return 1; }
luacov || return 1
travis_retry bash /tmp/codecov-bash -f build/luacov.report.out -X gcov -X coveragepy -F luacov -e LUANAME,LGIVER || return 1
rm build/luacov.report.out build/luacov.stats.out
fi
return 0
}
- |
do_codecov_gcov() {
echo "== do_codecov_gcov in $PWD: $*: =="
if [ "$DO_COVERAGE" = "codecov" ]; then
# Report coverage for each .gcno file separately.
# gcov will create .gcov files for the same source (e.g. for
# globalconf.h from awesome.c.gcno and event.c.gcno).
i=0
set -x
(
cd build
find -path "*/lgi-check.dir" -prune -o \( -name '*.gcno' -print \) | while read -r gcno; do
gcov -pb "$gcno"
mkdir gcov.$(( ++i ))
mv *.gcov "gcov.$i"
# Delete any files for /usr.
# They are not relevant and might cause "Invalid path part" errors
# with Code Climate.
find "gcov.$i" -maxdepth 1 -type f -name '#usr#*.gcov' -delete
done
# Upload to Codecov.
travis_retry bash /tmp/codecov-bash -X gcov -X coveragepy -F gcov -e LUANAME,LGIVER
)
fi
return 0
}
# Functions for custom Travis folds.
- |
travis_fold_start() {
travis_fold start "$1"
travis_fold_current="$1"
}
travis_fold_end() {
travis_fold end "$travis_fold_current"
}
travis_run_in_fold() {
travis_fold_start "$1"
shift
"$@" || return 1
travis_fold_end
return 0
}
script:
- if [ "$MANUAL_SCREENS" != "1" ]; then export MANUAL_SCREENS=0; fi
- export CMAKE_ARGS="-DLUA_LIBRARY=${LUALIBRARY} -D LUA_INCLUDE_DIR=${LUAINCLUDE} -D OVERRIDE_VERSION=$AWESOME_VERSION -D STRICT_TESTS=true -D DO_COVERAGE=$DO_COVERAGE -D TEST_MANUAL_SCREENS=$MANUAL_SCREENS -D CMAKE_C_FLAGS=-Werror"
- |
if [ "$EMPTY_THEME_WHILE_LOADING" = 1 ]; then
# Break beautiful so that trying to access the theme before beautiful.init() causes an error
sed -i -e 's/theme = {}/theme = setmetatable({}, { __index = function() error("May not access theme before beautiful.init()") end })/' lib/beautiful/init.lua \
&& grep -q 'May not access' lib/beautiful/init.lua
fi
- |
set -e
if [ -n "$BUILD_IN_DIR" ]; then
# Explicitly remove the Makefile to not build from the src dir accidentally.
rm Makefile
SOURCE_DIRECTORY="$PWD"
mkdir "$BUILD_IN_DIR"
cd "$BUILD_IN_DIR"
travis_run_in_fold "build_in_dir" cmake $CMAKE_ARGS "$SOURCE_DIRECTORY"
fi
- travis_run_in_fold "make" make ; sleep 5
- make ; sleep 5
- make ; sleep 5
- make ; sleep 5
- |
if [ "$TRAVIS_TEST_RESULT" = 0 ]; then
travis_run_in_fold "make.install" sudo env PATH=$PATH make install
awesome --version
fi
# Run checks.
- |
if [ "$TRAVIS_TEST_RESULT" = 0 ]; then
set -ex
if [ -n "$DO_COVERAGE" ]; then
travis_fold_start "DO_COVERAGE"
# Run tests/examples explicitly.
make check-examples || exit 1
do_codecov samples
make check-unit || exit 1
do_codecov unittests
make check-integration || exit 1
do_codecov functionaltests
make check-themes || exit 1
do_codecov themes
do_codecov_gcov c_code
travis_fold_end
else
# TODO: does not run check-examples. Should it?
travis_run_in_fold "make.check-unit" make check-unit
travis_run_in_fold "make.check-integration" make check-integration
travis_run_in_fold "make.check-themes" make check-themes
fi
set +x
fi
# Run check-qa.
- |
if [ "$DO_CHECKQA" = 1 ]; then
travis_run_in_fold "make.check-qa" make check-qa
fi
- |
if [ "$TEST_PREV_COMMITS" = 1 ] && ! [ "$TRAVIS_PULL_REQUEST" = false ]; then
set -e
# Check each commit separately (to make git-bisect less annoying).
# Fix Travis' commit range (https://github.com/travis-ci/travis-ci/issues/4596).
commit_range="${TRAVIS_COMMIT_RANGE/.../..}"
echo "Testing previous commits ($commit_range)"
rev_list="$(git rev-list --bisect-all $commit_range)"
echo "rev-list: $rev_list"
commits="$(echo "$rev_list" | grep -v 'dist=0' | cut -d\ -f 1)"
n="$(echo "$commits" | wc -l)"
echo "Testing $n commits: $commits"
i=0
failed=
for commit in $commits; do
i=$((i+1))
travis_fold_start "test_commit_${commit}_.$i.$n"
echo "Testing commit $commit"
git reset --hard # Some files are updated when compiling...
git checkout "$commit"
git --no-pager show --stat
if ! make all check CMAKE_ARGS+="-D DO_COVERAGE=0"; then
failed="$failed $commit"
fi
travis_fold_end
done
git checkout -qf FETCH_HEAD
if [ -n "$failed" ]; then
echo "Checks failed for these commits:"
for c in $failed; do
git log -1 --pretty="%h %s (%an, %ad)" "$c"
done
false
fi
fi
after_success:
# Push updated API docs for relevant branches, e.g. non-PRs builds on master.
- if [ "$BUILD_APIDOC" = "true" ]; then build-utils/travis-apidoc.sh; fi
# Push code coverage information
- |
set -e
if [ "$DO_COVERAGE" = "coveralls" ]; then
test -f build/luacov.stats.out || { echo 'build/luacov.stats.out does not exist.'; return 1; }
luacov-coveralls --verbose --merge
fi

View File

@ -619,13 +619,14 @@ main(int argc, char **argv)
bool success = true;
/* Get the first config that will be tried */
const char *config = luaA_find_config(&xdg, confpath, true_config_callback);
fprintf(stdout, "Checking config '%s'... ", config);
/* Try to parse it */
lua_State *L = luaL_newstate();
if(luaL_loadfile(L, config))
{
const char *err = lua_tostring(L, -1);
fprintf(stderr, "%s\n", err);
fprintf(stdout, "\nERROR: %s\n", err);
success = false;
}
p_delete(&config);
@ -633,12 +634,11 @@ main(int argc, char **argv)
if(!success)
{
fprintf(stderr, "✘ Configuration file syntax error.\n");
return EXIT_FAILURE;
}
else
{
fprintf(stderr, "✔ Configuration file syntax OK.\n");
fprintf(stdout, "OK\n");
return EXIT_SUCCESS;
}
}

View File

@ -71,6 +71,14 @@ if (NOT LUA_FOUND)
"You might want to hint it using the LUA_DIR environment variable, "
"or set the LUA_INCLUDE_DIR / LUA_LIBRARY CMake variables.")
endif()
set(LUA_FULL_VERSION "${LUA_VERSION_MAJOR}.${LUA_VERSION_MINOR}.${LUA_VERSION_PATCH}")
# 5.1 <= LUA_VERSION < 5.4
if(NOT ((LUA_FULL_VERSION VERSION_EQUAL 5.1.0 OR LUA_FULL_VERSION VERSION_GREATER 5.1.0) AND LUA_FULL_VERSION VERSION_LESS 5.4.0))
message(FATAL_ERROR "Awesome only supports Lua versions 5.1-5.3, please refer to"
"https://awesomewm.org/apidoc/documentation/10-building-and-testing.md.html#Building")
endif()
# }}}
# {{{ Check if documentation can be build

View File

@ -77,7 +77,7 @@ mylauncher = awful.widget.launcher({ image = beautiful.awesome_icon,
menubar.utils.terminal = terminal -- Set the terminal for applications that require it
-- }}}
-- {{{ Tag
-- {{{ Tag layout
-- @DOC_LAYOUT@
-- Table of layouts to cover with awful.layout.inc, order matters.
tag.connect_signal("request::default_layouts", function()
@ -99,6 +99,27 @@ tag.connect_signal("request::default_layouts", function()
end)
-- }}}
-- {{{ Wallpaper
-- @DOC_WALLPAPER@
screen.connect_signal("request::wallpaper", function(s)
awful.wallpaper {
screen = s,
widget = {
{
image = beautiful.wallpaper,
upscale = true,
downscale = true,
widget = wibox.widget.imagebox,
},
valign = "center",
halign = "center",
tiled = false,
widget = wibox.container.tile,
}
}
end)
-- }}}
-- {{{ Wibar
-- Keyboard map indicator and switcher
@ -107,19 +128,6 @@ mykeyboardlayout = awful.widget.keyboardlayout()
-- Create a textclock widget
mytextclock = wibox.widget.textclock()
-- @DOC_WALLPAPER@
screen.connect_signal("request::wallpaper", function(s)
-- Wallpaper
if beautiful.wallpaper then
local wallpaper = beautiful.wallpaper
-- If wallpaper is a function, call it with the screen
if type(wallpaper) == "function" then
wallpaper = wallpaper(s)
end
gears.wallpaper.maximized(wallpaper, s, true)
end
end)
-- @DOC_FOR_EACH_SCREEN@
screen.connect_signal("request::desktop_decoration", function(s)
-- Each screen has its own tag table.
@ -179,26 +187,27 @@ screen.connect_signal("request::desktop_decoration", function(s)
-- @DOC_WIBAR@
-- Create the wibox
s.mywibox = awful.wibar({ position = "top", screen = s })
-- @DOC_SETUP_WIDGETS@
-- Add widgets to the wibox
s.mywibox.widget = {
layout = wibox.layout.align.horizontal,
{ -- Left widgets
layout = wibox.layout.fixed.horizontal,
mylauncher,
s.mytaglist,
s.mypromptbox,
},
s.mytasklist, -- Middle widget
{ -- Right widgets
layout = wibox.layout.fixed.horizontal,
mykeyboardlayout,
wibox.widget.systray(),
mytextclock,
s.mylayoutbox,
},
s.mywibox = awful.wibar {
position = "top",
screen = s,
-- @DOC_SETUP_WIDGETS@
widget = {
layout = wibox.layout.align.horizontal,
{ -- Left widgets
layout = wibox.layout.fixed.horizontal,
mylauncher,
s.mytaglist,
s.mypromptbox,
},
s.mytasklist, -- Middle widget
{ -- Right widgets
layout = wibox.layout.fixed.horizontal,
mykeyboardlayout,
wibox.widget.systray(),
mytextclock,
s.mylayoutbox,
},
}
}
end)
-- }}}

View File

@ -63,7 +63,6 @@ local allowed_deps = {
},
-- TODO: Get rid of these
["gears.surface"] = { ["wibox.hierarchy"] = true },
["gears.connection"] = { ["*"] = true },
}
-- Turn "foo.bar.baz" into "foo.bar". Returns nil if there is nothing more to

View File

@ -124,6 +124,10 @@ to an object such as the mouse.
The `naughty.layout.box` allows to provide custom widgets to use within the
notifications.
The `awful.wallpaper` provides a non-intereactive "backgroud" for one or more
`screen`. While it uses normal widget, it will not automatically be repainted
if they change. It will also not provide any mouse events.
Finally, the `awful.titlebar`, while not technically a real `wibox`, acts
exactly the same way and allows to attach widgets on each side of clients.
@ -373,6 +377,50 @@ Code:
layout = wibox.layout.fixed.horizontal,
}
### Accessing widgets
For each widget or container, it is possible to add an `identifier` attribute
so that it can be accessed later.
Widgets defined using `setup` can be accessed using these methods:
* Avoiding the issue by using externally created widgets
* Using `my_wibox.my_first_widget.my_second_widget` style access
* Using JavaScript like `my_wibox:get_children_by_id("my_second_widget")[1]`
The first method mixes the imperative and declarative syntax, and makes the code
less readable. The second is a little verbose and only works if every node in
the chain has a valid identifier. The last one doesn't require long paths,
but it is not easy to get a specific instance if multiple widgets have the
same identifier.
WARNING: The widget identifier must not use a reserved name. This includes all
method names, existing widget attributes, `layout` and `widget`. Names should
also respect the Lua variable conventions (case-sensitive, alphanumeric,
underscore characters and non-numeric first character).
Code:
s.mywibox : setup {
{
id = "second",
widget = wibox.widget.textbox
},
{
id = "third",
widget = wibox.widget.textbox
},
id = "first",
layout = wibox.layout.fixed.horizontal,
}
s.mywibox.first.second:set_markup("changed!")
s.mywibox:get_children_by_id("third")[1]:set_markup("Also changed!")
### Extending the system
This system is very flexible. Each section attribute (the entries with string
@ -474,99 +522,3 @@ necessary for three reasons:
at a later time (by the parent widget).
* The code is highly redundant and some of the logic is delegated to the parent
widget to simplify everything.
## Accessing and updating widgets
There is 3 main ways to update the widgets. Each is best suited for it's own niche.
Choose the one that better suites the style of your code.
### Imperative
For each widget or container, it is possible to add an `identifier` attribute
so that it can be accessed later.
Widgets defined using `setup` can be accessed using these methods:
* Avoiding the issue by using externally created widgets
* Using `my_wibox.my_first_widget.my_second_widget` style access
* Using JavaScript like `my_wibox:get_children_by_id("my_second_widget")[1]`
The first method mixes the imperative and declarative syntax, and makes the code
less readable. The second is a little verbose and only works if every node in
the chain has a valid identifier. The last one doesn't require long paths,
but it is not easy to get a specific instance if multiple widgets have the
same identifier.
WARNING: The widget identifier must not use a reserved name. This includes all
method names, existing widget attributes, `layout` and `widget`. Names should
also respect the Lua variable conventions (case-sensitive, alphanumeric,
underscore characters and non-numeric first character).
Code:
s.mywibox : setup {
{
id = "second",
widget = wibox.widget.textbox
},
{
id = "third",
widget = wibox.widget.textbox
},
id = "first",
layout = wibox.layout.fixed.horizontal,
}
s.mywibox.first.second:set_markup("changed!")
s.mywibox:get_children_by_id("third")[1]:set_markup("Also changed!")
### Reactive
![Reactive update](../images/gears_reactive.svg)
Think of reactive programming like Excel/Spreadsheets. You define rules or even
business logic that will automatically be re-evaluated every time the data it
uses changes. In the background, this rewrites the function and automatically
creates all the signal connections. Lua is not a reactive language, so it has it's
limits and you should read the rules defined in the `gears.reactive` documentation
carefully. However, when it works, it is by far the simplest way to update a
widget defined using the declarative syntax.
#### Reactive expressions
A reactive expression is just a function wrapped by a `gears.reactive` object. This
will introspect the content and write all the boilerplate. Note that this *only* works
in declarative trees. It *cannot* be mixed with the imperative API.
@DOC_wibox_decl_doc_reactive_expr1_EXAMPLE@
Unlike QML, AwesomeWM also support extracting reactive blocks out of the tree:
@DOC_wibox_decl_doc_reactive_expr2_EXAMPLE@
#### Watcher objects
`gears.watcher` objects poll files or command at an interval. They do so using
background threads, so it wont affect performance much. They can be attached
directly to a widget or used with a `gears.connection` as demonstrated below.
Using `gears.connection` is preferred when the value is needed by multiple
widgets (see the CPU graph example later in this document).
@DOC_wibox_widget_progressbar_watcher_EXAMPLE@
### Declarative
The other way to interact with widgets is by creating `gears.connection` objects.
They can be added in your declarative widget tree like this:
@DOC_wibox_decl_doc_connection_EXAMPLE@
It is also possible to use them to bind non-widget objects with widgets:
@DOC_wibox_decl_doc_connection3_EXAMPLE@
One useful feature is that when the `gears.connection` has a callback, it has
direct access to all `id`s as variables or using `get_children_by_id`:
@DOC_wibox_decl_doc_connection5_EXAMPLE@

View File

@ -72,6 +72,17 @@ variables such as `bg_normal`. To get a list of all official variables, see
the [appearance guide](../documentation/06-appearance.md.html).
]]
sections.DOC_WALLPAPER = [[
The AwesomeWM wallpaper module, `awful.wallpaper` support both per-screen wallpaper
and wallpaper across multiple screens. In the default configuration, the `"request::wallpaper"` signal
is emitted everytime a screen is added, moved, resized or when the bars
(`awful.wibar`) are moved.
This is will suited for single-screen wallpapers. If you wish to use multi-screen wallpaper,
it is better to create a global wallpaper object and edit it when the screen change. See
the `add_screen`/`remove_screens` methods and the `screens` property of `awful.wallpaper` for
examples.
]]
sections.DOC_DEFAULT_APPLICATIONS = [[
&nbsp;

View File

@ -25,10 +25,10 @@ have not opened any programs. On the top right you see the time/date and a
symbol showing the current layout. You can also click on the symbol to change
the active layout.
One of the big advantages of Awesome over other tiling window managers is its good
mouse support. Awesome can act as a full floating window manager (almost like
openbox) if you want. For this basic tutorial we will mainly focus on keyboard
control, so let's learn some key bindings now.
One of the big advantages of Awesome over other tiling window managers is its
good mouse support. Awesome can act as a full floating window manager (almost
like openbox) if you want. For this basic tutorial we will mainly focus on
keyboard control, so let's learn some key bindings now.
Let's open a terminal: press *Mod4+Enter*. Mod4 is your "Windows key", the key
between Ctrl and Alt. You can change the modkey if you want, but we'll get to
@ -65,20 +65,23 @@ overview now also provides a cheat sheet for controlling Vim.
## Change the theme
Awesome has four themes you can choose from: *default*, *sky*, *xresources*, and
*zenburn*.
Awesome has multiple builtin themes you can choose from:
To change the theme, open your rc.lua and edit this line near the beginning of
the file:
* *default*
* *gtk*
* *sky*
* *xresources*
* *zenburn*
To change the theme, open your `rc.lua`, find this line near the beginning of
the file, and change `default` to one of the other values mentioned:
beautiful.init(gears.filesystem.get_themes_dir() .. "default/theme.lua")
For this tutorial we will stick with the default theme.
Now we will customize the theme. Copy
`/usr/share/awesome/themes/default/theme.lua` to `~/.config/awesome/` and change
the above line in your theme like this (remember to replace `USER` with your
user name):
However, for this tutorial we will copy and customize the default theme.
Copy `/usr/share/awesome/themes/default/theme.lua` to `~/.config/awesome/`
and change the line shown above in `rc.lua` like this. Make sure to replace
`USER` with your user name.
beautiful.init("/home/USER/.config/awesome/theme.lua")
@ -89,7 +92,9 @@ this line in your theme file:
theme.wallpaper = themes_path.."default/background.png"
The default uses a path relative to `themes_path` by using the `..` operator to join two strings together. To just set it to an absolute path for example, you could do:
The default uses a path relative to `themes_path` by using the `..` operator to
join two strings together. To just set it to an absolute path for example,
you could do:
theme.wallpaper = "/usr/share/backgrounds/my-awesome-wallpaper.png"

View File

@ -36,8 +36,7 @@ Do **not** use such functions and prefer `awful.spawn.easy_async`,
fast enough and won't impact the main event loop iteration time, you are wrong.
*Every* calls to `io.open` are impacted by the system `iowait` queue and can
spend hundreds of milliseconds blocked *before* being executed. Note that
some common widget or probe libraries such as
[Vicious](https://github.com/Mic92/vicious) do not follow this
some common widget or probe libraries do not follow this
advice currently and are known to cause input lag on some systems (but not all).
In both case, a warning like:
@ -53,6 +52,19 @@ provide a theme daemon. For more information about how to manage the
look and feel of applications, refer to the
[Arch Linux Wiki](https://wiki.archlinux.org/index.php/Category:Eye_candy).
### Awesome doesn't show up on my login screen
There have been cases where Awesome wasn't correctly registered with the display manager,
usually due to a missing `.desktop` file.
To fix such issues, copy `awesome.desktop` from the root of [the repository](https://github.com/awesomeWM/awesome/) to `/usr/share/xsessions/`.
```shell
curl https://raw.githubusercontent.com/awesomeWM/awesome/master/awesome.desktop | sudo tee /usr/share/xsessions/awesome.desktop
```
If you installed Awesome through a package manager, you might want to check if the package includes that file and, if not, notify the maintainer to add it as appropriate.
## Configuration
### How to change the default window management layout?

View File

@ -121,6 +121,8 @@ local function parse_files(paths, property_name, matcher, name_matcher)
local exp2 = matcher or "[-*]*[ ]*".. property_name ..".(.+)"
local exp3 = name_matcher or "[. ](.+)"
local count = 0
local names = {} -- Used to check for duplicates
local ret = {}
table.sort(paths)
@ -162,13 +164,47 @@ local function parse_files(paths, property_name, matcher, name_matcher)
"seems to be misformatted. Use `beautiful.namespace_name`"
)
else
table.insert(ret, {
file = file,
name = name:gsub("_", "_"),
link = get_link(file, var, var:match(exp3):gsub("_", "\\_")),
desc = buffer:gmatch("[-*/ \n]+([^\n.]*)")() or "",
mod = path_to_module(file),
})
local insert_name = name:gsub("_", "_")
local link = get_link(file, var, var:match(exp3):gsub("_", "\\_"))
local desc = buffer:gmatch("[-*/ \n]+([^\n.]*)")() or ""
local mod = path_to_module(file)
if names[insert_name] == nil then
count = count + 1
table.insert(ret, count, {
file = file,
name = insert_name,
link = link,
desc = desc,
mod = mod
})
names[insert_name] = count
else
link = link .. "(" .. mod .. ")"
if type(ret[names[insert_name]].link) ~= "table" then
ret[names[insert_name]].file = {
ret[names[insert_name]].file,
file
}
ret[names[insert_name]].link = {
ret[names[insert_name]].link .. " (" .. ret[names[insert_name]].mod .. ")",
link
}
ret[names[insert_name]].desc = {
ret[names[insert_name]].desc,
desc
}
ret[names[insert_name]].mod = {
ret[names[insert_name]].mod,
mod
}
else
table.insert(ret[names[insert_name]].file, file)
table.insert(ret[names[insert_name]].link, link)
table.insert(ret[names[insert_name]].desc, desc)
table.insert(ret[names[insert_name]].mod, mod)
end
end
end
buffer = ""
@ -190,7 +226,15 @@ local function create_table(entries, columns, prefix)
local line = " <tr>"
for _, column in ipairs(columns) do
line = line.."<td>"..entry[column].."</td>"
if type(entry[column]) == "table" then
line = line .. "<td><ul>"
for _,v in pairs(entry[column]) do
line = line .. "<li>" .. v .. "</li>"
end
line = line .. "</ul></td>"
else
line = line.."<td>"..entry[column].."</td>"
end
end
table.insert(lines, prefix..line.."</tr>\n")

View File

@ -1,80 +1,80 @@
* <table class='widget_list' border=1>
* <tr><td>num_glyphs </td></tr>
* <tr><td>cursor </td></tr>
* <tr><td>arrow </td></tr>
* <tr><td>based_arrow_down </td></tr>
* <tr><td>based_arrow_up </td></tr>
* <tr><td>boat </td></tr>
* <tr><td>bogosity </td></tr>
* <tr><td>bottom_left_corner </td></tr>
* <tr><td>bottom_right_corner </td></tr>
* <tr><td>bottom_side </td></tr>
* <tr><td>bottom_tee </td></tr>
* <tr><td>box_spiral </td></tr>
* <tr><td>center_ptr </td></tr>
* <tr><td>circle </td></tr>
* <tr><td>clock </td></tr>
* <tr><td>coffee_mug </td></tr>
* <tr><td>cross </td></tr>
* <tr><td>cross_reverse </td></tr>
* <tr><td>crosshair </td></tr>
* <tr><td>diamond_cross </td></tr>
* <tr><td>dot </td></tr>
* <tr><td>dotbox </td></tr>
* <tr><td>double_arrow </td></tr>
* <tr><td>draft_large </td></tr>
* <tr><td>draft_small </td></tr>
* <tr><td>draped_box </td></tr>
* <tr><td>exchange </td></tr>
* <tr><td>fleur </td></tr>
* <tr><td>gobbler </td></tr>
* <tr><td>gumby </td></tr>
* <tr><td>hand </td></tr>
* <tr><td>hand </td></tr>
* <tr><td>heart </td></tr>
* <tr><td>icon </td></tr>
* <tr><td>iron_cross </td></tr>
* <tr><td>left_ptr </td></tr>
* <tr><td>left_side </td></tr>
* <tr><td>left_tee </td></tr>
* <tr><td>leftbutton </td></tr>
* <tr><td>ll_angle </td></tr>
* <tr><td>lr_angle </td></tr>
* <tr><td>man </td></tr>
* <tr><td>middlebutton </td></tr>
* <tr><td>mouse </td></tr>
* <tr><td>pencil </td></tr>
* <tr><td>pirate </td></tr>
* <tr><td>plus </td></tr>
* <tr><td>question_arrow </td></tr>
* <tr><td>right_ptr </td></tr>
* <tr><td>right_side </td></tr>
* <tr><td>right_tee </td></tr>
* <tr><td>rightbutton </td></tr>
* <tr><td>rtl_logo </td></tr>
* <tr><td>sailboat </td></tr>
* <tr><td>sb_down_arrow </td></tr>
* <tr><td>sb_h_double_arrow </td></tr>
* <tr><td>sb_left_arrow </td></tr>
* <tr><td>sb_right_arrow </td></tr>
* <tr><td>sb_up_arrow </td></tr>
* <tr><td>sb_v_double_arrow </td></tr>
* <tr><td>shuttle </td></tr>
* <tr><td>sizing </td></tr>
* <tr><td>spider </td></tr>
* <tr><td>spraycan </td></tr>
* <tr><td>star </td></tr>
* <tr><td>target </td></tr>
* <tr><td>tcross </td></tr>
* <tr><td>top_left_arrow </td></tr>
* <tr><td>top_left_corner </td></tr>
* <tr><td>top_right_corner </td></tr>
* <tr><td>top_side </td></tr>
* <tr><td>top_tee </td></tr>
* <tr><td>trek </td></tr>
* <tr><td>ul_angle </td></tr>
* <tr><td>umbrella </td></tr>
* <tr><td>ur_angle </td></tr>
* <tr><td>watch </td></tr>
* <tr><td>xterm </td></tr>
* </table>
<div class='flex-list'>
* <div>num_glyphs</div>
* <div>arrow</div>
* <div>based_arrow_down</div>
* <div>based_arrow_up</div>
* <div>boat</div>
* <div>bogosity</div>
* <div>bottom_left_corner</div>
* <div>bottom_right_corner</div>
* <div>bottom_side</div>
* <div>bottom_tee</div>
* <div>box_spiral</div>
* <div>center_ptr</div>
* <div>circle</div>
* <div>clock</div>
* <div>coffee_mug</div>
* <div>cross</div>
* <div>crosshair</div>
* <div>cross_reverse</div>
* <div>cursor</div>
* <div>diamond_cross</div>
* <div>dotbox</div>
* <div>dot</div>
* <div>double_arrow</div>
* <div>draft_large</div>
* <div>draft_small</div>
* <div>draped_box</div>
* <div>exchange</div>
* <div>fleur</div>
* <div>gobbler</div>
* <div>gumby</div>
* <div>hand</div>
* <div>hand</div>
* <div>heart</div>
* <div>icon</div>
* <div>iron_cross</div>
* <div>leftbutton</div>
* <div>left_ptr</div>
* <div>left_side</div>
* <div>left_tee</div>
* <div>ll_angle</div>
* <div>lr_angle</div>
* <div>man</div>
* <div>middlebutton</div>
* <div>mouse</div>
* <div>pencil</div>
* <div>pirate</div>
* <div>plus</div>
* <div>question_arrow</div>
* <div>rightbutton</div>
* <div>right_ptr</div>
* <div>right_side</div>
* <div>right_tee</div>
* <div>rtl_logo</div>
* <div>sailboat</div>
* <div>sb_down_arrow</div>
* <div>sb_h_double_arrow</div>
* <div>sb_left_arrow</div>
* <div>sb_right_arrow</div>
* <div>sb_up_arrow</div>
* <div>sb_v_double_arrow</div>
* <div>shuttle</div>
* <div>sizing</div>
* <div>spider</div>
* <div>spraycan</div>
* <div>star</div>
* <div>target</div>
* <div>tcross</div>
* <div>top_left_arrow</div>
* <div>top_left_corner</div>
* <div>top_right_corner</div>
* <div>top_side</div>
* <div>top_tee</div>
* <div>trek</div>
* <div>ul_angle</div>
* <div>umbrella</div>
* <div>ur_angle</div>
* <div>watch</div>
* <div>xterm</div>
* </div>

View File

@ -1,24 +1,25 @@
--- Set a widget at a specific index, replace the current one.
--- Set a widget at a specific index, replacing the current one.
--
-- @tparam number index A widget or a widget index
-- @tparam widget widget2 The widget to take the place of the first one
-- @treturn boolean If the operation is successful
-- @tparam widget widget2 The widget to replace the previous one with
-- @treturn boolean Returns `true` if the widget was replaced successfully,
-- `false` otherwise.
-- @method set
-- @emits widget::replaced
-- @emitstparam widget::replaced widget self The layout.
-- @emitstparam widget::replaced widget widget index The inserted widget.
-- @emitstparam widget::replaced widget widget The inserted widget.
-- @emitstparam widget::replaced widget previous The previous widget.
-- @emitstparam widget::replaced number index The replaced index.
-- @interface layout
--- Replace the first instance of `widget` in the layout with `widget2`.
--
-- **Signal:** widget::replaced The argument is the new widget and the old one
-- and the index.
-- @tparam widget widget The widget to replace
-- @tparam widget widget2 The widget to replace `widget` with
-- @tparam[opt=false] boolean recursive Dig in all compatible layouts to find the widget.
-- @treturn boolean If the operation is successful
-- @tparam[opt=false] boolean recursive Recurse into all compatible layouts to
-- find the widget.
-- @treturn boolean Returns `true` if the widget was replaced successfully,
-- `false` otherwise.
-- @method replace_widget
-- @emits widget::replaced
-- @emitstparam widget::replaced widget self The layout.
@ -31,35 +32,44 @@
--
-- @tparam number index1 The first widget index
-- @tparam number index2 The second widget index
-- @treturn boolean If the operation is successful
-- @treturn boolean Returns `true` if the widget was replaced successfully,
-- `false` otherwise.
-- @method swap
-- @emits widget::swapped
-- @emitstparam widget::swapped widget self The layout.
-- @emitstparam widget::swapped widget widget1 index The first widget.
-- @emitstparam widget::swapped widget widget2 index The second widget.
-- @emitstparam widget::swapped widget widget1 The first widget.
-- @emitstparam widget::swapped widget widget2 The second widget.
-- @emitstparam widget::swapped number index1 The first index.
-- @emitstparam widget::swapped number index1 The second index.
-- @interface layout
--- Swap 2 widgets in a layout.
-- If widget1 is present multiple time, only the first instance is swapped
-- **Signal:** widget::swapped The arguments are both widgets and both (new) indexes.
-- if the layouts not the same, then only `widget::replaced` will be emitted.
--
-- If `widget1` is present multiple time, only the first instance is swapped.
--
-- Calls `set` internally, so the signal `widget::replaced` is emitted for both
-- widgets as well.
--
-- @tparam widget widget1 The first widget
-- @tparam widget widget2 The second widget
-- @tparam[opt=false] boolean recursive Dig in all compatible layouts to find the widget.
-- @treturn boolean If the operation is successful
-- @tparam[opt=false] boolean recursive Recurse into all compatible layouts to
-- find the widget.
-- @treturn boolean Returns `true` if the widget was replaced successfully,
-- `false` otherwise.
-- @method swap_widgets
-- @emits widget::swapped
-- @emitstparam widget::swapped widget self The layout.
-- @emitstparam widget::swapped widget widget1 index The first widget.
-- @emitstparam widget::swapped widget widget2 index The second widget.
-- @emitstparam widget::swapped widget widget1 The first widget.
-- @emitstparam widget::swapped widget widget2 The second widget.
-- @emitstparam widget::swapped number index1 The first index.
-- @emitstparam widget::swapped number index1 The second index.
-- @interface layout
-- @see set
--- Reset a ratio layout. This removes all widgets from the layout.
--- Reset the layout. This removes all widgets from the layout.
-- @method reset
-- @emits widget::reset
-- @emitstparam widget::reset widget self The layout.
-- @interface layout
-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80

View File

@ -0,0 +1,28 @@
--<table class='widget_list' border=1>
-- <tr>
-- <th align='center'>Format</th>
-- <th align='center'>Description</th>
-- </tr>
-- <tr><td>%a</td><td>The abbreviated weekday name according to the current locale</td></tr>
-- <tr><td>%A</td><td>the full weekday name according to the current locale</td></tr>
-- <tr><td>%b</td><td>The abbreviated month name according to the current locale</td></tr>
-- <tr><td>%B</td><td>The full month name according to the current locale</td></tr>
-- <tr><td>%d</td><td>The day of the month as a decimal number (range 01 to 31)</td></tr>
-- <tr><td>%e</td><td>The day of the month as a decimal number (range 1 to 31)</td></tr>
-- <tr><td>%F</td><td>Equivalent to %Y-%m-%d (the ISO 8601 date format)</td></tr>
-- <tr><td>%H</td><td>The hour as a decimal number using a 24-hour clock (range 00 to 23)</td></tr>
-- <tr><td>%I</td><td>The hour as a decimal number using a 12-hour clock (range 01 to 12)</td></tr>
-- <tr><td>%k</td><td>The hour (24-hour clock) as a decimal number (range 0 to 23); single digits are preceded by a blank</td></tr>
-- <tr><td>%l</td><td>The hour (12-hour clock) as a decimal number (range 1 to 12); single digits are preceded by a blank</td></tr>
-- <tr><td>%m</td><td>The month as a decimal number (range 01 to 12)</td></tr>
-- <tr><td>%M</td><td>The minute as a decimal number (range 00 to 59)</td></tr>
-- <tr><td>%p</td><td>Either "AM" or "PM" according to the given time value, or the corresponding strings for the current locale. Noon is treated as "PM" and midnight as "AM".</td></tr>
-- <tr><td>%P</td><td>Like %p but lowercase: "am" or "pm" or a corresponding string for the current locale</td></tr>
-- <tr><td>%r</td><td>The time in a.m. or p.m. notation</td></tr>
-- <tr><td>%R</td><td>The time in 24-hour notation (%H:%M)</td></tr>
-- <tr><td>%S</td><td>The second as a decimal number (range 00 to 60)</td></tr>
-- <tr><td>%T</td><td>The time in 24-hour notation with seconds (%H:%M:%S)</td></tr>
-- <tr><td>%y</td><td>The year as a decimal number without the century</td></tr>
-- <tr><td>%Y</td><td>The year as a decimal number including the century</td></tr>
-- <tr><td>%%</td><td>A literal % character</td></tr>
-- </table>

View File

@ -37,16 +37,16 @@ should be useful for you.
<div class="index_guides">
<div>
<a href="../doc/documentation/07-my-first-awesome.md.html">Getting started</a>
<a href="../doc/documentation/90-FAQ.md.html">FAQ</a>
<a href="../doc/documentation/01-readme.md.html">Read me</a>
<a href="../doc/documentation/89-NEWS.md.html">NEWS</a>
@{07-my-first-awesome.md|Getting started}
@{90-FAQ.md|FAQ}
@{01-readme.md|Read Me}
@{89-NEWS.md|NEWS}
</div>
<div>
<a href="../doc/documentation/03-declarative-layout.md.html">The widget system</a>
<a href="../doc/documentation/09-options.md.html">Startup options</a>
<a href="../doc/documentation/05-awesomerc.md.html">The default rc.lua</a>
<a href="../doc/documentation/08-client-layout-system.md.html">Window management</a>
@{03-declarative-layout.md|The widget system}
@{09-options.md|Startup options}
@{05-awesomerc.md|The default rc.lua}
@{08-client-layout-system.md|Window management}
</div>
</div>
@ -456,6 +456,25 @@ add_custom_tag {
},
}
-- Define the supermodule class.
-- This tag should be used at the module level. All properties from the
-- supermodule will be recursively added to the module by our ldoc template.
-- @supermodule supermodule
add_custom_tag {
name = "supermodule",
hidden = true,
auto_subtags = false
}
-- Mark the item ad hidden.
-- This tag should be used to hide items from the documentation.
-- @hidden
add_custom_tag {
name = "hidden",
hidden = true,
auto_subtags = false
}
-- More fitting section names
kind_names={topic='Documentation', module='Libraries', script='Sample files'}

File diff suppressed because it is too large Load Diff

Before

Width:  |  Height:  |  Size: 124 KiB

View File

@ -257,6 +257,23 @@ table th, table td {
border-bottom-width: 1px;
}
.flex-list {
display: flex;
flex-wrap: wrap;
}
.flex-list > div {
flex-grow: 1;
/* Use base width based on font size
to make sure text fits when zooming */
width: 9em;
text-align: center;
padding: 10px;
margin: 5px;
border: 1px solid rgb(193, 204, 228);
}
#about {
padding: 15px;
padding-left: 16em;
@ -503,3 +520,32 @@ pre .url { color: #272fc2; text-decoration: underline; }
.index_guides div a:hover {
background-color: #99b3ec;
}
/* Inheritance diagram */
.inheritance .inheritance__level {
list-style: none;
}
.inheritance .inheritance__level--root {
padding-left: 0;
}
.inheritance .inheritance__level__node::before {
content: "↳";
}
.inheritance .inheritance__level__node--root::before {
/* simulate the spacing of the arrow character */
content: " ";
white-space: pre;
}
.extra-header {
display: flex;
flex-direction: row;
flex-wrap: wrap;
}
.extra-header__section {
flex-grow: 1;
}

View File

@ -38,11 +38,86 @@
# local use_li = ldoc.use_li
# local display_name = ldoc.display_name
# local iter = ldoc.modules.iter
# local function M(txt,item) return ldoc.markup(txt,item,ldoc.plain) end
# local function un_cmake(s) return s:gsub("&#59", ";"):gsub("&#34", '"') end
# local function M(txt,item) return ldoc.markup(txt and un_cmake(txt) or nil,item,ldoc.plain) end
# local nowrap = ldoc.wrap and '' or 'nowrap'
# local html_space = function(s) return s:gsub(" ", "%%20") end
# local no_underscores = function(s) return s:gsub("_", " ") end
# --------- modules hierarchy -------------
# local hierarchy = {}
# local curr = module
# while curr do
# hierarchy[#hierarchy + 1] = curr
# -- no need to do anything more if there is no explicite @supermodule
# if not curr.tags.supermodule then break end
# local super = curr.tags.supermodule[1] -- only consider one way inheritance
# local found = false
# for kind, mods, type in ldoc.kinds() do
# for mod in mods() do
# local name = display_name(mod)
# if name == super then
# curr = mod
# found = true
# end
# if found then break end
# end
# if found then break end
# end
# if not found then curr = nil end
# end
# --------- merge modules content with supermodules -------------
# local all_module_kinds = {}
# if module then
# for kind,items in module.kinds() do
# local myitems = {}
# for item in items() do
# myitems[#myitems + 1] = item
# end
# all_module_kinds[#all_module_kinds + 1] = { kind = kind, items = myitems }
# end
# local filtered_kinds = { "Constructors", "Static module functions",
# "Functions", "Methods", "lib.gears.object.properties Functions" }
# for supermodule in iter(hierarchy) do
# for kind,items in supermodule.kinds() do
# local ignored = false
# for _,filtered in ldoc.pairs(filtered_kinds) do
# if kind == filtered then
# ignored = true
# break
# end
# end
# if not ignored then
# local curr_kind = nil
# for k in iter(all_module_kinds) do
# if k.kind == kind then
# curr_kind = k
# break
# end
# end
# if not curr_kind then
# curr_kind = { kind = kind, items = {} }
# all_module_kinds[#all_module_kinds + 1] = curr_kind
# end
# for item in items() do
# local tobeadded = true
# for i in iter(curr_kind.items) do
# if item.name == i.name then
# tobeadded = false
# break
# end
# end
# if tobeadded then
# item.inherited = true -- force inherited status
# curr_kind.items[#curr_kind.items + 1] = item
# end
# end
# end
# end
# end
# end
<!-- Menu -->
<div id="navigation">
@ -59,7 +134,8 @@
# if module and not ldoc.no_summary and #module.items > 0 then
<h2>Contents</h2>
<ul>
# for kind,items in module.kinds() do
# for k in iter(all_module_kinds) do
# local kind = k.kind
# if not kind:match("^ldoc_skip") then
<li><a href="#$(no_spaces(kind))">$(kind)</a></li>
# end
@ -109,41 +185,90 @@
<h1>Module: <code>$(module.name)</code></h1>
<p>$(M(module.summary,module))</p>
<p>$(M(module.description,module))</p>
# if module.tags.include then
$(M(ldoc.include_file(module.tags.include)))
# end
# if module.see then
# local li,il = use_li(module.see)
<h3>See also:</h3>
<ul>
# for see in iter(module.see) do
$(li)<a href="$(ldoc.href(see))">$(see.label)</a>$(il)
# end -- for
</ul>
# end -- if see
# if module.usage then
# local li,il = use_li(module.usage)
<h3>Usage:</h3>
<ul>
# for usage in iter(module.usage) do
$(li)<pre class="example">$(ldoc.escape(usage))</pre>$(il)
$(li)<pre class="example">$(ldoc.escape(un_cmake(usage)))</pre>$(il)
# end -- for
</ul>
# end -- if usage
<div class="extra-header">
# if module.tags.supermodule then
<div class="extra-header__section">
<h3>Class Hierarchy</h3>
<div class="inheritance">
# local function draw_hierary_recursifly(i)
# local is_root_level = (i == #hierarchy)
<ul class="inheritance__level $(is_root_level and 'inheritance__level--root' or '')">
<li class="inheritance__level__node $(is_root_level and 'inheritance__level__node--root' or '')">
# local mod = hierarchy[i]
# local name = display_name(hierarchy[i])
# if mod == module then
<strong>$(name)</strong>
# else
<a href="$(ldoc.ref_to_module(mod))">$(name)</a>
# end
</li>
# if i > 1 then
<li>
# draw_hierary_recursifly(i - 1)
</li>
# end
</ul>
# end -- function draw_hierary_recursifly
# draw_hierary_recursifly(#hierarchy)
</div>
</div>
# end -- module.tags.supermodule
# if module.tags.include then
$(M(ldoc.include_file(module.tags.include)))
# end
# if module.info then
<div class="extra-header__section">
<h3>Info:</h3>
<ul>
# for tag, value in module.info:iter() do
<li><strong>$(tag)</strong>: $(M(value,module))</li>
# if tag == 'Author' then
<li>
<strong>Originally authored by</strong>: $(M(value,module))<br />
<small>(Full contributors list available on
<a href="https://github.com/awesomeWM/awesome/graphs/contributors">
our github project)
</a></small>
</li>
# else
<li><strong>$(tag)</strong>: $(M(value,module))</li>
# end
# end
</ul>
</div>
# end -- if module.info
# if module.see then
<div class="extra-header__section">
# local li,il = use_li(module.see)
# local list_or_p =(#module.see > 1) and 'ul' or 'p'
<h3>See also:</h3>
<$(list_or_p)>
# for see in iter(module.see) do
$(li)<a href="$(ldoc.href(see))">$(see.label)</a>$(il)
# end -- for
</$(list_or_p)>
</div>
# end -- if module.see
</div>
# if not ldoc.no_summary then
# -- bang out the tables of item types for this module (e.g Functions, Tables, etc)
# local last_kind = ""
# for kind,items in module.kinds() do
# for k in iter(all_module_kinds) do
# local kind = k.kind
# if not kind:match("^ldoc_skip") then
# if last_kind ~= "" then
</table>
@ -151,8 +276,9 @@
<h2><a href="#$(no_spaces(kind))">$(kind)</a></h2>
<table class="function_list">
# end
# for item in items() do
# for item in iter(k.items) do if not item.tags.hidden then
# local dn = display_name(item)
# local inherited = (item.baseclass ~= module.name)
# if item.sanitize_type then item.sanitize_type(item, ldoc) end
<tr>
# if item.display_type and not item.compact_signature then
@ -166,14 +292,14 @@
# end
</td>
# end
<td colspan="$(item.inherited and 1 or 2)" class="summary">$(M(item.summary,item))</td>
# if item.inherited then
<td class="baseclass" $(nowrap)>
<td colspan="$(inherited and 1 or 2)" class="summary">$(M(item.summary,item))</td>
# if inherited then
<td class="baseclass" nowrap>
Inherited from $(item.baseclass)
</td>
# end
</tr>
# end -- for items
# end end -- for items
# last_kind = kind
#end -- for kinds
</table>
@ -187,7 +313,8 @@
# --- function parameters or table fields.
# local show_return = not ldoc.no_return_or_parms
# local show_parms, last_kind = show_return, ""
# for kind, items in module.kinds() do
# for k in iter(all_module_kinds) do
# local kind = k.kind
# local kitem = module.kinds:get_item(kind)
# local has_description = kitem and ldoc.descript(kitem) ~= ""
# if not kind:match("^ldoc_skip") then
@ -205,13 +332,13 @@
# end
# if kitem.usage then
<h3>Usage:</h3>
<pre class="example">$(ldoc.prettify(kitem.usage[1]))</pre>
<pre class="example">$(ldoc.prettify(un_cmake(kitem.usage[1])))</pre>
# end
# end
# if not kind:match("^ldoc_skip") then
<dl class="function">
# end
# for item in items() do
# for item in iter(k.items) do if not item.tags.hidden then
<dt>
<a name = "$(item.name)"></a>
<strong>$(display_name(item))</strong>
@ -225,8 +352,8 @@
<span class="proptype">$(item.display_type)</span>
# end
<span class="baseclass" $(nowrap)>
# if item.inherited then
&middot;&nbsp;Inherited from $(item.baseclass)
# if item.baseclass ~= module.name then
&middot;&nbsp;Inherited from $(M(item.baseclass, item))
# end
# if item.extra_summary then
# for _, col in ldoc.ipairs(item.extra_summary) do
@ -324,7 +451,7 @@
<h3>Usage:</h3>
<ul>
# for usage in iter(item.usage) do
$(li)<pre class="example">$(ldoc.prettify(usage))</pre>$(il)
$(li)<pre class="example">$(ldoc.prettify(un_cmake(usage)))</pre>$(il)
# end -- for
</ul>
# end -- if usage
@ -371,7 +498,7 @@
</span>
</dd>
# end -- for items
# end end -- for items
# last_kind = kind
# end -- for kinds
</dl>

2
ewmh.c
View File

@ -468,7 +468,7 @@ ewmh_process_client_message(xcb_client_message_event_t *ev)
lua_State *L = globalconf_get_lua_State();
luaA_object_push(L, globalconf.tags.tab[idx]);
lua_pushstring(L, "ewmh");
luaA_object_emit_signal(L, -1, "request::select", 1);
luaA_object_emit_signal(L, -2, "request::select", 1);
lua_pop(L, 1);
}
}

View File

@ -58,6 +58,28 @@ do
assert(root.object == root_object)
end
--- The old wallpaper only took native surfaces.
--
-- This was a problem for the test backend. The new function takes both
-- native surfaces and LGI-ified Cairo surfaces.
function root.wallpaper(pattern)
if not pattern then return root._wallpaper() end
-- Checking for type will either potentially `error()` or always
-- return `userdata`. This check will error() when the surface is
-- already native.
local err = pcall(function() return pattern._native end)
-- The presence of `root._write_string` means the test backend is
-- used. Avoid passing the native surface.
if err and not root._write_string then
return root._wallpaper(pattern._native)
else
return root._wallpaper(pattern)
end
end
-- root.bottons() used to be a capi function. However this proved confusing
-- as rc.lua used `awful.button` and `root.buttons()` used capi.button. There
-- was a little documented hack to "flatten" awful.button into a pair of

View File

@ -595,18 +595,42 @@ function client.object.move_to_screen(self, s)
end
end
--- Tag a client with the set of current tags.
--- Find suitable tags for newly created clients.
--
-- In most cases, the functionality you're actually looking for as a user will
-- either be
--
-- c:tags(c.screen.selected_tags)
--
-- or
--
-- local s = awful.screen.focused()
-- c:move_to_screen(s)
-- c:tags(s.selected_tags)
--
-- Despite its naming, this is primarily used to tag newly created clients.
-- As such, this method has no effect when applied to a client that already has
-- tags assigned (except for emitting `property::tag`).
--
-- Additionally, while it is a rare case, if the client's screen has no selected
-- tags at the point of calling this method, it will fall back to the screen's
-- full set of tags.
--
-- @method to_selected_tags
-- @see screen.selected_tags
function client.object.to_selected_tags(self)
local tags = {}
-- From the client's current tags, find the ones that
-- belong to the client's screen
for _, t in ipairs(self:tags()) do
if get_screen(t.screen) == get_screen(self.screen) then
table.insert(tags, t)
end
end
-- If no tags were found,
-- choose the screen's selected tags, if any, or all of the screens tags
if self.screen then
if #tags == 0 then
tags = self.screen.selected_tags
@ -617,6 +641,7 @@ function client.object.to_selected_tags(self)
end
end
-- Prevent clients from becoming untagged
if #tags ~= 0 then
self:tags(tags)
end
@ -1134,10 +1159,13 @@ end
--- Change window factor of a client.
--
-- This will emit `property::windowfact` on the specific tag object
-- `c.screen.selected_tag`.
--
-- @legacylayout awful.client.incwfact
-- @tparam number add Amount to increase/decrease the client's window factor.
-- @tparam number add Amount to increase/decrease the client's window factor by.
-- Should be between `-current_window_factor` and something close to
-- infinite. The normalisation then ensures that the sum of all factors is 1.
-- infinite. Normalisation then ensures that the sum of all factors is 1.
-- @tparam client c the client.
-- @emits property::windowfact
function client.incwfact(add, c)

View File

@ -196,7 +196,7 @@ function focus.global_bydirection(dir, c, stacked)
local scr = get_screen(sel and sel.screen or screen.focused())
-- change focus inside the screen
focus.bydirection(dir, sel)
focus.bydirection(dir, sel, stacked)
-- if focus not changed, we must change screen
if sel == capi.client.focus then

View File

@ -1,8 +1,8 @@
---------------------------------------------------------------------------
--- D-Bus module for awful.
--
-- This module simply request the org.awesomewm.awful name on the D-Bus
-- for futur usage by other awful modules.
-- This module simply requests the org.awesomewm.awful name on the D-Bus
-- for future usage by other awful modules.
--
-- @author Julien Danjou &lt;julien@danjou.info&gt;
-- @copyright 2009 Julien Danjou

View File

@ -438,7 +438,10 @@ function widget.new(args)
end
function widget_instance:_create_group_columns(column_layouts, group, keys, s, wibox_height)
local line_height = beautiful.get_font_height(self.font)
local line_height = math.max(
beautiful.get_font_height(self.font),
beautiful.get_font_height(self.description_font)
)
local group_label_height = line_height + self.group_margin
-- -1 for possible pagination:
local max_height_px = wibox_height - group_label_height
@ -472,7 +475,7 @@ function widget.new(args)
table.insert(((i<available_height_items) and new_keys or overlap_leftovers), keys[i])
end
keys = new_keys
table.insert(keys, {key=markup.fg(self.modifiers_fg, ""), description=""})
table.insert(keys, {key="", description=""})
end
if not current_column then
current_column = {layout=wibox.layout.fixed.vertical()}
@ -481,32 +484,39 @@ function widget.new(args)
local function insert_keys(_keys, _add_new_column)
local max_label_width = 0
local max_label_content = ""
local joined_labels = ""
for i, key in ipairs(_keys) do
local length = string.len(key.key or '') + string.len(key.description or '')
local modifiers = key.mod
if not modifiers or modifiers == "none" then
modifiers = ""
else
length = length + string.len(modifiers) + 1 -- +1 for "+" character
modifiers = markup.fg(self.modifiers_fg, modifiers.."+")
end
local key_label = ""
if key.keylist and #key.keylist > 1 then
for each_key_idx, each_key in ipairs(key.keylist) do
key_label = key_label .. gstring.xml_escape(each_key)
if each_key_idx ~= #key.keylist then
key_label = key_label .. markup.fg(self.modifiers_fg, '/')
end
end
elseif key.key then
key_label = gstring.xml_escape(key.key)
end
local rendered_hotkey = markup.font(self.font,
modifiers .. (key.key or "") .. " "
modifiers .. key_label .. " "
) .. markup.font(self.description_font,
key.description or ""
)
if length > max_label_width then
max_label_width = length
max_label_content = rendered_hotkey
local label_width = wibox.widget.textbox.get_markup_geometry(rendered_hotkey, s).width
if label_width > max_label_width then
max_label_width = label_width
end
joined_labels = joined_labels .. rendered_hotkey .. (i~=#_keys and "\n" or "")
end
current_column.layout:add(wibox.widget.textbox(joined_labels))
local max_width, _ = wibox.widget.textbox(max_label_content):get_preferred_size(s)
max_width = max_width + self.group_margin
if not current_column.max_width or max_width > current_column.max_width then
local max_width = max_label_width + self.group_margin
if not current_column.max_width or (max_width > current_column.max_width) then
current_column.max_width = max_width
end
-- +1 for group label:

View File

@ -35,6 +35,7 @@ local ret = {
tooltip = require("awful.tooltip");
permissions = require("awful.permissions");
titlebar = require("awful.titlebar");
wallpaper = require("awful.wallpaper");
rules = require("awful.rules");
popup = require("awful.popup");
spawn = require("awful.spawn");

View File

@ -1,6 +1,27 @@
---------------------------------------------------------------------------
--- Create easily new key objects ignoring certain modifiers.
--
-- A key object can be used by @{awful.keyboard} and @{client} to define
-- keybindings.
--
-- Use awful.key to define a keybinding
-- ---
--
-- This example shows how to define a basic key object:
--
-- @DOC_text_awful_key_constructor_default_EXAMPLE@
--
-- This example shows how to define the same basic key object with the
-- declarative pattern:
--
-- @DOC_text_awful_key_constructor_declarative_EXAMPLE@
--
-- This second example of a key definition uses the numrow keygroup. In this
-- example, we define a key object, that select the tag to show according to
-- the key index from the numrow.
--
-- @DOC_text_awful_key_constructor_keygroup_EXAMPLE@
--
-- @author Julien Danjou &lt;julien@danjou.info&gt;
-- @author Emmanuel Lepage Vallee &lt;elv1313@gmail.com&gt;
-- @copyright 2018 Emmanuel Lepage Vallee
@ -24,21 +45,6 @@ local gobject = require("gears.object")
-- @property key
-- @param string
--- A group of keys.
--
-- The valid keygroups are:
--
-- * **numrow**: The row above the letters in the US PC-105/PC-104 keyboards
-- and its derivative. This is usually the number 1-9 followed by 0.
-- * **arrows**: The Left/Right/Top/Bottom keys usually located right of the
-- spacebar.
-- * **fkeys**: The keys F1 through F12 located at the topmost row of any
-- keyboard, plus F13 through F35 on specialist keyboards.
-- * **numpad**: The number keys on the keypad to the right of the letters and
-- the arrow keys. Not present in every keyboard.
--
-- @property keygroup
--- The table of modifier keys.
--
-- A modifier, such as `Control` are a predetermined set of keys that can be
@ -99,6 +105,32 @@ local key = { mt = {}, hotkeys = {} }
local reverse_map = setmetatable({}, {__mode="k"})
--- The keygroups names.
--
-- It can be used instead of keygroup names.
--
-- Values associated to each property of this table are string:
--
-- - **NUMROW** = `"numrow"`: The row above the letters in the US PC-105/PC-104 keyboards and
-- its derivative. This is usually the number 1-9 followed by 0.
--
-- - **ARROWS** = `"arrows"`: The Left/Right/Top/Bottom keys usually located right of the
-- spacebar.
--
-- - **FKEYS** = `"fkeys"`: The keys F1 through F12 located at the topmost row of any
-- keyboard, plus F13 through F35 on specialist keyboards.
--
-- - **NUMPAD** = `"numpad"`: The number keys on the keypad to the right of the letters and
-- the arrow keys. Not present in every keyboard.
--
-- @table keygroup
key.keygroup = {
NUMROW = 'numrow', -- The number row.
ARROWS = 'arrows', -- The directionnal arrows.
FKEYS = 'fkeys', -- The function keys.
NUMPAD = 'numpad', -- The numpad keys.
}
function key:set_key(k)
for _, v in ipairs(self) do
v.key = k
@ -216,11 +248,13 @@ end
--
-- @constructorfct2 awful.key
-- @tparam table args
-- @tparam function args.key The key to trigger an event. It can be the character
-- itself of `#+keycode` (**mandatory**).
-- @tparam function args.modifiers A list of modifier keys. Valid modifiers are:
-- `Any`, `Mod1`, Mod2`, `Mod3`, `Mod4`, `Mod5`, `Shift`, `Lock` and `Control`.
-- This argument is (**mandatory**).
-- @tparam string args.key The key to trigger an event. It can be the character
-- itself of `#+keycode`.
-- @tparam[opt] string args.keygroup The keygroup to trigger an event. This
-- parameter must be used as a replacement for the `key` parameter. See
-- @{awful.key.keygroup}.
-- @tparam table args.modifiers A list of modifier keys. Valid modifiers are:
-- `Any`, `Mod1`, Mod2`, `Mod3`, `Mod4`, `Mod5`, `Shift`, `Lock` and `Control`.
-- @tparam function args.on_press Callback for when the key is pressed.
-- @tparam function args.on_release Callback for when the key is released.

View File

@ -474,12 +474,20 @@ function keygrabber:start()
end
--- Stop the keygrabber.
--
-- Also stops any `timeout`.
--
-- @method stop
-- @emits stopped
-- @emits property::current_instance
function keygrabber:stop(_stop_key, _stop_mods) -- (at)function disables ldoc params
keygrab.stop(self.grabber)
local timer = self._private.timer
if timer and timer.started then
timer:stop()
end
if self.stop_callback then
self.stop_callback(
self.current_instance, _stop_key, _stop_mods, self.sequence
@ -706,12 +714,13 @@ function keygrab.run_with_keybindings(args)
else
gdebug.print_warning(
"The hook's 3rd parameter has to be a function. " ..
gdebug.dump(v or {})
gdebug.dump_return(v or {})
)
end
else
gdebug.print_warning(
"The keybindings should be awful.key objects".. gdebug.dump(v or {})
"The keybindings should be awful.key objects" ..
gdebug.dump_return(v or {})
)
end
end

View File

@ -35,28 +35,29 @@ mouse.wibox = {}
--
-- @DOC_screen_client_snap_EXAMPLE@
--
-- @tfield integer awful.mouse.snap.default_distance
-- @tfield integer snap.default_distance
-- @tparam[opt=8] integer default_distance
-- @see awful.mouse.snap
--- The default distance before activating screen edge snap.
-- @tfield integer awful.mouse.snap.aerosnap_distance
-- @tparam[opt=16] integer default_distance
-- @tfield integer snap.aerosnap_distance
-- @tparam[opt=16] integer aerosnap_distance
-- @see awful.mouse.snap
--- Enable screen edges snapping.
--
--
--
--@DOC_awful_placement_aero_snap_EXAMPLE@
--
-- @tfield[opt=true] boolean awful.mouse.snap.edge_enabled
-- @tfield[opt=true] boolean snap.edge_enabled
-- @tparam boolean edge_enabled
--- Enable client to client snapping.
-- @tfield[opt=true] boolean awful.mouse.snap.client_enabled
-- @tfield[opt=true] boolean snap.client_enabled
-- @tparam boolean client_enabled
--- Enable changing tag when a client is dragged to the edge of the screen.
-- @tfield[opt=false] integer awful.mouse.drag_to_tag.enabled
-- @tfield[opt=false] boolean drag_to_tag.enabled
-- @tparam boolean enabled
--- The snap outline background color.
-- @beautiful beautiful.snap_bg
@ -341,9 +342,9 @@ function mouse.remove_client_mousebinding(button)
return false
end
for _, b in ipairs {"left", "right", "middle"} do
for k, b in ipairs {"left", "middle", "right"} do
mouse.object["is_".. b .."_mouse_button_pressed"] = function()
return capi.mouse.coords().buttons[1]
return capi.mouse.coords().buttons[k]
end
end
@ -367,6 +368,10 @@ end)
capi.mouse.set_index_miss_handler(function(_,key)
if mouse.object["get_"..key] then
return mouse.object["get_"..key]()
elseif mouse.object[key] and key:sub(1, 3) == "is_" then
return mouse.object[key]()
elseif mouse.object[key] then
return mouse.object[key]
else
return props[key]
end

View File

@ -184,6 +184,10 @@ local function compose(...)
attach(d, ret, args)
end
-- Cleanup the override because otherwise it might leak into
-- future calls.
rawset(args, "override_geometry", nil)
return last_geo, rets
end, "compose")
@ -276,6 +280,30 @@ local outer_positions = {
bottom_middle = function(r, w, _) return {x=r.x-w/2+r.width/2, y=r.y }, "middle" end,
}
-- Map the opposite side for a string
local opposites = {
top = "bottom",
bottom = "top",
left = "right",
right = "left",
width = "height",
height = "width",
x = "y",
y = "x",
}
-- List reletvant sides for each orientation.
local struts_orientation_to_sides = {
horizontal = { "top" , "bottom" },
vertical = { "left", "right" }
}
-- Map orientation to the length components (width/height).
local orientation_to_length = {
horizontal = "width",
vertical = "height"
}
--- Add a context to the arguments.
-- This function extend the argument table. The context is used by some
-- internal helper methods. If there already is a context, it has priority and
@ -477,8 +505,8 @@ wibox_update_strut = function(d, position, args)
end
-- Detect horizontal or vertical drawables
local geo = area_common(d)
local vertical = geo.width < geo.height
local geo = area_common(d)
local orientation = geo.width < geo.height and "vertical" or "horizontal"
-- Look into the `position` string to find the relevants sides to crop from
-- the workarea
@ -486,17 +514,12 @@ wibox_update_strut = function(d, position, args)
local m = get_decoration(args)
if vertical then
for _, v in ipairs {"right", "left"} do
if (not position) or position:match(v) then
struts[v] = geo.width + m[v]
end
end
else
for _, v in ipairs {"top", "bottom"} do
if (not position) or position:match(v) then
struts[v] = geo.height + m[v]
end
for _, v in ipairs(struts_orientation_to_sides[orientation]) do
if (not position) or position:match(v) then
-- Add the "short" rectangle lenght then the above and below margins.
struts[v] = geo[opposites[orientation_to_length[orientation]]]
+ m[v]
+ m[opposites[v]]
end
end

View File

@ -19,6 +19,7 @@
-- @author Emmanuel Lepage Vallee
-- @copyright 2016 Emmanuel Lepage Vallee
-- @popupmod awful.popup
-- @supermodule wibox
---------------------------------------------------------------------------
local wibox = require( "wibox" )
local gtable = require( "gears.table" )

View File

@ -114,7 +114,6 @@ local io = io
local table = table
local math = math
local ipairs = ipairs
local pcall = pcall
local capi =
{
selection = selection
@ -258,6 +257,11 @@ local function history_add(id, command)
end
local function have_multibyte_char_at(text, position)
return text:sub(position, position):wlen() == -1
end
--- Draw the prompt text with a cursor.
-- @tparam table args The table of arguments.
-- @field text The text.
@ -285,10 +289,14 @@ local function prompt_text_with_cursor(args)
text_start = gstring.xml_escape(text)
text_end = ""
else
char = gstring.xml_escape(text:sub(args.cursor_pos, args.cursor_pos))
local offset = 0
if have_multibyte_char_at(text, args.cursor_pos) then
offset = 1
end
char = gstring.xml_escape(text:sub(args.cursor_pos, args.cursor_pos + offset))
spacer = " "
text_start = gstring.xml_escape(text:sub(1, args.cursor_pos - 1))
text_end = gstring.xml_escape(text:sub(args.cursor_pos + 1))
text_end = gstring.xml_escape(text:sub(args.cursor_pos + 1 + offset))
end
local cursor_color = gcolor.ensure_pango_color(args.cursor_color)
@ -544,9 +552,9 @@ function prompt.run(args, textbox, exe_callback, completion_callback,
local function update()
textbox:set_font(font)
textbox:set_markup(prompt_text_with_cursor{
text = command, text_color = inv_col, cursor_color = cur_col,
cursor_pos = cur_pos, cursor_ul = cur_ul, selectall = selectall,
prompt = prettyprompt, highlighter = highlighter })
text = command, text_color = inv_col, cursor_color = cur_col,
cursor_pos = cur_pos, cursor_ul = cur_ul, selectall = selectall,
prompt = prettyprompt, highlighter = highlighter })
end
grabber = keygrabber.run(
@ -663,6 +671,9 @@ function prompt.run(args, textbox, exe_callback, completion_callback,
elseif key == "b" then
if cur_pos > 1 then
cur_pos = cur_pos - 1
if have_multibyte_char_at(command, cur_pos) then
cur_pos = cur_pos - 1
end
end
elseif key == "d" then
if cur_pos <= #command then
@ -711,12 +722,20 @@ function prompt.run(args, textbox, exe_callback, completion_callback,
end
elseif key == "f" then
if cur_pos <= #command then
cur_pos = cur_pos + 1
if have_multibyte_char_at(command, cur_pos) then
cur_pos = cur_pos + 2
else
cur_pos = cur_pos + 1
end
end
elseif key == "h" then
if cur_pos > 1 then
command = command:sub(1, cur_pos - 2) .. command:sub(cur_pos)
cur_pos = cur_pos - 1
local offset = 0
if have_multibyte_char_at(command, cur_pos - 1) then
offset = 1
end
command = command:sub(1, cur_pos - 2 - offset) .. command:sub(cur_pos)
cur_pos = cur_pos - 1 - offset
end
elseif key == "k" then
command = command:sub(1, cur_pos - 1)
@ -844,8 +863,12 @@ function prompt.run(args, textbox, exe_callback, completion_callback,
cur_pos = #command + 1
elseif key == "BackSpace" then
if cur_pos > 1 then
command = command:sub(1, cur_pos - 2) .. command:sub(cur_pos)
cur_pos = cur_pos - 1
local offset = 0
if have_multibyte_char_at(command, cur_pos - 1) then
offset = 1
end
command = command:sub(1, cur_pos - 2 - offset) .. command:sub(cur_pos)
cur_pos = cur_pos - 1 - offset
end
elseif key == "Delete" then
command = command:sub(1, cur_pos - 1) .. command:sub(cur_pos + 1)
@ -889,22 +912,7 @@ function prompt.run(args, textbox, exe_callback, completion_callback,
selectall = nil
end
local success = pcall(update)
while not success do
-- TODO UGLY HACK TODO
-- Setting the text failed. Most likely reason is that the user
-- entered a multibyte character and pressed backspace which only
-- removed the last byte. Let's remove another byte.
if cur_pos <= 1 then
-- No text left?!
break
end
command = command:sub(1, cur_pos - 2) .. command:sub(cur_pos)
cur_pos = cur_pos - 1
success = pcall(update)
end
update()
if changed_callback then
changed_callback(command)
end

View File

@ -224,15 +224,56 @@ local capi =
client = client,
}
local lgi = require("lgi")
local Gio = lgi.Gio
local GLib = lgi.GLib
local util = require("awful.util")
local gtable = require("gears.table")
local gtimer = require("gears.timer")
local aclient = require("awful.client")
local watcher = require("gears.watcher")
local protected_call = require("gears.protected_call")
local spawn = {}
local end_of_file
do
-- API changes, bug fixes and lots of fun. Figure out how a EOF is signalled.
local input
if not pcall(function()
-- No idea when this API changed, but some versions expect a string,
-- others a table with some special(?) entries
input = Gio.DataInputStream.new(Gio.MemoryInputStream.new_from_data(""))
end) then
input = Gio.DataInputStream.new(Gio.MemoryInputStream.new_from_data({}))
end
local line, length = input:read_line()
if not line then
-- Fixed in 2016: NULL on the C side is transformed to nil in Lua
end_of_file = function(arg)
return not arg
end
elseif tostring(line) == "" and #line ~= length then
-- "Historic" behaviour for end-of-file:
-- - NULL is turned into an empty string
-- - The length variable is not initialized
-- It's highly unlikely that the uninitialized variable has value zero.
-- Use this hack to detect EOF.
end_of_file = function(arg1, arg2)
return #arg1 ~= arg2
end
else
assert(tostring(line) == "", "Cannot determine how to detect EOF")
-- The above uninitialized variable was fixed and thus length is
-- always 0 when line is NULL in C. We cannot tell apart an empty line and
-- EOF in this case.
require("gears.debug").print_warning("Cannot reliably detect EOF on an "
.. "GIOInputStream with this LGI version")
end_of_file = function(arg)
return tostring(arg) == ""
end
end
end
local function hash_command(cmd, rules)
rules = rules or {}
cmd = type(cmd) == "string" and cmd or table.concat(cmd, ';')
@ -351,10 +392,38 @@ end
-- @treturn[1] Integer the PID of the forked process.
-- @treturn[2] string Error message.
-- @staticfct awful.spawn.with_line_callback
function spawn.with_line_callback(cmd, callbacks)
local stdout_callback, stderr_callback, done_callback, exit_callback =
callbacks.stdout, callbacks.stderr, callbacks.output_done, callbacks.exit
local have_stdout, have_stderr = stdout_callback ~= nil, stderr_callback ~= nil
local pid, _, stdin, stdout, stderr = capi.awesome.spawn(cmd,
false, false, have_stdout, have_stderr, exit_callback)
if type(pid) == "string" then
-- Error
return pid
end
-- It is still "officially" in `awful.spawn`, but since `gears.watcher`
-- depends on it, the implementation lives there.
spawn.with_line_callback = watcher._with_line_callback
local done_before = false
local function step_done()
if have_stdout and have_stderr and not done_before then
done_before = true
return
end
if done_callback then
done_callback()
end
end
if have_stdout then
spawn.read_lines(Gio.UnixInputStream.new(stdout, true),
stdout_callback, step_done, true)
end
if have_stderr then
spawn.read_lines(Gio.UnixInputStream.new(stderr, true),
stderr_callback, step_done, true)
end
assert(stdin == nil)
return pid
end
--- Asynchronously spawn a program and capture its output.
-- (wraps `spawn.with_line_callback`).
@ -369,10 +438,43 @@ spawn.with_line_callback = watcher._with_line_callback
-- @treturn[2] string Error message.
-- @see spawn.with_line_callback
-- @staticfct awful.spawn.easy_async
-- It is still "officially" in `awful.spawn`, but since `gears.watcher`
-- depends on it, the implementation lives there.
spawn.easy_async = watcher._easy_async
function spawn.easy_async(cmd, callback)
local stdout = ''
local stderr = ''
local exitcode, exitreason
local function parse_stdout(str)
stdout = stdout .. str .. "\n"
end
local function parse_stderr(str)
stderr = stderr .. str .. "\n"
end
local function done_callback()
return callback(stdout, stderr, exitreason, exitcode)
end
local exit_callback_fired = false
local output_done_callback_fired = false
local function exit_callback(reason, code)
exitcode = code
exitreason = reason
exit_callback_fired = true
if output_done_callback_fired then
return done_callback()
end
end
local function output_done_callback()
output_done_callback_fired = true
if exit_callback_fired then
return done_callback()
end
end
return spawn.with_line_callback(
cmd, {
stdout=parse_stdout,
stderr=parse_stderr,
exit=exit_callback,
output_done=output_done_callback
})
end
--- Call `spawn.easy_async` with a shell.
-- This calls `cmd` with `$SHELL -c` (via `awful.util.shell`).
@ -399,8 +501,42 @@ end
-- operation finishes (e.g. due to end of file).
-- @tparam[opt=false] boolean close Should the stream be closed after end-of-file?
-- @staticfct awful.spawn.read_lines
function spawn.read_lines(input_stream, line_callback, done_callback, close)
local stream = Gio.DataInputStream.new(input_stream)
local function done()
if close then
stream:close()
end
stream:set_buffer_size(0)
if done_callback then
protected_call(done_callback)
end
end
local start_read, finish_read
start_read = function()
stream:read_line_async(GLib.PRIORITY_DEFAULT, nil, finish_read)
end
finish_read = function(obj, res)
local line, length = obj:read_line_finish(res)
if type(length) ~= "number" then
-- Error
print("Error in awful.spawn.read_lines:", tostring(length))
done()
elseif end_of_file(line, length) then
-- End of file
done()
else
-- Read a line
-- This needs tostring() for older lgi versions which returned
-- "GLib.Bytes" instead of Lua strings (I guess)
protected_call(line_callback, tostring(line))
spawn.read_lines = watcher._read_lines
-- Read the next line
start_read()
end
end
start_read()
end
-- When a command should only be executed once or only have a single instance,
-- track the SNID set on them to prevent multiple execution.

View File

@ -349,7 +349,7 @@ function tag.find_fallback(screen, invalids)
end
end
--- When all clients are removed from the tag.
--- Emitted when all clients are removed from the tag.
-- @signal cleared
-- @see clear
@ -1841,20 +1841,25 @@ capi.client.connect_signal("untagged", client_untagged)
capi.client.connect_signal("tagged", client_tagged)
capi.tag.connect_signal("request::select", tag.object.view_only)
--- True when a tagged client is urgent
--- Emitted when the number of urgent clients on this tag changes.
-- @signal property::urgent
-- @param boolean `true` if there is at least one urgent client on the tag.
-- @see client.urgent
--- The number of urgent tagged clients
--- Emitted when the number of urgent clients on this tag changes.
-- @signal property::urgent_count
-- @param integer The number of urgent clients on the tag.
-- @see client.urgent
--- Emitted when a screen is removed.
--
-- This can be used to salvage existing tags by moving them to a new
-- screen (or creating a virtual screen). By default, there is no
-- handler for this request. The tags will be deleted. To prevent
-- this, an handler for this request must simply set a new screen
-- screen (or creating a virtual screen).
--
-- By default, there is no handler for this request and the tags will be deleted.
-- To prevent this, an handler for this request must simply set a new screen
-- for the tag.
--
-- @signal request::screen
-- @tparam string context Why it was called.

View File

@ -505,20 +505,33 @@ local function get_children_by_id(self, name)
end
--- Get a client's titlebar.
-- @tparam client c The client for which a titlebar is wanted.
--- Create a new titlebar for the given client.
--
-- Every client can hold up to four titlebars, one for each side (i.e. each
-- value of `args.position`).
--
-- If this constructor is called again with the same
-- values for the client (`c`) and the titlebar position (`args.position`),
-- the previous titlebar will be removed and replaced by the new one.
--
-- @DOC_awful_titlebar_constructor_EXAMPLE@
--
-- @tparam client c The client the titlebar will be attached to.
-- @tparam[opt={}] table args A table with extra arguments for the titlebar.
-- @tparam[opt=font.height*1.5] number args.size The height of the titlebar.
-- @tparam[opt=top] string args.position" values are `top`,
-- `left`, `right` and `bottom`.
-- @tparam[opt=top] string args.bg_normal
-- @tparam[opt=top] string args.bg_focus
-- @tparam[opt=top] string args.bgimage_normal
-- @tparam[opt=top] string args.bgimage_focus
-- @tparam[opt=top] string args.fg_normal
-- @tparam[opt=top] string args.fg_focus
-- @tparam[opt=top] string args.font
-- @tparam[opt=font.height*1.5] number args.size The size of the titlebar. Will
-- be interpreted as `height` for horizontal titlebars or as `width` for
-- vertical titlebars.
-- @tparam[opt="top"] string args.position Possible values are `"top"`,
-- `"left"`, `"right"` and `"bottom"`.
-- @tparam[opt] string args.bg_normal
-- @tparam[opt] string args.bg_focus
-- @tparam[opt] string args.bgimage_normal
-- @tparam[opt] string args.bgimage_focus
-- @tparam[opt] string args.fg_normal
-- @tparam[opt] string args.fg_focus
-- @tparam[opt] string args.font
-- @constructorfct awful.titlebar
-- @treturn wibox.drawable The newly created titlebar object.
local function new(c, args)
args = args or {}
local position = args.position or "top"
@ -578,10 +591,10 @@ local function new(c, args)
return ret
end
--- Show a client's titlebar.
--- Show the client's titlebar.
-- @param c The client whose titlebar is modified
-- @param[opt] position The position of the titlebar. Must be one of "left",
-- "right", "top", "bottom". Default is "top".
-- @tparam[opt="top"] string position The position of the titlebar. Must be one of `"left"`,
-- `"right"`, `"top"`, `"bottom"`.
-- @staticfct awful.titlebar.show
-- @request client titlebars show granted Called when `awful.titlebar.show` is
-- called.
@ -594,20 +607,20 @@ function titlebar.show(c, position)
new(c, args)
end
--- Hide a client's titlebar.
--- Hide the client's titlebar.
-- @param c The client whose titlebar is modified
-- @param[opt] position The position of the titlebar. Must be one of "left",
-- "right", "top", "bottom". Default is "top".
-- @tparam[opt="top"] string position The position of the titlebar. Must be one of `"left"`,
-- `"right"`, `"top"`, `"bottom"`.
-- @staticfct awful.titlebar.hide
function titlebar.hide(c, position)
position = position or "top"
get_titlebar_function(c, position)(c, 0)
end
--- Toggle a client's titlebar, hiding it if it is visible, otherwise showing it.
--- Toggle the client's titlebar, hiding it if it is visible, otherwise showing it.
-- @param c The client whose titlebar is modified
-- @param[opt] position The position of the titlebar. Must be one of "left",
-- "right", "top", "bottom". Default is "top".
-- @tparam[opt="top"] string position The position of the titlebar. Must be one of `"left"`,
-- `"right"`, `"top"`, `"bottom"`.
-- @staticfct awful.titlebar.toggle
-- @request client titlebars toggle granted Called when `awful.titlebar.toggle` is
-- called.
@ -649,9 +662,12 @@ local function update_on_signal(c, signal, widget)
table.insert(widgets, widget)
end
--- Create a new titlewidget. A title widget displays the name of a client.
--- Create a new title widget.
--
-- A title widget displays the name of a client.
-- Please note that this returns a textbox and all of textbox' API is available.
-- This way, you can e.g. modify the font that is used.
--
-- @param c The client for which a titlewidget should be created.
-- @return The title widget.
-- @constructorfct awful.titlebar.widget.titlewidget
@ -667,9 +683,12 @@ function titlebar.widget.titlewidget(c)
return ret
end
--- Create a new icon widget. An icon widget displays the icon of a client.
--- Create a new icon widget.
--
-- An icon widget displays the icon of a client.
-- Please note that this returns an imagebox and all of the imagebox' API is
-- available. This way, you can e.g. disallow resizes.
--
-- @param c The client for which an icon widget should be created.
-- @return The icon widget.
-- @constructorfct awful.titlebar.widget.iconwidget
@ -677,13 +696,16 @@ function titlebar.widget.iconwidget(c)
return clienticon(c)
end
--- Create a new button widget. A button widget displays an image and reacts to
--- Create a new button widget.
--
-- A button widget displays an image and reacts to
-- mouse clicks. Please note that the caller has to make sure that this widget
-- gets redrawn when needed by calling the returned widget's update() function.
-- gets redrawn when needed by calling the returned widget's `:update()` method.
-- The selector function should return a value describing a state. If the value
-- is a boolean, either "active" or "inactive" are used. The actual image is
-- then found in the theme as "titlebar_[name]_button_[normal/focus]_[state]".
-- is a boolean, either `"active"` or `"inactive"` are used. The actual image is
-- then found in the theme as `titlebar_[name]_button_[normal/focus]_[state]`.
-- If that value does not exist, the focused state is ignored for the next try.
--
-- @param c The client for which a button is created.
-- @tparam string name Name of the button, used for accessing the theme and
-- in the tooltip.

View File

@ -40,6 +40,7 @@
-- @author Sébastien Gross &lt;seb•ɱɩɲʋʃ•awesome•ɑƬ•chezwam•ɖɵʈ•org&gt;
-- @copyright 2009 Sébastien Gross
-- @popupmod awful.tooltip
-- @supermodule wibox
-------------------------------------------------------------------------
local timer = require("gears.timer")

View File

@ -7,6 +7,7 @@
---------------------------------------------------------------------------
-- Grab environment we need
local os = os
local assert = assert
local load = loadstring or load -- luacheck: globals loadstring (compatibility with Lua 5.1)
local loadfile = loadfile
@ -18,7 +19,6 @@ local gstring = require("gears.string")
local grect = require("gears.geometry").rectangle
local gcolor = require("gears.color")
local gfs = require("gears.filesystem")
local watcher = require("gears.watcher")
local capi =
{
awesome = awesome,
@ -31,7 +31,8 @@ local util = {}
util.table = {}
--- The default shell used when spawing processes.
-- @tfield string awful.util.shell
-- @param string
util.shell = os.getenv("SHELL") or "/bin/sh"
--- Execute a system command and road the output.
-- This function implementation **has been removed** and no longer
@ -483,19 +484,6 @@ function util.round(x)
return gmath.round(x)
end
return setmetatable(util, {
__index = function(_, key)
if key == "shell" then
return watcher._shell
end
end,
__newindex = function(_, key, value)
if key == "shell" then
watcher._shell = value
else
rawset(util, key, value)
end
end
})
return util
-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80

859
lib/awful/wallpaper.lua Normal file
View File

@ -0,0 +1,859 @@
---------------------------------------------------------------------------
--- Allows to use the wibox widget system to draw the wallpaper.
--
-- Rather than simply having a function to set an image
-- (stretched, centered or tiled) like most wallpaper tools, this module
-- leverage the full widget system to draw the wallpaper. Note that the result
-- is **not** interactive. If you want an interactive wallpaper, better use
-- a `wibox` object with the `below` property set to `true` and maximized
-- using `awful.placement.maximized`.
--
-- It is possible to create an `awful.wallpaper` object from any places, but
-- it is recommanded to do it from the `request::wallpaper` signal handler.
-- That signal is called everytime something which could affect the wallpaper
-- rendering changes, such as new screens.
--
-- Single image
-- ============
--
-- This is the default `rc.lua` wallpaper format. It fills the whole screen
-- and stretches the image while keeping the aspect ratio.
--
--@DOC_awful_wallpaper_mazimized1_EXAMPLE@
--
-- If the image aspect ratio doesn't match, the `bg` property can be used to
-- fill the empty area:
--
--@DOC_awful_wallpaper_mazimized2_EXAMPLE@
--
-- It is also possible to stretch the image:
--
--@DOC_awful_wallpaper_mazimized3_EXAMPLE@
--
-- Finally, it is also possible to use simpler "branding" in a corner using
-- `awful.placement`:
--
--@DOC_awful_wallpaper_corner1_EXAMPLE@
--
-- Tiled
-- =====
--
-- This example tiles an image:
--
--@DOC_awful_wallpaper_tiled1_EXAMPLE@
--
-- This one tiles a shape using the `wibox.widget.separator` widget:
--
--@DOC_awful_wallpaper_tiled2_EXAMPLE@
--
-- See the `wibox.container.tile` for more advanced tiling configuration
-- options.
--
-- Solid colors and gradients
-- ==========================
--
-- Solid colors can be set using the `bg` property mentionned above. It
-- is also possible to set gradients:
--
--@DOC_awful_wallpaper_gradient1_EXAMPLE@
--
--@DOC_awful_wallpaper_gradient2_EXAMPLE@
--
-- Widgets
-- =======
--
-- It is possible to create a wallpaper using any widgets. However, keep
-- in mind that the wallpaper surface is not interactive, so some widgets
-- like the sliders will render, but will not behave correctly. Also, it
-- is not recommanded to update the wallpaper too often. This is very slow.
--
--@DOC_awful_wallpaper_widget2_EXAMPLE@
--
-- Cairo graphics API
-- ==================
--
-- AwesomeWM widgets are backed by Cairo. So it is always possible to get
-- access to the Cairo context directly to do some vector art:
--
--@DOC_awful_wallpaper_widget1_EXAMPLE@
--
--
-- SVG vector images
-- =================
--
-- SVG are supported if `librsvg` is installed. Please note that `librsvg`
-- doesn't implement all filters you might find in the latest version of
-- your web browser. It is possible some advanced SVG will not look exactly
-- as they do in a web browser or even Inkscape. However, for most images,
-- it should look identical.
--
-- Our SVG support goes beyond simple rendering. It is possible to set a
-- custom CSS stylesheet (see `wibox.widget.imagebox.stylesheet`):
--
--@DOC_awful_wallpaper_svg_EXAMPLE@
--
-- Note that in the example above, it is raw SVG code, but it is also possible
-- to use a file path. If you have a `.svgz`, you need to uncompress it first
-- using `gunzip` or a software like Inkscape.
--
-- Multiple screen
-- ===============
--
-- The default `rc.lua` creates a new wallpaper everytime `request::wallpaper`
-- is emitted. This is well suited for having a single wallpaper per screen.
-- It is also much simpler to implement slideshows and add/remove screens.
--
-- However, it isn't wall suited for wallpaper rendered across multiple screens.
-- For this case, it is better to capture the return value of `awful.wallpaper {}`
-- as a global variable. Then manually call `add_screen` and `remove_screen` when
-- needed. A shortcut can be to do:
--
-- @DOC_text_awful_wallpaper_multi_screen_EXAMPLE@
--
-- Slideshow
-- =========
--
-- Slideshows (changing the wallpaper after a few minutes) can be implemented
-- directly using a timer and callback, but it is more elegant to simply request
-- a new wallpaper, then get a random image from within the request handler. This
-- way, corner cases such as adding and removing screens are handled:
--
--@DOC_awful_wallpaper_slideshow1_EXAMPLE@
--
-- @author Emmanuel Lepage Vallee &lt;elv1313@gmail.com&gt;
-- @copyright 2019 Emmanuel Lepage Vallee
-- @popupmod awful.wallpaper
---------------------------------------------------------------------------
require("awful._compat")
local gtable = require( "gears.table" )
local gobject = require( "gears.object" )
local gcolor = require( "gears.color" )
local gtimer = require( "gears.timer" )
local surface = require( "gears.surface" )
local base = require( "wibox.widget.base" )
local background = require( "wibox.container.background")
local beautiful = require( "beautiful" )
local cairo = require( "lgi" ).cairo
local draw = require( "wibox.widget" ).draw_to_cairo_context
local grect = require( "gears.geometry" ).rectangle
local capi = { screen = screen, root = root }
local module = {}
local function get_screen(s)
return s and capi.screen[s]
end
-- Screen as key, wallpaper as values.
local pending_repaint = setmetatable({}, {__mode = 'k'})
local backgrounds = setmetatable({}, {__mode = 'k'})
local panning_modes = {}
-- Get a list of all screen areas.
local function get_rectangles(screens, honor_workarea, honor_padding)
local ret = {}
for _, s in ipairs(screens) do
table.insert(ret, s:get_bounding_geometry {
honor_padding = honor_padding,
honor_workarea = honor_workarea
})
end
return ret
end
-- Outer perimeter of all rectangles.
function panning_modes.outer(self)
local rectangles = get_rectangles(self.screens, self.honor_workarea, self.honor_padding)
local p1, p2 = {x = math.huge, y = math.huge}, {x = 0, y = 0}
for _, rect in ipairs(rectangles) do
p1.x, p1.y = math.min(p1.x, rect.x), math.min(p1.y, rect.y)
p2.x, p2.y = math.max(p2.x, rect.x + rect.width), math.max(p2.y, rect.y + rect.height)
end
-- Never try to paint this, it would freeze the system.
assert(p1.x ~= math.huge and p1.y ~= math.huge, "Setting wallpaper failed"..#self.screens)
return {
x = p1.x,
y = p1.y,
width = p2.x - p1.x,
height = p2.y - p1.y,
}
end
-- Horizontal inner perimeter of all rectangles.
function panning_modes.inner_horizontal(self)
local rectangles = get_rectangles(self.screens, self.honor_workarea, self.honor_padding)
local p1, p2 = {x = math.huge, y = 0}, {x = 0, y = math.huge}
for _, rect in ipairs(rectangles) do
p1.x, p1.y = math.min(p1.x, rect.x), math.max(p1.y, rect.y)
p2.x, p2.y = math.max(p2.x, rect.x + rect.width), math.min(p2.y, rect.y + rect.height)
end
-- Never try to paint this, it would freeze the system.
assert(p1.x ~= math.huge and p2.y ~= math.huge, "Setting wallpaper failed")
return {
x = p1.x,
y = p1.y,
width = p2.x - p1.x,
height = p2.y - p1.y,
}
end
-- Vertical inner perimeter of all rectangles.
function panning_modes.inner_vertical(self)
local rectangles = get_rectangles(self.screens, self.honor_workarea, self.honor_padding)
local p1, p2 = {x = 0, y = math.huge}, {x = math.huge, y = 0}
for _, rect in ipairs(rectangles) do
p1.x, p1.y = math.max(p1.x, rect.x), math.min(p1.y, rect.y)
p2.x, p2.y = math.min(p2.x, rect.x + rect.width), math.max(p2.y, rect.y + rect.height)
end
-- Never try to paint this, it would freeze the system.
assert(p1.y ~= math.huge and p2.a ~= math.huge, "Setting wallpaper failed")
return {
x = p1.x,
y = p1.y,
width = p2.x - p1.x,
height = p2.y - p1.y,
}
end
-- Best or vertical and horizontal "inner" modes.
function panning_modes.inner(self)
local vert = panning_modes.inner_vertical(self)
local hori = panning_modes.inner_horizontal(self)
if vert.width <= 0 or vert.height <= 0 then return hori end
if hori.width <= 0 or hori.height <= 0 then return vert end
if vert.width * vert.height > hori.width * hori.height then
return vert
else
return hori
end
end
local function paint()
if not next(pending_repaint) then return end
local root_width, root_height = capi.root.size()
-- Get the current wallpaper content.
local source = surface(root.wallpaper())
local target, cr
-- It's possible that a wallpaper for 1 screen is set using another tool, so make
-- sure we copy the current content.
if source then
target = source:create_similar(cairo.Content.COLOR, root_width, root_height)
cr = cairo.Context(target)
-- Copy the old wallpaper to the new one
cr:save()
cr.operator = cairo.Operator.SOURCE
cr:set_source_surface(source, 0, 0)
for s in screen do
cr:rectangle(
s.geometry.x,
s.geometry.y,
s.geometry.width,
s.geometry.height
)
end
cr:clip()
cr:paint()
cr:restore()
else
target = cairo.ImageSurface(cairo.Format.RGB32, root_width, root_height)
cr = cairo.Context(target)
end
local walls = {}
for _, wall in pairs(backgrounds) do
walls[wall] = true
end
-- Not supposed to happen, but there is enough API surface for
-- it to be a side effect of some signals. Calling the panning
-- mode callback with zero screen is not supported.
if not next(walls) then
return
end
for wall in pairs(walls) do
local geo = type(wall._private.panning_area) == "function" and
wall._private.panning_area(wall) or
panning_modes[wall._private.panning_area](wall)
-- If false, this panning area isn't well suited for the screen geometry.
if geo.width > 0 or geo.height > 0 then
local uncovered_areas = grect.area_remove(get_rectangles(wall.screens, false, false), geo)
cr:save()
-- Prevent overwrite then there is multiple non-continuous screens.
for _, s in ipairs(wall.screens) do
cr:rectangle(
s.geometry.x,
s.geometry.y,
s.geometry.width,
s.geometry.height
)
end
cr:clip()
-- The older surface might contain garbage, optionally clean it.
if wall.uncovered_areas_color then
cr:set_source(gcolor(wall.uncovered_areas_color))
for _, area in ipairs(uncovered_areas) do
cr:rectangle(area.x, area.y, area.width, area.height)
cr:fill()
end
end
if not wall._private.container then
wall._private.container = background()
wall._private.container.bg = wall._private.bg or beautiful.wallpaper_bg or "#000000"
wall._private.container.fg = wall._private.fg or beautiful.wallpaper_fg or "#ffffff"
wall._private.container.widget = wall.widget
end
local a_context = {
dpi = wall._private.context.dpi
}
-- Pick the lowest DPI.
if not a_context.dpi then
a_context.dpi = math.huge
for _, s in ipairs(wall.screens) do
a_context.dpi = math.min(
s.dpi and s.dpi or s.preferred_dpi, a_context.dpi
)
end
end
-- Fallback.
if not a_context.dpi then
a_context.dpi = 96
end
cr:translate(geo.x, geo.y)
draw(wall._private.container, cr, geo.width, geo.height, a_context)
cr:restore()
end
end
-- Set the wallpaper.
local pattern = cairo.Pattern.create_for_surface(target)
capi.root.wallpaper(pattern)
-- Limit some potential GC induced increase in memory usage.
-- But really, is someone is trying to apply wallpaper changes more
-- often than the GC is executed, they are doing it wrong.
target:finish()
end
local mutex = false
-- Uploading the surface to X11 is *very* resource intensive. Given the updates
-- will often happen in batch (like startup), make sure to only do one "real"
-- update.
local function update()
if mutex then return end
mutex = true
gtimer.delayed_call(function()
-- Remove the mutex first in case `paint()` raises an exception.
mutex = false
paint()
end)
end
capi.screen.connect_signal("removed", function(s)
if not backgrounds[s] then return end
backgrounds[s]:remove_screen(s)
update()
end)
capi.screen.connect_signal("property::geometry", function(s)
if not backgrounds[s] then return end
backgrounds[s]:repaint()
end)
--- The wallpaper widget.
--
-- When set, instead of using the `image_path` or `surface` properties, the
-- wallpaper will be defined as a normal `wibox` widget tree.
--
-- @property widget
-- @tparam wibox.widget widget
-- @see wibox.widget.imagebox
-- @see wibox.container.tile
--- The wallpaper DPI (dots per inch).
--
-- Each screen has a DPI. This value will be used by default, but sometime it
-- is useful to override the screen DPI and use a custom one. This makes
-- possible, for example, to draw the widgets bigger than they would otherwise
-- be.
--
-- If not DPI is defined, it will use the smallest DPI from any of the screen.
--
-- In this example, there is 3 screens with DPI of 100, 200 and 300. As you can
-- see, only the text size is affected. Many widgetds are DPI aware, but not all
-- of them. This is either because DPI isn't relevant to them or simply because it
-- isn't supported (like `wibox.widget.graph`).
--
-- @DOC_awful_wallpaper_dpi1_EXAMPLE@
--
-- @property dpi
-- @tparam[opt=screen.dpi] number dpi
-- @see screen
-- @see screen.dpi
--- The wallpaper screen.
--
-- Note that there can only be one wallpaper per screen. If there is more, one
-- will be chosen and all other ignored.
--
-- @property screen
-- @tparam screen screen
-- @see screens
-- @see add_screen
-- @see remove_screen
--- A list of screen for this wallpaper.
--
--@DOC_awful_wallpaper_screens1_EXAMPLE@
--
-- Some large wallpaper are made to span multiple screens.
-- @property screens
-- @tparam table screens
-- @see screen
-- @see add_screen
-- @see remove_screen
-- @see detach
--- The background color.
--
-- It will be used as the "fill" color if the `image` doesn't take all the
-- screen space. It will also be the default background for the `widget.
--
-- As usual with colors in `AwesomeWM`, it can also be a gradient or a pattern.
--
-- @property bg
-- @tparam gears.color bg
-- @see gears.color
--- The foreground color.
--
-- This will be used by the `widget` (if any).
--
-- As usual with colors in `AwesomeWM`, it can also be a gradient or a pattern.
--
-- @property fg
-- @tparam gears.color fg
-- @see gears.color
--- The default wallpaper background color.
-- @beautiful beautiful.wallpaper_bg
-- @tparam gears.color wallpaper_bg
-- @see bg
--- The default wallpaper foreground color.
--
-- This is useful when using widgets or text in the wallpaper. A wallpaper
-- created from a single image wont use this.
--
-- @beautiful beautiful.wallpaper_fg
-- @tparam gears.color wallpaper_fg
-- @see bg
--- Honor the workarea.
--
-- When set to `true`, the wallpaper will only fill the workarea space instead
-- of the entire screen. This means it wont be drawn below the `awful.wibar` or
-- docked clients. This is useful when using opaque bars. Note that it can cause
-- aspect ratio issues for the wallpaper `image` and add bars colored with the
-- `bg` color on the sides.
--
--@DOC_awful_wallpaper_workarea1_EXAMPLE@
--
-- @property honor_workarea
-- @tparam[opt=false] boolean honor_workarea
-- @see honor_padding
-- @see uncovered_areas
--- Honor the screen padding.
--
-- When set, this will look at the `screen.padding` property to restrict the
-- area where the wallpaper is rendered.
--
-- @DOC_awful_wallpaper_padding1_EXAMPLE@
--
-- @property honor_padding
-- @tparam boolean honor_padding
-- @see honor_workarea
-- @see uncovered_areas
--- Returns the list of screen(s) area which won't be covered by the wallpaper.
--
-- When `honor_workarea`, `honor_padding` or panning are used, some section of
-- the screen won't have a wallpaper. This returns a list of areas tables. Each
-- table has a `x`, `y`, `width` and `height` key.
--
-- @property uncovered_areas
-- @tparam table uncovered_areas
-- @see honor_workarea
-- @see honor_padding
-- @see uncovered_areas_color
--- The color for the uncovered areas.
--
-- Some application rely on the wallpaper for "fake" transparency. Even if an
-- area is hidden under a wibar (or other clients), its background can still
-- become visible. If you use such application and change your screen geometry
-- often enough, it is possible some areas would become filled with the remains
-- of previous wallpapers. This property allows to clean those areas with a solid
-- color or a gradient.
--
-- @property uncovered_areas_color
-- @tparam gears.color uncovered_areas_color
-- @see uncovered_areas
--- Defines where the wallpaper is placed when there is multiple screens.
--
-- When there is more than 1 screen, it is possible they don't have the same
-- resolution, position or orientation. Panning the wallpaper over them may look
-- better if a continuous rectangle is used rather than creating a virtual rectangle
-- around all screens.
--
-- The default algorithms are:
--
-- **outer:** *(default)*
--
-- Draw an imaginary rectangle around all screens.
--
-- @DOC_awful_wallpaper_panning_outer_EXAMPLE@
--
-- **inner:**
--
-- Take the largest area or either `inner_horizontal` or `inner_vertical`.
--
-- @DOC_awful_wallpaper_panning_inner_EXAMPLE@
--
-- **inner_horizontal:**
--
-- Take the smallest `x` value, the largest `x+width`, the smallest `y`
-- and the smallest `y+height`.
--
-- @DOC_awful_wallpaper_panning_inner_horizontal_EXAMPLE@
--
-- **inner_vertical:**
--
-- Take the smallest `y` value, the largest `y+height`, the smallest `x`
-- and the smallest `x+width`.
--
-- @DOC_awful_wallpaper_panning_inner_vertical_EXAMPLE@
--
-- **Custom function:**
--
-- It is also possible to define a custom function.
--
-- @DOC_awful_wallpaper_panning_custom_EXAMPLE@
--
-- @property panning_area
-- @tparam function|string panning_area
-- @see uncovered_areas
function module:set_panning_area(value)
value = value or "outer"
assert(type(value) == "function" or panning_modes[value], "Invalid panning mode: "..tostring(value))
self._private.panning_area = value
self:repaint()
self:emit_signal("property::panning_area", value)
end
function module:set_widget(w)
self._private.widget = base.make_widget_from_value(w)
if self._private.container then
self._private.container.widget = self._private.widget
end
self:repaint()
end
function module:get_widget()
return self._private.widget
end
function module:set_dpi(dpi)
self._private.context.dpi = dpi
self:repaint()
end
function module:get_dpi()
return self._private.context.dpi
end
function module:set_screen(s)
if not s then return end
self:_clear()
self:add_screen(s)
end
for _, prop in ipairs {"bg", "fg"} do
module["set_"..prop] = function(self, color)
if self._private.container then
self._private.container[prop] = color
end
self._private[prop] = color
self:repaint()
end
end
function module:get_uncovered_areas()
local geo = type(self._private.panning_area) == "function" and
self._private.panning_area(self) or
panning_modes[self._private.panning_area](self)
return grect.area_remove(get_rectangles(self.screens, false, false), geo)
end
function module:set_screens(screens)
local to_rem = {}
-- All screens.
-- The copy is needed because it's a metatable, `ipairs` doesn't work
-- correctly in all Lua versions.
if screens == capi.screen then
screens = {}
for s in capi.screen do
table.insert(screens, s)
end
end
for _, s in ipairs(screens) do
to_rem[get_screen(s)] = true
end
for _, s in ipairs(self.screens) do
to_rem[get_screen(s)] = nil
end
for _, s in ipairs(screens) do
s = get_screen(s)
self:add_screen(s)
to_rem[s] = nil
end
for s, remove in pairs(to_rem) do
if remove then
self:remove_screen(s)
end
end
end
function module:get_screens()
return self._private.screens
end
--- Add another screen (enable panning).
--
-- **Before:**
--
--@DOC_awful_wallpaper_add_screen1_EXAMPLE@
--
-- **After:**
--
--@DOC_awful_wallpaper_add_screen2_EXAMPLE@
--
-- Also note that adding a non-continuous screen might not work well,
-- but will not automatically add the screens in between:
--
--@DOC_awful_wallpaper_add_screen3_EXAMPLE@
--
-- @method add_screen
-- @tparam screen screen The screen object.
-- @see remove_screen
function module:add_screen(s)
s = get_screen(s)
for _, s2 in ipairs(self._private.screens) do
if s == s2 then return end
end
table.insert(self._private.screens, s)
if backgrounds[s] and backgrounds[s] ~= self then
backgrounds[s]:remove_screen(s)
end
backgrounds[s] = self
self:repaint()
end
--- Detach the wallpaper from all screens.
--
-- Adding a new wallpaper to a screen will automatically
-- detach the older one. However there is some case when
-- it is useful to call this manually. For example, when
-- adding a new panned wallpaper, it is possible that 2
-- wallpaper will have an overlap.
--
-- @method detach
-- @see remove_screen
-- @see add_screen
function module:detach()
local screens = gtable.clone(self.screens)
for _, s in ipairs(screens) do
self:remove_screen(s)
end
end
function module:_clear()
self._private.screens = setmetatable({}, {__mode = "v"})
update()
end
--- Repaint the wallpaper.
--
-- By default, even if the widget changes, the wallpaper will **NOT** be
-- automatically repainted. Repainting the native X11 wallpaper is slow and
-- it would be too easy to accidentally cause a performance problem. If you
-- really need to repaint the wallpaper, call this method.
--
-- @method repaint
function module:repaint()
for _, s in ipairs(self._private.screens) do
pending_repaint[s] = true
end
update()
end
--- Remove a screen.
--
-- Calling this will remove a screen, but will **not** repaint its area.
-- In this example, the wallpaper was spanning all 3 screens and the
-- first screen was removed:
--
-- @DOC_awful_wallpaper_remove_screen1_EXAMPLE@
--
-- As you can see, the content of screen 1 still looks like it is part of
-- the 3 screen wallpaper. The only use case for calling this method is if
-- you use a 3rd party tools to change the wallpaper.
--
-- If you wish to simply remove a screen and not have leftover content, it is
-- simpler to just create a new wallpaper for that screen:
--
-- @DOC_awful_wallpaper_remove_screen2_EXAMPLE@
--
-- @method remove_screen
-- @tparam screen screen The screen to remove.
-- @see detach
-- @see add_screen
-- @see screens
function module:remove_screen(s)
s = get_screen(s)
for k, s2 in ipairs(self._private.screens) do
if s == s2 then
table.remove(self._private.screens, k)
end
end
backgrounds[s] = nil
self:repaint()
end
--- Create a wallpaper.
--
-- Note that all parameters are not required. Please refer to the module description and examples to understand parameters usages.
--
-- @constructorfct awful.wallpaper
-- @tparam table args
-- @tparam[opt] wibox.widget args.widget The wallpaper widget.
-- @tparam[opt] number args.dpi The wallpaper DPI (dots per inch).
-- @tparam[opt] screen args.screen The wallpaper screen.
-- @tparam[opt] table args.screens A list of screen for this wallpaper. Use this parameter as a remplacement for `args.screen` to manage multiscreen wallpaper. (Note: the expected table should be an array-like table `{screen1, screen2, ...}`)
-- @tparam[opt] gears.color args.bg The background color.
-- @tparam[opt] gears.color args.fg The foreground color.
-- @tparam[opt] gears.color args.uncovered_areas_color The color for the uncovered areas.
-- @tparam[opt] boolean args.honor_workarea Honor the workarea.
-- @tparam[opt] boolean args.honor_padding Honor the screen padding.
-- @tparam[opt] table args.uncovered_areas Returns the list of screen(s) area which won't be covered by the wallpaper.
-- @tparam[opt] function|string args.panning_area Defines where the wallpaper is placed when there is multiple screens.
local function new(_, args)
args = args or {}
local ret = gobject {
enable_auto_signals = true,
enable_properties = true,
}
rawset(ret, "_private", {})
ret._private.context = {}
ret._private.panning_area = "outer"
gtable.crush(ret, module, true)
ret:_clear()
-- Set the screen or screens first to avoid a race condition
-- with the other setters.
local args_screen, args_screens = args.screen, args.screens
if args_screen then
ret.screen = args_screen
elseif args_screens then
ret.screens = args_screens
end
-- Avoid crushing `screen` and `screens` twice.
args.screen, args.screens = nil, nil
gtable.crush(ret, args, false)
args.screen, args.screens = args_screen, args_screens
return ret
end
return setmetatable(module, {__call = new})

View File

@ -1,16 +1,19 @@
---------------------------------------------------------------------------
--- Wibox module for awful.
--- The main AwesomeWM "bar" module.
--
-- This module allows you to easily create wibox and attach them to the edge of
-- a screen.
--
--@DOC_awful_wibar_default_EXAMPLE@
--
-- You can even have vertical bars too.
--
--@DOC_awful_wibar_left_EXAMPLE@
--
-- @author Emmanuel Lepage Vallee &lt;elv1313@gmail.com&gt;
-- @copyright 2016 Emmanuel Lepage Vallee
-- @popupmod awful.wibar
-- @supermodule awful.popup
---------------------------------------------------------------------------
-- Grab environment we need
@ -27,6 +30,7 @@ local wibox = require("wibox")
local beautiful = require("beautiful")
local gdebug = require("gears.debug")
local placement = require("awful.placement")
local gtable = require("gears.table")
local function get_screen(s)
return s and capi.screen[s]
@ -38,16 +42,94 @@ local awfulwibar = { mt = {} }
-- It's an array so it is ordered.
local wiboxes = setmetatable({}, {__mode = "v"})
local opposite_margin = {
top = "bottom",
bottom = "top",
left = "right",
right = "left"
}
local align_map = {
top = "left",
left = "top",
bottom = "right",
right = "bottom",
centered = "centered"
}
--- If the wibar needs to be stretched to fill the screen.
--
-- @DOC_awful_wibar_stretch_EXAMPLE@
--
-- @property stretch
-- @tparam boolean stretch
-- @propbeautiful
-- @propemits true false
-- @see align
--- How to align non-stretched wibars.
--
-- Values are:
--
-- * `"top"`
-- * `"bottom"`
-- * `"left"`
-- * `"right"`
-- * `"centered"`
--
-- @DOC_awful_wibar_align_EXAMPLE@
--
-- @property align
-- @tparam string align
-- @propbeautiful
-- @propemits true false
-- @see stretch
--- Margins on each side of the wibar.
--
-- It can either be a table with `top`, `bottom`, `left` and `right`
-- properties, or a single number that applies to all four sides.
--
-- @DOC_awful_wibar_margins_EXAMPLE@
--
-- @property margins
-- @tparam number|table margins
-- @propbeautiful
-- @propemits true false
--- If the wibar needs to be stretched to fill the screen.
--
-- @beautiful beautiful.wibar_stretch
-- @tparam boolean stretch
--- Allow or deny the tiled clients to cover the wibar.
--
-- In the example below, we can see that the first screen workarea
-- shrunk by the height of the wibar while the second screen is
-- unchanged.
--
-- @DOC_screen_wibar_workarea_EXAMPLE@
--
-- @property restrict_workarea
-- @tparam[opt=true] boolean restrict_workarea
-- @propemits true false
-- @see client.struts
-- @see screen.workarea
--- If there is both vertical and horizontal wibar, give more space to vertical ones.
--
-- By default, if multiple wibars risk overlapping, it will be resolved
-- by giving more space to the horizontal one:
--
-- ![wibar position](../images/AUTOGEN_awful_wibar_position.svg)
--
-- If this variable is to to `true`, it will behave like:
--
-- @DOC_awful_wibar_position2_EXAMPLE@
--
-- @beautiful beautiful.wibar_favor_vertical
-- @tparam[opt=false] boolean favor_vertical
--- The wibar border width.
-- @beautiful beautiful.wibar_border_width
-- @tparam integer border_width
@ -96,6 +178,15 @@ local wiboxes = setmetatable({}, {__mode = "v"})
-- @beautiful beautiful.wibar_shape
-- @tparam gears.shape shape
--- The wibar's margins.
-- @beautiful beautiful.wibar_margins
-- @tparam number|table margins
--- The wibar's alignments.
-- @beautiful beautiful.wibar_align
-- @tparam string align
-- Compute the margin on one side
local function get_margin(w, position, auto_stop)
local h_or_w = (position == "top" or position == "bottom") and "height" or "width"
@ -107,7 +198,15 @@ local function get_margin(w, position, auto_stop)
if v.position == position and v.screen == w.screen and v.visible then
ret = ret + v[h_or_w]
local wb_margins = v.margins
if wb_margins then
ret = ret + wb_margins[position] + wb_margins[opposite_margin[position]]
end
end
end
return ret
@ -119,32 +218,49 @@ local function get_margins(w)
local position = w.position
assert(position)
local margins = {left=0, right=0, top=0, bottom=0}
local margins = gtable.clone(w._private.margins)
margins[position] = get_margin(w, position, true)
margins[position] = margins[position] + get_margin(w, position, true)
-- Avoid overlapping wibars
if position == "left" or position == "right" then
if (position == "left" or position == "right") and not beautiful.wibar_favor_vertical then
margins.top = get_margin(w, "top" )
margins.bottom = get_margin(w, "bottom")
elseif (position == "top" or position == "bottom") and beautiful.wibar_favor_vertical then
margins.left = get_margin(w, "left" )
margins.right = get_margin(w, "right")
end
return margins
end
-- Create the placement function
local function gen_placement(position, stretch)
local function gen_placement(position, align, stretch)
local maximize = (position == "right" or position == "left") and
"maximize_vertically" or "maximize_horizontally"
return placement[position] + (stretch and placement[maximize] or nil)
local corner = nil
if align ~= "centered" then
if position == "right" or position == "left" then
corner = placement[align .. "_" .. position]
or placement[align_map[align] .. "_" .. position]
else
corner = placement[position .. "_" .. align]
or placement[position .. "_" .. align_map[align]]
end
end
corner = corner or placement[position]
return corner + (stretch and placement[maximize] or nil)
end
-- Attach the placement function.
local function attach(wb, align)
gen_placement(align, wb._stretch)(wb, {
local function attach(wb, position)
gen_placement(position, wb._private.align, wb._stretch)(wb, {
attach = true,
update_workarea = true,
update_workarea = wb._private.restrict_workarea,
margins = get_margins(wb)
})
end
@ -172,15 +288,23 @@ end
-- * top
-- * bottom
--
-- @DOC_awful_wibar_position_EXAMPLE@
--
-- @property position
-- @tparam string position Either "left", right", "top" or "bottom"
-- @propemits true false
local function get_position(wb)
function awfulwibar.get_position(wb)
return wb._position or "top"
end
local function set_position(wb, position, skip_reattach)
function awfulwibar.set_position(wb, position, screen)
if position == wb._position then return end
if screen then
gdebug.deprecate("Use `wb.screen = screen` instead of awful.wibar.set_position", {deprecated_in=4})
end
-- Detach first to avoid any uneeded callbacks
if wb.detach_callback then
wb.detach_callback()
@ -191,14 +315,12 @@ local function set_position(wb, position, skip_reattach)
-- Move the wibar to the end of the list to avoid messing up the others in
-- case there is stacked wibars on one side.
if wb._position then
for k, w in ipairs(wiboxes) do
if w == wb then
table.remove(wiboxes, k)
end
for k, w in ipairs(wiboxes) do
if w == wb then
table.remove(wiboxes, k)
end
table.insert(wiboxes, wb)
end
table.insert(wiboxes, wb)
-- In case the position changed, it may be necessary to reset the size
if (wb._position == "left" or wb._position == "right")
@ -217,7 +339,7 @@ local function set_position(wb, position, skip_reattach)
-- A way to skip reattach is required when first adding a wibar as it's not
-- in the `wiboxes` table yet and can't be added until it's attached.
if not skip_reattach then
if not wb._private.skip_reattach then
-- Changing the position will also cause the other margins to be invalidated.
-- For example, adding a wibar to the top will change the margins of any left
-- or right wibars. To solve, this, they need to be re-attached.
@ -227,11 +349,11 @@ local function set_position(wb, position, skip_reattach)
wb:emit_signal("property::position", position)
end
local function get_stretch(w)
function awfulwibar.get_stretch(w)
return w._stretch
end
local function set_stretch(w, value)
function awfulwibar.set_stretch(w, value)
w._stretch = value
attach(w, w.position)
@ -239,10 +361,89 @@ local function set_stretch(w, value)
w:emit_signal("property::stretch", value)
end
function awfulwibar.get_restrict_workarea(w)
return w._private.restrict_workarea
end
function awfulwibar.set_restrict_workarea(w, value)
w._private.restrict_workarea = value
attach(w, w.position)
w:emit_signal("property::restrict_workarea", value)
end
function awfulwibar.set_margins(w, value)
if type(value) == "number" then
value = {
top = value,
bottom = value,
right = value,
left = value,
}
end
local old = gtable.crush({
left = 0,
right = 0,
top = 0,
bottom = 0
}, w._private.margins or {}, true)
value = gtable.crush(old, value or {}, true)
w._private.margins = value
attach(w, w.position)
w:emit_signal("property::margins", value)
end
-- Allow each margins to be set individually.
local function meta_margins(self)
return setmetatable({}, {
__index = self._private.margins,
__newindex = function(_, k, v)
self._private.margins[k] = v
awfulwibar.set_margins(self, self._private.margins)
end
})
end
function awfulwibar.get_margins(self)
return self._private.meta_margins
end
function awfulwibar.get_align(self)
return self._private.align
end
function awfulwibar.set_align(self, value, screen)
if value == "center" then
gdebug.deprecate("awful.wibar.align(wb, 'center' is deprecated, use 'centered'", {deprecated_in=4})
value = "centered"
end
if screen then
gdebug.deprecate("awful.wibar.align 'screen' argument is deprecated", {deprecated_in=4})
end
assert(align_map[value])
self._private.align = value
attach(self, self.position)
self:emit_signal("property::align", value)
end
--- Remove a wibar.
-- @method remove
local function remove(self)
function awfulwibar.remove(self)
self.visible = false
if self.detach_callback then
@ -259,26 +460,6 @@ local function remove(self)
self._screen = nil
end
--- Get a wibox position if it has been set, or return top.
-- @param wb The wibox
-- @deprecated awful.wibar.get_position
-- @return The wibox position.
function awfulwibar.get_position(wb)
gdebug.deprecate("Use wb:get_position() instead of awful.wibar.get_position", {deprecated_in=4})
return get_position(wb)
end
--- Put a wibox on a screen at this position.
-- @param wb The wibox to attach.
-- @param position The position: top, bottom left or right.
-- @param screen This argument is deprecated, use wb.screen directly.
-- @deprecated awful.wibar.set_position
function awfulwibar.set_position(wb, position, screen) --luacheck: no unused args
gdebug.deprecate("Use wb:set_position(position) instead of awful.wibar.set_position", {deprecated_in=4})
set_position(wb, position)
end
--- Attach a wibox to a screen.
--
-- This function has been moved to the `awful.placement` module. Calling this
@ -289,7 +470,7 @@ end
-- @param screen The screen to attach to
-- @see awful.placement
-- @deprecated awful.wibar.attach
function awfulwibar.attach(wb, position, screen) --luacheck: no unused args
local function legacy_attach(wb, position, screen) --luacheck: no unused args
gdebug.deprecate("awful.wibar.attach is deprecated, use the 'attach' property"..
" of awful.placement. This method doesn't do anything anymore",
{deprecated_in=4}
@ -318,7 +499,7 @@ end
-- directly.
-- @deprecated awful.wibar.align
-- @see awful.placement.align
function awfulwibar.align(wb, align, screen) --luacheck: no unused args
local function legacy_align(wb, align, screen) --luacheck: no unused args
if align == "center" then
gdebug.deprecate("awful.wibar.align(wb, 'center' is deprecated, use 'centered'", {deprecated_in=4})
align = "centered"
@ -350,6 +531,9 @@ end
-- @tparam[opt=nil] table args
-- @tparam string args.position The position.
-- @tparam string args.stretch If the wibar need to be stretched to fill the screen.
-- @tparam boolean args.restrict_workarea Allow or deny the tiled clients to cover the wibar.
-- @tparam string args.align How to align non-stretched wibars.
-- @tparam table|number args.margins The wibar margins.
--@DOC_wibox_constructor_COMMON@
-- @return The new wibar
-- @constructorfct awful.wibar
@ -399,7 +583,7 @@ function awfulwibar.new(args)
-- The C code scans the table directly, so metatable magic cannot be used.
for _, prop in ipairs {
"border_width", "border_color", "font", "opacity", "ontop", "cursor",
"bgimage", "bg", "fg", "type", "stretch", "shape"
"bgimage", "bg", "fg", "type", "stretch", "shape", "margins", "align"
} do
if (args[prop] == nil) and beautiful["wibar_"..prop] ~= nil then
args[prop] = beautiful["wibar_"..prop]
@ -408,25 +592,37 @@ function awfulwibar.new(args)
local w = wibox(args)
w.screen = screen
w._screen = screen --HACK When a screen is removed, then getbycoords wont work
w._stretch = args.stretch == nil and has_to_stretch or args.stretch
w._private.align = (args.align and align_map[args.align]) and args.align or "centered"
w.get_position = get_position
w.set_position = set_position
w._private.margins = {
left = 0,
right = 0,
top = 0,
bottom = 0
}
w._private.meta_margins = meta_margins(w)
w.get_stretch = get_stretch
w.set_stretch = set_stretch
w.remove = remove
if args.visible == nil then w.visible = true end
w._private.restrict_workarea = true
-- `w` needs to be inserted in `wiboxes` before reattach or its own offset
-- will not be taken into account by the "older" wibars when `reattach` is
-- called. `skip_reattach` is required.
w:set_position(position, true)
w._private.skip_reattach = true
table.insert(wiboxes, w)
w.screen = screen
w._screen = screen --HACK When a screen is removed, then getbycoords wont work
w._stretch = args.stretch == nil and has_to_stretch or args.stretch
if args.visible == nil then w.visible = true end
gtable.crush(w, awfulwibar, true)
gtable.crush(w, args, false)
-- Now, let set_position behave normally.
w._private.skip_reattach = false
awfulwibar.set_margins(w, args.margins)
-- Force all the wibars to be moved
reattach(w)
@ -454,6 +650,14 @@ function awfulwibar.mt:__call(...)
return awfulwibar.new(...)
end
function awfulwibar.mt:__index(_, k)
if k == "align" then
return legacy_align
elseif k == "attach" then
return legacy_attach
end
end
--@DOC_wibox_COMMON@
--@DOC_object_COMMON@

View File

@ -6,6 +6,7 @@
-- @author Julien Danjou &lt;julien@danjou.info&gt;
-- @copyright 2008-2009 Julien Danjou
-- @widgetmod awful.widget.button
-- @supermodule wibox.widget.imagebox
---------------------------------------------------------------------------
local setmetatable = setmetatable
@ -65,10 +66,6 @@ function button.mt:__call(...)
return button.new(...)
end
--@DOC_widget_COMMON@
--@DOC_object_COMMON@
return setmetatable(button, button.mt)
-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80

View File

@ -1,7 +1,10 @@
---------------------------------------------------------------------------
--- Container showing the icon of a client.
-- @author Uli Schlachter
-- @copyright 2017 Uli Schlachter
-- @widgetmod awful.widget.clienticon
-- @supermodule wibox.widget.base
---------------------------------------------------------------------------
local base = require("wibox.widget.base")
local surface = require("gears.surface")
@ -123,10 +126,6 @@ client.connect_signal("property::icon", function(c)
end
end)
--@DOC_widget_COMMON@
--@DOC_object_COMMON@
return setmetatable(clienticon, {
__call = function(_, ...)
return new(...)

View File

@ -11,6 +11,7 @@
-- @author Julien Danjou &lt;julien@danjou.info&gt;
-- @copyright 2008-2009 Julien Danjou
-- @widgetmod awful.widget.launcher
-- @supermodule awful.widget.button
---------------------------------------------------------------------------
local setmetatable = setmetatable
@ -50,10 +51,6 @@ function launcher.mt:__call(...)
return launcher.new(...)
end
--@DOC_widget_COMMON@
--@DOC_object_COMMON@
return setmetatable(launcher, launcher.mt)
-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80

View File

@ -6,6 +6,7 @@
-- @author Julien Danjou &lt;julien@danjou.info&gt;
-- @copyright 2009 Julien Danjou
-- @widgetmod awful.widget.layoutbox
-- @supermodule wibox.layout.fixed
---------------------------------------------------------------------------
local setmetatable = setmetatable

View File

@ -21,6 +21,7 @@
-- @author Emmanuel Lepage Vallee &lt;elv1313@gmail.com&gt;
-- @copyright 2010, 2018 Emmanuel Lepage Vallee
-- @widgetmod awful.widget.layoutlist
-- @supermodule wibox.widget.base
----------------------------------------------------------------------------
local capi = {screen = screen, tag = tag}
@ -444,8 +445,4 @@ local function new(_, args)
return ret
end
--@DOC_widget_COMMON@
--@DOC_object_COMMON@
return setmetatable(module, {__call = new})

View File

@ -5,6 +5,7 @@
-- @author Uli Schlachter
-- @copyright 2017 Uli Schlachter
-- @containermod awful.widget.only_on_screen
-- @supermodule wibox.widget.base
---------------------------------------------------------------------------
local type = type
@ -129,10 +130,6 @@ capi.screen.connect_signal("property::outputs", function()
end
end)
--@DOC_widget_COMMON@
--@DOC_object_COMMON@
return setmetatable(only_on_screen, only_on_screen.mt)
-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80

View File

@ -7,6 +7,7 @@
-- @copyright 2009 Julien Danjou
-- @copyright 2018 Aire-One
-- @widgetmod awful.widget.prompt
-- @supermodule wibox.container.background
---------------------------------------------------------------------------
--- The prompt foreground color.
@ -158,10 +159,6 @@ function widgetprompt.mt:__call(...)
return widgetprompt.new(...)
end
--@DOC_widget_COMMON@
--@DOC_object_COMMON@
return setmetatable(widgetprompt, widgetprompt.mt)
-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80

View File

@ -70,7 +70,8 @@ function gtk.get_theme_variables()
local result = {}
local _gtk_status, Gtk = pcall(function()
return require('lgi').Gtk
local lgi = require('lgi')
return lgi.require('Gtk', '3.0')
end)
if not _gtk_status or not Gtk then
gears_debug.print_warning(

View File

@ -1,628 +0,0 @@
--- Object oriented way to connect objects.
--
-- All AwesomeWM objects have a `emit_signal` method.
-- They allow to attach business logic to a property value change or random
-- types of events.
--
-- The default way to attach business logic to signals is to use `connect_signal`.
-- It allows to call a function when the signal is emitted. This remains the most
-- common way to perform a connection. However, it is very verbose to use that
-- construct alongside the declarative widget system. `gears.connection` is much
-- easier to integrate in such constructs:
--
-- @DOC_wibox_decl_doc_connection4_EXAMPLE@
--
-- Limitations
-- ===========
--
-- * When used directly as a value to a declarative object
-- (`text = gears.connection{...}`), it is necessary to manually disconnect
-- the connectio if you want it to stop being auto-updated.
--
-- @author Emmanuel Lepage-Vallee &lt;elv1313@gmail.com&gt;
-- @copyright 2019-2020 Emmanuel Lepage-Vallee
-- @classmod gears.connection
local gobject = require("gears.object")
local gtable = require("gears.table")
local gtimer = require("gears.timer")
local module = {}
local function get_class(name)
if type(name) ~= "string" then return name end
return _G[name] or require(name)
end
local function gen_get_children_by_id(self)
return function(id)
return (self._private.env_ids or {})[id] or {}
end
end
local function get_env(_, cb)
local env, position = nil, nil
for i = 1, math.huge do
local name, val = debug.getupvalue(cb, i)
if name == "_ENV" then
env, position = val, i
break
elseif name == nil then
break
end
end
return env, position
end
-- Expose the declarative tree `id`s to the callback as variables.
--
-- Note that this runs into a slight conflict with the ability to
-- place a widget in multiple trees. If it happens, then obviously
-- there will be `id` conflicts. That's solvable by only using
-- `get_children_by_id` in such situation. As of now, there is no
-- code to track `id`s across trees. So the callback will only have the
-- ids from the tree it was last added to.
local function extend_env(self)
local cb, ids = self._private.callbacks[1], self._private.env_ids
if (not cb) or (not ids) then return end
self._private.env_init = true
local env, position = get_env(self, cb)
if not env then return end
local gcbi = nil
local new_env = setmetatable({}, {
__index = function(_, key)
if key == "get_children_by_id" then
gcbi = gcbi or gen_get_children_by_id(self)
return gcbi
elseif ids[key] and #ids[key] == 1 then
return ids[key][1]
end
local v = env[key]
if v then return v end
return _G[key]
end,
__newindex = function(_, key, value)
_G[key] = value
end
})
debug.setupvalue(cb, position, new_env)
end
local function set_target(self)
local p = self._private
local has_target = p.target
and p.initiate ~= false
local has_source = #p.sources >= 1
and #p.source_properties >= 1
extend_env(self)
for _, callback in ipairs(self._private.callbacks) do
local ret = callback(
p.sources[1],
p.target,
(p.sources[1] and p.source_properties[1]) and
p.sources[1][p.source_properties[1]] or nil
)
if self.target_property and self._private.target then
self._private.target[self.target_property] = ret
end
end
if p.target_method then
p.target[p.target_method]()
end
-- There isn't enough information to initiate anything yet.
if not (has_target and has_source) then return end
if p.target_property then
p.target[p.target_property] = p.sources[1][p.source_properties[1]]
end
end
-- When all properties necessary to set the initial value are set.
local function initiate(self)
if self._private.initiating then return end
-- We don't know if properties will be overriden or if a callback/method
-- will be added. Better wait.
gtimer.delayed_call(function()
-- It might have been disabled since then.
if not self._private.enabled then return end
set_target(self)
self._private.initiating = false
end)
self._private.initiating = true
end
function module:get_initiate()
return self._private.initiate
end
--- If the property should be set when the target object is defined.
--
-- It is **enabled** by default for convinience.
--
-- @DOC_text_gears_connection_initiate_EXAMPLE@
--
-- @property initiate
-- @tparam[opt=true] boolean string initiate
-- @propemits true false
function module:set_initiate(value)
if self._private.initiate == value then return end
self._private.initiate = value
self:emit_signal("property::initiate", value)
if value then
initiate(self)
end
end
--- Turn this connection on or off.
--
-- @DOC_text_gears_connection_enabled_EXAMPLE@
--
-- @property enabled
-- @tparam boolean enabled
-- @see disconnect
-- @see reconnect
-- @propemits true false
function module:get_enabled()
return self._private.enabled
end
function module:set_enabled(value)
if value == self._private.enabled then return end
self._private.enabled = value
self:emit_signal("property::enabled", value)
end
--- A list of source object signals.
--
-- @property signals
-- @tparam table signals
-- @propemits true false
-- @see signal
-- @see source_property
function module:get_signals()
return self._private.signals
end
function module:set_signals(value)
self:disconnect()
self._private.signals = value
self:reconnect()
self:emit_signal("property::signal", value[1])
self:emit_signal("property::signals", value)
initiate(self)
end
function module:get_signal()
return self._private.signals < 2 and self._private.signals[1] or nil
end
--- The (source) signal to monitor.
--
-- Note that `signals` and `source_property` are also provided to simplify
-- common use cases.
--
-- @property signal
-- @param string
-- @propemits true false
-- @see signals
-- @see source_property
function module:set_signal(signal)
self._private.signals = {signal}
end
--- The object for the right side object of the connection.
--
-- When used in a widget declarative tree, this is implicit and
-- is the parent object.
--
-- @DOC_text_gears_connection_target_EXAMPLE@
--
-- @property target
-- @tparam gears.object target
-- @propemits true false
-- @see target_property
-- @see target_method
function module:get_target()
return self._private.target
end
function module:set_target(target)
self._private.target = target
self:emit_signal("property::target", target)
initiate(self)
end
--- The target object property to set when the source property changes.
--
-- @property target_property
-- @tparam string target_property
-- @propemits true false
-- @see target
-- @see target_method
-- @see source_property
function module:get_target_property()
return self._private.target_property
end
function module:set_target_property(value)
self._private.target_property = value
self:emit_signal("property::target_property", value)
initiate(self)
end
--- Rather than use a property, call a method.
--
-- @DOC_text_gears_connection_method_EXAMPLE@
--
-- @property target_method
-- @tparam string target_method
-- @propemits true false
-- @see target
-- @see target_property
function module:get_target_method()
return self._private.target_method
end
function module:set_target_method(value)
self._private.target_method = value
self:emit_signal("property::target_method", value)
initiate(self)
end
--- Use a whole class rather than an object as source.
--
-- Many classes, like `client`, `tag`, `screen` and `naughty.notification`
-- provide class level signals. When any instance of those classes emit a
-- signal, it is forwarded to the class level signals.
--
-- @DOC_text_gears_connection_class_EXAMPLE@
--
-- @property source_class
-- @tparam class|string source_class
-- @propemits true false
-- @see source
-- @see source_property
function module:set_source_class(class)
self:disconnect()
self._private.source_class = get_class(class)
self:reconnect()
self:emit_signal("property::source_class", self._private.source_class)
end
function module:get_source_class()
return self._private.source_class
end
--- The source object (connection left hand side).
-- @property source
-- @tparam gears.object source
-- @propemits true false
-- @see sources
-- @see source_class
function module:get_source()
return self._private.sources[1]
end
function module:set_source(source)
self:disconnect()
self._private.sources = {source}
self:reconnect()
self:emit_signal("property::source", source)
self:emit_signal("property::sources", self._private.sources)
initiate(self)
end
--- The source object(s)/class property.
--
-- @property source_property
-- @tparam string source_property
-- @propemits true false
function module:get_source_property()
return #self._private.source_properties == 1 and
self._private.source_properties[1] or nil
end
function module:set_source_property(prop)
self.source_properties = {prop}
end
function module:get_source_properties()
return self._private.source_properties
end
function module:set_source_properties(props)
self:disconnect()
self._private.source_properties = props
local signals = {}
self:reconnect()
for _, prop in ipairs(props) do
table.insert(signals, "property::"..prop)
end
self.signals = signals
self:emit_signal("property::source_property", props[1])
self:emit_signal("property::source_properties", props)
end
--- A list of source objects (connection left hand side).
--
-- If many objects have the same signal, it's not necessary
-- to make multiple `gears.connection`. They can share the same.
--
-- @property sources
-- @tparam gears.object sources
-- @propemits true false
-- @see append_source_object
-- @see remove_source_object
function module:get_sources()
return self._private.sources
end
function module:set_sources(sources)
if not sources then
sources = {}
end
self:disconnect()
self._private.sources = sources
self:reconnect()
self:emit_signal("property::source", sources[1])
self:emit_signal("property::sources", sources)
initiate(self)
end
--- Add a source object.
--
-- @DOC_text_gears_connection_add_remove_EXAMPLE@
--
-- @method append_source_object
-- @tparam gears.object obj The object.
-- @see sources
-- @see remove_source_object
-- @see has_source_object
-- @see source
-- @see sources
function module:append_source_object(obj)
if self:has_source_object(obj) then return end
table.insert(self._private.sources, obj)
self:emit_signal("property::sources", self._private.sources)
if #self._private.sources == 1 then
initiate(self)
end
end
--- Remove a source object.
--
-- @method remove_source_object
-- @tparam gears.object obj The object.
-- @see sources
-- @see append_source_object
-- @see has_source_object
-- @see source
-- @see sources
function module:remove_source_object(obj)
for k, o in ipairs(self._private.sources) do
if obj == o then
table.remove(self._private.sources, k)
self:emit_signal("property::sources", self._private.sources)
return true
end
end
return false
end
--- Return true when `obj` is already a source object.
--
-- @method module:has_source_object
-- @tparam gears.object obj The object.
-- @see append_source_object
-- @see remove_source_object
-- @see source
-- @see sources
function module:has_source_object(obj)
for _, o in ipairs(self._private.sources) do
if o == obj then return true end
end
return false
end
--- A function called when the source changes.
--
--
-- @DOC_wibox_decl_doc_connection2_EXAMPLE@
--
-- The callback arguments are:
--
-- callback = function(source, target, sig_arg1, ...)
-- /\ /\ /\ /\
-- | | | |
-- The client -| | | |
-- It will be the widget -| | |
-- Signal first argument, the client -| |
-- All other signal arguments -|
--
-- @property callback
-- @tparam function callback
-- @propemits true false
function module:get_callback()
return self._private.callbacks[1]
end
function module:set_callback(cb)
self._private.callbacks = {cb}
self:emit_signal("property::callback", cb)
self._private.env_init = false
initiate(self)
end
-- When used in a declarative tree, this will be the
-- object it is initiated from. The `key` can be a number,
-- in which case we do nothing. It can also be a string,
-- in which case it becomes `target_property`
function module:_set_declarative_handler(parent, key, ids)
self.target = parent
self._private.env_ids = ids
self._private.env_init = false
if type(key) == "string" then
self.target_property = key
end
initiate(self)
end
--- Disconnect this object.
--
-- @method disconnect
-- @see reconnect
function module:disconnect()
if self._private.source_class then
for _, sig in ipairs(self._private.signals) do
self._private.source_class.disconnect_signal(
sig, self._private._callback
)
end
end
for _, src in ipairs(self._private.sources) do
for _, sig in ipairs(self._private.signals) do
src:disconnect_signal(sig, self._private._callback)
end
end
end
--- Reconnect this object.
--
-- @method reconnect
-- @see disconnect
function module:reconnect()
self:disconnect()
if self._private.source_class then
for _, sig in ipairs(self._private.signals) do
self._private.source_class.connect_signal(
sig, self._private._callback
)
end
end
for _, src in ipairs(self._private.sources) do
for _, sig in ipairs(self._private.signals) do
src:connect_signal(sig, self._private._callback)
end
end
end
--- Create a new `gears.connection` object.
--
-- @constructorfct gears.connection
-- @tparam table args
-- @tparam boolean args.initiate If the property should be set when the target object is defined.
-- @tparam boolean args.enabled Turn this connection on or off.
-- @tparam boolean args.signals A list of source object signals.
-- @tparam string args.signal The (source) signal to monitor.
-- @tparam gears.object args.target The object for the right side object of the connection.
-- @tparam string args.target_property The target object property to set when the source property changes.
-- @tparam string args.target_method Rather than use a property, call a method.
-- @tparam class|string args.source_class Use a whole class rather than an object as source.
-- @tparam gears.object args.source The source object (connection left hand side).
-- @tparam string args.source_property The source object(s)/class property.
-- @tparam gears.object args.sources A list of source objects (connection left hand side).
-- @tparam function args.callback A function called when the source changes.
local function new(_, args)
local self = gobject {
enable_properties = true,
}
rawset(self, "_private", {
enabled = true,
signals = {},
initiate = true,
sources = {},
callbacks = {},
source_properties = {},
target = nil,
_callback = function()
if not self._private.enabled then return end
set_target(self)
end
})
gtable.crush(self, module, true )
gtable.crush(self, args , false)
return self
end
--@DOC_object_COMMON@
return setmetatable(module, {__call = new})

View File

@ -172,28 +172,39 @@ end
-- @tparam string path The directory to search.
-- @tparam[opt] table exts Specific extensions to limit the search to. eg:`{ "jpg", "png" }`
-- If ommited, all files are considered.
-- @tparam[opt=false] boolean absolute_path Return the absolute path instead of the filename.
-- @treturn string|nil A randomly selected filename from the specified path (with
-- a specified extension if required) or nil if no suitable file is found.
-- a specified extension if required) or nil if no suitable file is found. If `absolute_path`
-- is set, then a path is returned instead of a file name.
-- @staticfct gears.filesystem.get_random_file_from_dir
function filesystem.get_random_file_from_dir(path, exts)
function filesystem.get_random_file_from_dir(path, exts, absolute_path)
local files, valid_exts = {}, {}
-- Transforms { "jpg", ... } into { [jpg] = #, ... }
if exts then for i, j in ipairs(exts) do valid_exts[j:lower()] = i end end
if exts then for i, j in ipairs(exts) do valid_exts[j:lower():gsub("^[.]", "")] = i end end
-- Build a table of files from the path with the required extensions
local file_list = Gio.File.new_for_path(path):enumerate_children("standard::*", 0)
-- This will happen when the directory doesn't exist.
if not file_list then return nil end
for file in function() return file_list:next_file() end do
if file:get_file_type() == "REGULAR" then
local file_name = file:get_display_name()
if not exts or valid_exts[file_name:lower():match(".+%.(.*)$") or ""] then
table.insert(files, file_name)
end
end
end
if #files == 0 then return nil end
-- Return a randomly selected filename from the file table
return #files > 0 and files[math.random(#files)] or nil
local file = files[math.random(#files)]
return absolute_path and (path:gsub("[/]*$", "") .. "/" .. file) or file
end
return filesystem

View File

@ -23,9 +23,6 @@ return
string = require("gears.string");
sort = require("gears.sort");
filesystem = require("gears.filesystem");
reactive = require("gears.reactive");
connection = require("gears.connection");
watcher = require("gears.watcher");
}
-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80

View File

@ -46,9 +46,11 @@ function object.add_signal()
end
--- Connect to a signal.
--
--@DOC_text_gears_object_signal_EXAMPLE@
-- @tparam string name The name of the signal
-- @tparam function func The callback to call when the signal is emitted
--
-- @tparam string name The name of the signal.
-- @tparam function func The callback to call when the signal is emitted.
-- @method connect_signal
function object:connect_signal(name, func)
assert(type(func) == "function", "callback must be a function, got: " .. type(func))
@ -92,10 +94,16 @@ local function make_the_gc_obey(func)
return func
end
--- Connect to a signal weakly. This allows the callback function to be garbage
-- collected and automatically disconnects the signal when that happens.
-- @tparam string name The name of the signal
-- @tparam function func The callback to call when the signal is emitted
--- Connect to a signal weakly.
--
-- This allows the callback function to be garbage collected and
-- automatically disconnects the signal when that happens.
-- **Warning:**
-- Only use this function if you really, really, really know what you
-- are doing.
--
-- @tparam string name The name of the signal.
-- @tparam function func The callback to call when the signal is emitted.
-- @method weak_connect_signal
function object:weak_connect_signal(name, func)
assert(type(func) == "function", "callback must be a function, got: " .. type(func))
@ -104,9 +112,9 @@ function object:weak_connect_signal(name, func)
sig.weak[func] = make_the_gc_obey(func)
end
--- Disonnect to a signal.
-- @tparam string name The name of the signal
-- @tparam function func The callback that should be disconnected
--- Disonnect from a signal.
-- @tparam string name The name of the signal.
-- @tparam function func The callback that should be disconnected.
-- @method disconnect_signal
function object:disconnect_signal(name, func)
local sig = find_signal(self, name)
@ -118,8 +126,8 @@ end
--
-- @tparam string name The name of the signal
-- @param ... Extra arguments for the callback functions. Each connected
-- function receives the object as first argument and then any extra arguments
-- that are given to emit_signal()
-- function receives the object as first argument and then any extra
-- arguments that are given to emit_signal()
-- @method emit_signal
function object:emit_signal(name, ...)
local sig = find_signal(self, name)

View File

@ -1,656 +0,0 @@
--- Utility module to convert functions to Excel like objects.
--
-- When converting a function into a `gears.reactive` object, all
-- properties accessed by the function are monitored for changes.
--
-- When such a change is detected, the `property::value` signal is emitted. When
-- used within a widget declarative construct, the property it is attached
-- to will also be automatically updated.
--
-- Theory
-- ======
--
-- ![Client geometry](../images/gears_reactive.svg)
--
-- To use this module and, more importantly, to understand how to write something
-- that actually works based on it, some background is required. Most AwesomeWM
-- objects are based on the `gears.object` module or it's `C` equivalent. Those
-- objects have "properties". Behind the scene, they have getters, setters and
-- a signal to notify the value changed.
--
-- `gears.reactive` adds a firewall like sandbox around the function. It
-- intercept any `gears.object` instance trying to cross the sandbox boundary
-- and replace them with proxy objects. Those proxies have built-in
-- introspection code to detect how they are used. This is then converted into
-- a list of objects and signals to monitor. Once one of the monitored object
-- emits one of the monitored signal, then the whole function is re-evaluated.
-- Each time the function is evaluated, the "target" properties are updated. The
-- reactive function result or any call to external function from within goes
-- through the firewall again and any proxies are removed.
--
-- That design has one big limitation. It cannot detect any changes which are
-- not directly part of `gears.object` instance. You cannot use random tables
-- and expect the function to be called when it's content change. To work
-- around this, it is recommanded to make "hub" objects to store the data used
-- within the reactive function.
--
-- Recommanded usage
-- =================
--
-- The best way to use `gears.reactive` is when the value used within the
-- expressions are part of other objects. It can be a `gears.watcher`, but
-- it can also be a custom object:
--
-- @DOC_wibox_widget_declarative_reactive_EXAMPLE@
--
-- Limitations
-- ===========
--
-- `gears.reactive` is pushing Lua way beyond what it has been designed for.
-- Because of this, there is some limitations.
--
-- * This module will **NOT** try to track the change of other
-- functions and methods called by the expression. It is **NOT** recursive
-- and only the top level properties are tracked. This is a feature, not a
-- bug. If it was recursive, this list of limitations or gotchas would be
-- endless.
-- * This only works with `gears.object` and Core API objects which implement
-- the `property::*****` signals correctly. If it is a regular Lua table
-- or the property signals are incorrectly used, the value changes cannot
-- be detected. If you find something that should work, but doesn't in
-- one of the AwesomeWM API, [please report a bug](https://github.com/awesomeWM/awesome/issues/new).
-- * More generally, when making a custom `gears.object` with custom setters,
-- it is the programmer responsibility to emit the signal. It is also
-- required to only emit those signals when the property actually changes to
-- avoid an unecessary re-evaluations.
-- * Changing the type of the variables accessed by the reactive function
-- (its "upvalues") after the reactive expression has been created wont
-- be detected. It will cause missed updates and, potentially, hard to debug
-- Lua errors within the proxy code itself.
-- * Internally, the engine tries its best to prevent the internal proxy objects
-- to leak out the sandbox. However this cannot be perfect, at least not
-- without adding limitation elsewhere. It is probably worth reporting a bug if
-- you encounter such an issue. But set your expectations, not all corner case
-- can be fixed.
-- * Don't use rawset in the expression.
-- * If the return value is a table, only the first 3 nesting levels are sanitized.
-- Avoid using nested tables in the returned value if you can. `gears.object`
-- instances *should* be fine.
-- * There is currently no way to disable a reactive expression once it's
-- been defined. This may change eventually.
-- * Rio Lua 5.1 (not LuaJIT 2.1) is currently not support. If you need it,
-- [please report a bug](https://github.com/awesomeWM/awesome/issues/new).
--
-- @author Emmanuel Lepage-Vallee &lt;elv1313@gmail.com&gt;
-- @copyright 2017-2020 Emmanuel Lepage-Vallee
-- @classmod gears.reactive
-- This file is provided under the BSD 2 clause license since it is better than
-- any existing implementation (most of which only support Lua 5.1).
--
-- Copyright 2020 Emmanuel Lepage-Vallee &lt;elv1313@gmail.com&gt;
--
-- Redistribution and use in source and binary forms, with or without modification,
-- are permitted provided that the following conditions are met:
--
-- 1. Redistributions of source code must retain the above copyright notice, this
-- list of conditions and the following disclaimer.
--
-- 2. Redistributions in binary form must reproduce the above copyright notice,
-- this list of conditions and the following disclaimer in the documentation
-- and/or other materials provided with the distribution.
--
-- THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
-- AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
-- IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
-- DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
-- FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
-- (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
-- OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
-- THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
-- NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
-- IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
local gobject = require("gears.object")
local gtable = require("gears.table")
local gtimer = require("gears.timer")
local unpack = unpack or table.unpack -- luacheck: globals unpack (compatibility with Lua 5.1)
-- It's only possible to know when something changes if it has signals.
local function is_gears_object(input)
return type(input) == "table" and rawget(input, "emit_signal")
end
-- If the sandbox proxies were to leave the reactive expressions, bad
-- things would happen. Catch functions and constructors to make sure
-- we strip away the proxy.
local function is_callable(object)
local t = type(object)
if t == "function" then return true end
if not (t == "table" or t == "userdata") then return false end
local mt = getmetatable(object)
return mt and mt.__call ~= nil
end
-- Get the upvalue current value or cached value.
local function get_upvalue(proxy_md)
if type(proxy_md) ~= "table" then return proxy_md end
return proxy_md._reactive.getter and
proxy_md._reactive.getter() or proxy_md._reactive.input
end
-- Attempt to remove the proxy references. There might be loops
-- and stackoverflows, so it will only go 3 level deep. With some
-- luck, the result wont be a table. Even then, hopefully it wont
-- contain "raw" sub-tables. If it contains `gears.object`, it's
-- safe since they will be proxied.
local sanitize_return = nil
sanitize_return = function(input, depth)
if (depth or 0) > 3 then return input end
-- The first `input` is always a table, so it is safe to call `pairs`.
for k, v in pairs(input) do
local t = type(v)
if t == "table" then
if v._reactive then
-- No need to go deeper unless the user used rawset.
input[k] = get_upvalue(v)
else
-- It's an unproxied raw table, dig further.
sanitize_return(v, (depth or 1) + 1)
end
end
end
return input
end
-- The actual code that connects the gears.object signals.
local function add_key(self, key)
if type(key) ~= "string" or not is_gears_object(get_upvalue(self)) then return end
local input, objs = get_upvalue(self), self._reactive.origin.objects
objs[input] = objs[input] or {}
if objs[input][key] then return end
objs[input][key] = true
-- Prefer the `weak` version to avoid memory leaks. If nothing is holding a
-- reference, then the value wont change. Yes, there is some hairy corner cases.
-- Yes, the expression proxy itself is holding a strong reference so this wont
-- get used very often.
if input.weak_connect_signal then
input:weak_connect_signal("property::"..key, self._reactive.origin.callback)
else
input:connect_signal("property::"..key, self._reactive.origin.callback)
end
end
local function gen_proxy_md(input, origin, getter)
assert(input and origin)
return {
input = (not getter) and input or nil,
getter = getter,
children = {},
properties = {},
origin = origin
}
end
local create_proxy = nil
-- Un-proxy the function input and proxy its output.
local function proxy_call(self, ...)
local newargs, newrets = {}, {}
-- Remove the proxies before calling the function.
for _, v in ipairs{...} do
if type(v) == "table" and v._reactive then
v = get_upvalue(v)
end
table.insert(newargs, v)
end
local rets
if #newargs > 0 then
rets = {get_upvalue(self)(unpack(newargs))}
else
rets = {get_upvalue(self)(nil)}
end
-- Add new proxies to make sure changes are detected if something
-- like `a_function().foo` is used. Since we still have legacy accessor
-- implemented as method (t:clients(), c:tags(), etc), this is actually
-- likely to happen.
for _, v in ipairs(rets) do
local ret, _ = create_proxy(v, self._reactive.origin)
table.insert(newrets, ret)
end
return unpack(newrets)
end
-- Build a tree of proxies or return the primitives.
local function proxy_index(self, key)
local up = get_upvalue(self)
-- Since it would otherwise print a cryptic error.
assert(
type(up) == "table",
"Trying to index a "..type(up).." value in a `gears.reactive` expression."
)
local upk = up[key]
-- Connect.
add_key(self, key)
local ret, is_proxy = create_proxy(upk, self._reactive.origin)
-- Always query the non-proxy upvalue. We cannot detect if they
-- change.
if is_proxy then
rawset(self, key, ret)
end
return ret
end
-- Set valuers trough the proxy.
local function proxy_newindex(self, key, value)
rawset(self, key, create_proxy(value, self._reactive.origin))
-- Strip the proxy before setting the value on the original object.
if type(value) == "table" and value._reactive then
value = get_upvalue(value)
end
local v = get_upvalue(self)
v[key] = value
-- Connect.
if is_gears_object(v) and not self._reactive.origin.objects[self][key] then
add_key(self, key)
end
end
-- It's possible that multiple proxy points to the same value.
-- While we could make a large map of all proxies, it's simpler
-- to just implement __eq/__lt/__le and be done.
local function proxy_equal(a, b)
a, b = get_upvalue(a), get_upvalue(b)
return a == b
end
local function proxy_lesser(a, b)
a, b = get_upvalue(a), get_upvalue(b)
return a < b
end
local function proxy_lesser_equal(a, b)
a, b = get_upvalue(a), get_upvalue(b)
return a <= b
end
local function proxy_tostring(o)
return tostring(get_upvalue(o))
end
-- Wrap tables and functions into a proxy object.
create_proxy = function(input, origin, getter)
local t = type(input)
local is_call = is_callable(input)
-- Everything but the tables are immutable.
if t ~= "table" and not is_call then return input, false end
-- Remove any foreign proxy.
if t ~= "function" and input._reactive and input._reactive.origin ~= origin then
input = get_upvalue(input)
end
return setmetatable({_reactive = gen_proxy_md(input, origin, getter)}, {
__index = proxy_index,
__newindex = proxy_newindex,
__call = proxy_call,
__eq = proxy_equal,
__lt = proxy_lesser,
__le = proxy_lesser_equal,
__tostring = proxy_tostring
}), true
end
-- Create a "fake" upvalue ref because otherwise the sandbox could
-- "leak" and set upvalue in actual code outside of the function.
-- By "leak", I mean the proxy would become visible from outside
-- of the sandbox by the reactive expression "siblings" sharing the
-- same environment.
local function copy_upvalue_reference(loaded, idx, value)
-- Make sure it has a fresh upvalue reference. Something that
-- we are sure cannot be shared with something else.
local function fake_upvalue_env()
return value
end
debug.upvaluejoin(loaded, idx, fake_upvalue_env, 1) --luacheck: ignore
end
-- Create a meta-getter for the original `fct` upvalue at index `idx`.
-- This will allow all the other code to dynamically get a "current" version
-- of the upvalue rather than something from the cache. It will also avoid
-- having to store a strong reference to the original value.
local function create_upvalue_getter(fct, idx)
local placeholder = nil
local function fake_upvalue_getter()
return placeholder
end
-- Using this, each time `fake_upvalue_getter` is called, it
-- will return the current upvalue. This means we don't have to
-- cache it. Thus, the cache cannot get outdated. The drawback if
-- that if the type changes from an object to a primitive, it
-- will explode.
debug.upvaluejoin(fake_upvalue_getter, 1, fct, idx) --luacheck: ignore
return fake_upvalue_getter
end
-- Create a copy of the function and replace it's environment.
local function sandbox(fct, env, vars, values)
-- We must serialize the function for several reasons. First of all, it
-- might get wrapped into multiple `gears.reactive` objects (which we should
-- handle differently, but currently allow) or share the "upvalue environemnt"
-- (this variables from the upper execution context and stack frames).
--
-- For example, if 2 function both access the variables "foo" and "bar"
-- from the global context, they might end up with the same execution
-- environment. If we didn't create a copy, calling `debug.upvaluejoin`
-- woulc affect both functions.
local dump = string.dump(fct)
local loaded = load(dump, nil, nil, env)
-- It doesn't seem possible to "just remove" the upvalues. It's not possible
-- to have the catch-all in the metatable. It would have been nicer since this
-- code is redundant with the metatable (which are still needed for _G and _ENV
-- content).
for name, k in pairs(vars) do
if is_callable(values[name]) or type(values[name]) == "table" then
-- For table, functions and objects, use a proxy upvalue.
copy_upvalue_reference(
loaded,
k,
create_proxy(values[name], env.__origin, create_upvalue_getter(fct ,k))
)
else
-- For string, booleans, numbers and function, use the real upvalue.
-- This means if it is changed by something else, the sandboxed
-- copy sees the change.
debug.upvaluejoin(loaded, k, fct, k) --luacheck: ignore
end
end
return loaded
end
-- `getfenv` and `setfenv` would simplify this a lot, but are not
-- present in newer versions of Lua. Technically, we could have 3
-- implementation optimized for each Lua versions, but this one
-- seems to be portable (until now...). So while the performance
-- isn't as good as it could be, it's maintainable.
local function getfenv_compat(fct, root)
local vars, soft_env = {}, {}
local origin = {
objects = {},
callback = function()
root:emit_signal("property::value")
end
}
for i = 1, math.huge do
local name, val = debug.getupvalue(fct, i)
if name == nil then break end
soft_env[name] = val
if not vars[name] then
vars[name] = i
end
end
-- Create the sandbox.
local self = {__origin = origin}
local sandboxed = sandbox(fct, self, vars, soft_env)
return setmetatable(self, {
__index = function(_, key)
if _G[key] then
return create_proxy(_G[key], origin)
end
return soft_env[key]
end,
__newindex = function(_, key, value)
-- This `if` might be dead code.
if vars[key] then
debug.setupvalue(sandboxed, vars[key], value)
-- Do not try to disconnect the old one. It would make the code too complex.
if (not soft_env[key]) or get_upvalue(soft_env[key]) ~= value then
soft_env[key] = create_proxy(value, origin)
end
else
rawset(vars, key, create_proxy(value, origin))
end
end,
__call = function()
return unpack(sanitize_return({sandboxed()}))
end
})
end
local module = {}
local function real_set_value(self, force)
if self._private.delayed_started and not force then return end
local function value_transaction()
-- If `get_value` was called, then this transaction is no longer
-- pending.
if not self._private.delayed_started then return end
-- Reset the delay in case the setter causes the expression to
-- change.
self._private.delayed_started = false
self._private.value = self._private.origin()
self._private.evaluated = true
for _, target in pairs(self._private.targets) do
local obj, property = target[1], target[2]
obj[property] = self._private.value
end
end
if self._private.delayed and not force then
gtimer.delayed_call(value_transaction)
self._private.delayed_started = true
else
self._private.delayed_started = true
value_transaction()
end
end
--- A function which will be evaluated each time its content changes.
--
-- @property expression
-- @param function
-- @propemits true false
--- The current value of the expression.
-- @property value
-- @propemits false false
--- Only evaluate once per event loop iteration.
--
-- In most case this is a simple performance win, but there is some
-- case where you might want the expression to be evaluated each time
-- one of the upvalue "really" change rather than batch them. This
-- option is enabled by default.
--
-- @property delayed
-- @tparam[opt=true] boolean delayed
-- @propemits true false
function module:get_delayed()
return self._private.delayed
end
function module:set_delayed(value)
if value == self._private.delayed then return end
self._private.delayed = value
self:emit_signal("property::delayed", value)
end
function module:set_expression(value)
self:disconnect()
self._private.origin = getfenv_compat(value, self)
self:connect_signal("property::value", real_set_value)
end
function module:get_value()
-- This will call `real_set_value`.
if (not self._private.evaluated) and self._private.origin then
self:refresh()
end
if self._private.delayed_started then
real_set_value(self, true)
end
return self._private.value
end
function module:set_value()
assert(false, "A value cannot be set on a `gears.reactive` instance.")
end
--- Disconnect all expression signals.
--
-- @method disconnect
function module:disconnect()
if self._private.origin then
for obj, properties in pairs(self._private.origin.__origin.objects) do
for property in pairs(properties) do
obj:disconnect_signal("property::"..property, self._private.origin.__origin.callback)
end
end
end
end
--- Recompute the expression.
--
-- When the expression uses a non-object upvalue, the changes cannot
-- be auto-retected. Calling `:refresh()` will immediatly recompute the
-- expression.
--
-- @method refresh
function module:refresh()
if self._private.origin then
self:emit_signal("property::value")
end
end
-- Add a new target property and object.
function module:_add_target(object, property)
local hash = tostring(object)..property
if self._private.targets[hash] then return end
self._private.targets[hash] = {object, property}
self:emit_signal("target_added", object, property)
if self._private.evaluated then
object[property] = self._private.value
else
real_set_value(self)
end
end
--- Emitted when a new property is attached to this reactive expression.
--
-- @signal target_added
-- @tparam gears.object object The object (often the widget).
-- @tparam string property The property name.
function module:_set_declarative_handler(parent, key)
-- Lua "properties", aka table.foo must be strings.
assert(
type(key) == "string",
"gears.reactive can only be used ob object properties"
)
self:_add_target(parent, key)
end
--- Create a new `gears.reactive` object.
-- @constructorfct gears.reactive
-- @tparam table args
-- @tparam function args.expression A function which accesses other `gears.object`.
-- @tparam gears.object args.object Any AwesomeWM object.
-- @tparam string args.property The name of the property to track.
local function new(_, args)
-- It *can* be done. Actually, it is much easier to implement this module
-- on 5.1 since `setfenv` is much simpler than `debug.upvaluejoin`. However,
-- unless someone asks, then why support 2 incompatible code paths. Luajit 2.1
-- supports `debug.upvaluejoin`.
assert(
debug.upvaluejoin, --luacheck: ignore
"Sorry, `gears.reactive` doesn't support Lua 5.1 at this time"
)
if type(args) == "function" then
args = {expression = args}
end
local self = gobject {
enable_properties = true,
}
rawset(self, "_private", {
targets = {},
delayed = true
})
gtable.crush(self, module, true)
gtable.crush(self, args, false)
self:connect_signal("property::value", real_set_value)
return self
end
--@DOC_object_COMMON@
return setmetatable(module, {__call = new})

View File

@ -39,27 +39,31 @@ function gtable.join(...)
return ret
end
--- Override elements in the first table by the one in the second.
--- Override elements in the target table with values from the source table.
--
-- Note that this method doesn't copy entries found in `__index`.
-- Nested tables are copied by reference and not recursed into.
--
-- @tparam table t the table to be overriden
-- @tparam table set the table used to override members of `t`
-- @tparam[opt=false] bool raw Use rawset (avoid the metatable)
-- @treturn table t (for convenience)
-- @tparam table target The target table. Values from `source` will be copied
-- into this table.
-- @tparam table source The source table. Its values will be copied into
-- `target`.
-- @tparam[opt=false] bool raw If `true`, values will be assigned with `rawset`.
-- This will bypass metamethods on `target`.
-- @treturn table The target table.
-- @staticfct gears.table.crush
function gtable.crush(t, set, raw)
function gtable.crush(target, source, raw)
if raw then
for k, v in pairs(set) do
rawset(t, k, v)
for k, v in pairs(source) do
rawset(target, k, v)
end
else
for k, v in pairs(set) do
t[k] = v
for k, v in pairs(source) do
target[k] = v
end
end
return t
return target
end
--- Pack all elements with an integer key into a new table.
@ -165,6 +169,23 @@ function gtable.keys(t)
return keys
end
--- Get the number of keys in a table, both integer and string indicies.
--
-- This is functionally equivalent, but faster than `#gears.table.keys(t)`.
--
-- @DOC_text_gears_table_count_keys_EXAMPLE@
--
-- @tparam table t The table for which to count the keys.
-- @treturn number The number of keys in the table.
-- @staticfct gears.table.count_keys
function gtable.count_keys(t)
local count = 0
for _ in pairs(t) do
count = count + 1
end
return count
end
--- Filter a table's keys for certain content type.
--
-- @tparam table t The table to retrieve the keys for.
@ -208,7 +229,8 @@ end
--- Clone a table.
--
-- @tparam table t The table to clone.
-- @tparam[opt=true] bool deep Create a deep clone?
-- @tparam[opt=true] bool deep If `true`, recurse into nested tables to create
-- a deep clone.
-- @treturn table A clone of `t`.
-- @staticfct gears.table.clone
function gtable.clone(t, deep)
@ -230,14 +252,16 @@ end
-- `first_index` has to be specified.
--
-- @tparam table t The input table.
-- @param value A value from the table.
-- @tparam[opt=1] number step_size How many element forward (or backward) to pick.
-- @tparam[opt=nil] function filter An optional function. When it returns
-- `false`, the element are skipped until a match if found. It takes the value
-- as its sole parameter.
-- @param value The start value. Must be an element of the input table `t`.
-- @tparam[opt=1] number step_size The amount to increment the index by.
-- When this is negative, the function will cycle through the table backwards.
-- @tparam[opt=nil] function filter An optional filter function. It receives a
-- value from the table as parameter and should return a boolean. If it
-- returns `false`, the value is skipped and `cycle_value` tries the next one.
-- @tparam[opt=1] number start_at Where to start the lookup from.
-- @return The value. If no element match, then `nil` is returned.
-- @treturn number|nil The element (if any) key.
-- @return The next eligible value. If no value matches, `nil` is returned.
-- @treturn number|nil If a value is found, this is its index within the input
-- table.
-- @staticfct gears.table.cycle_value
function gtable.cycle_value(t, value, step_size, filter, start_at)
local k = gtable.hasitem(t, value, true, start_at)
@ -287,17 +311,23 @@ function gtable.iterate(t, filter, start)
end
end
--- Merge items from one table to another one.
--- Merge items from the source table into the target table.
--
-- @tparam table t The container table
-- @tparam table set The mixin table.
-- @treturn table (for convenience).
-- Note that this only considers the array part of `source` (same semantics
-- as `ipairs`).
-- Nested tables are copied by reference and not recursed into.
--
-- @tparam table target The target table. Values from `source` will be copied
-- into this table.
-- @tparam table source The source table. Its values will be copied into
-- `target`.
-- @treturn table The target table.
-- @staticfct gears.table.merge
function gtable.merge(t, set)
for _, v in ipairs(set) do
table.insert(t, v)
function gtable.merge(target, source)
for _, v in ipairs(source) do
table.insert(target, v)
end
return t
return target
end
--- Update the `target` table with entries from the `new` table.
@ -384,7 +414,7 @@ end
--- Map a function to a table.
--
-- The function is applied to each value on the table, returning a modified
-- The function is applied to each value in the table, returning a modified
-- table.
--
-- @tparam function f The function to be applied to each value in the table.
@ -400,3 +430,5 @@ function gtable.map(f, tbl)
end
return gtable
-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80

View File

@ -59,7 +59,6 @@ local traceback = debug.traceback
local unpack = unpack or table.unpack -- luacheck: globals unpack (compatibility with Lua 5.1)
local glib = require("lgi").GLib
local object = require("gears.object")
local gtable = require("gears.table")
local protected_call = require("gears.protected_call")
local gdebug = require("gears.debug")
@ -88,11 +87,11 @@ local timer = { mt = {} }
-- @method start
-- @emits start
function timer:start()
if self._private.source_id ~= nil then
if self.data.source_id ~= nil then
gdebug.print_error(traceback("timer already started"))
return
end
self._private.source_id = glib.timeout_add(glib.PRIORITY_DEFAULT, self._private.timeout * 1000, function()
self.data.source_id = glib.timeout_add(glib.PRIORITY_DEFAULT, self.data.timeout * 1000, function()
protected_call(self.emit_signal, self, "timeout")
return true
end)
@ -100,15 +99,17 @@ function timer:start()
end
--- Stop the timer.
--
-- Does nothing if the timer isn't running.
--
-- @method stop
-- @emits stop
function timer:stop()
if self._private.source_id == nil then
gdebug.print_error(traceback("timer not started"))
if self.data.source_id == nil then
return
end
glib.source_remove(self._private.source_id)
self._private.source_id = nil
glib.source_remove(self.data.source_id)
self.data.source_id = nil
self:emit_signal("stop")
end
@ -119,7 +120,7 @@ end
-- @emits start
-- @emits stop
function timer:again()
if self._private.source_id ~= nil then
if self.data.source_id ~= nil then
self:stop()
end
self:start()
@ -135,28 +136,24 @@ end
-- @param number
-- @propemits true false
function timer:get_timeout()
return self._private.timeout
end
local timer_instance_mt = {
__index = function(self, property)
if property == "timeout" then
return self.data.timeout
elseif property == "started" then
return self.data.source_id ~= nil
end
function timer:get_started()
return self._private.source_id ~= nil
end
return timer[property]
end,
function timer:set_started(value)
if value == self:get_started() then return end
if value then
self:start()
else
self:stop()
__newindex = function(self, property, value)
if property == "timeout" then
self.data.timeout = tonumber(value)
self:emit_signal("property::timeout", value)
end
end
end
function timer:set_timeout(value)
self._private.timeout = tonumber(value)
self:emit_signal("property::timeout", value)
end
}
--- Create a new timer object.
-- @tparam table args Arguments.
@ -170,33 +167,10 @@ end
-- @constructorfct gears.timer
function timer.new(args)
args = args or {}
local ret = object {
enable_properties = true,
enable_auto_signals = true,
}
local ret = object()
gtable.crush(ret, timer, true)
rawset(ret, "_private", { timeout = 0 })
-- Preserve backward compatibility with Awesome 4.0-4.3 use of "data"
-- rather then "_private".
rawset(ret, "data", setmetatable({}, {
__index = function(_, key)
gdebug.deprecate(
"gears.timer.data is deprecated, use normal properties",
{deprecated_in=5}
)
return ret._private[key]
end,
__newindex = function(_, key, value)
gdebug.deprecate(
"gears.timer.data is deprecated, use normal properties",
{deprecated_in=5}
)
ret._private[key] = value
end
}))
ret.data = { timeout = 0 } --TODO v5 rename to ._private
setmetatable(ret, timer_instance_mt)
for k, v in pairs(args) do
ret[k] = v
@ -220,13 +194,17 @@ function timer.new(args)
return ret
end
--- Create a timeout for calling some callback function.
-- When the callback function returns true, it will be called again after the
-- same timeout. If false is returned, no more calls will be done. If the
-- callback function causes an error, no more calls are done.
--- Create a simple timer for calling the `callback` function continuously.
--
-- This is a small wrapper around `gears.timer`, that creates a timer based on
-- `callback`.
-- The timer will run continuously and call `callback` every `timeout` seconds.
-- It is stopped when `callback` returns `false`, when `callback` throws an
-- error or when the `:stop()` method is called on the return value.
--
-- @tparam number timeout Timeout in seconds (e.g. 1.5).
-- @tparam function callback Function to run.
-- @treturn timer The timer object that was set up.
-- @treturn timer The new timer object.
-- @staticfct gears.timer.start_new
-- @see gears.timer.weak_start_new
function timer.start_new(timeout, callback)
@ -241,14 +219,18 @@ function timer.start_new(timeout, callback)
return t
end
--- Create a timeout for calling some callback function.
-- This function is almost identical to `gears.timer.start_new`. The only difference
-- is that this does not prevent the callback function from being garbage
-- collected. After the callback function was collected, the timer returned
-- will automatically be stopped.
--- Create a simple timer for calling the `callback` function continuously.
--
-- This function is almost identical to `gears.timer.start_new`. The only
-- difference is that this does not prevent the callback function from being
-- garbage collected.
-- In addition to the conditions in `gears.timer.start_new`,
-- this timer will also stop if `callback` was garbage collected since the
-- previous run.
--
-- @tparam number timeout Timeout in seconds (e.g. 1.5).
-- @tparam function callback Function to start.
-- @treturn timer The timer object that was set up.
-- @treturn timer The new timer object.
-- @staticfct gears.timer.weak_start_new
-- @see gears.timer.start_new
function timer.weak_start_new(timeout, callback)
@ -275,7 +257,7 @@ function timer.run_delayed_calls_now()
delayed_calls = {}
end
--- Call the given function at the end of the current main loop iteration
--- Call the given function at the end of the current GLib event loop iteration.
-- @tparam function callback The function that should be called
-- @param ... Arguments to the callback function
-- @staticfct gears.timer.delayed_call

View File

@ -1,22 +1,7 @@
---------------------------------------------------------------------------
-- Functions for setting the wallpaper.
--
-- There are two levels of functionality provided by this module:
--
-- The low-level functionality consists of two functions.
-- @{set} an already-prepared wallpaper on all screens and @{prepare_context}
-- prepares things to draw a new wallpaper.
--
-- The low-level API can for example be used to set solid red as a wallpaper
-- (see @{gears.color} for details on the supported syntax):
--
-- gears.wallpaper.set("#ff0000")
--
-- Ontop of these low-level functions, the remaining functions implement more
-- useful functionality. For example, given a screen object `s`, an image can be
-- set as the wallpaper as follows:
--
-- gears.wallpaper.maximized("path/to/image.png", s)
-- This module is deprecated, please use `awful.wallpaper`.
--
-- @author Uli Schlachter
-- @copyright 2012 Uli Schlachter
@ -51,8 +36,10 @@ end
-- @param s The screen to set the wallpaper on or nil for all screens
-- @return[1] The available geometry (table with entries width and height)
-- @return[1] A cairo context that the wallpaper should be drawn to.
-- @staticfct gears.wallpaper.prepare_context
-- @deprecated gears.wallpaper.prepare_context
function wallpaper.prepare_context(s)
debug.deprecate("Use `awful.wallpaper`", {deprecated_in=5})
s = get_screen(s)
local root_width, root_height = root.size()
@ -110,8 +97,10 @@ end
-- @param pattern The wallpaper that should be set. This can be a cairo surface,
-- a description for gears.color or a cairo pattern.
-- @see gears.color
-- @staticfct gears.wallpaper.set
-- @deprecated gears.wallpaper.set
function wallpaper.set(pattern)
debug.deprecate("Use `awful.wallpaper`", {deprecated_in=5})
if cairo.Surface:is_type_of(pattern) then
pattern = cairo.Pattern.create_for_surface(pattern)
end
@ -121,7 +110,7 @@ function wallpaper.set(pattern)
if not cairo.Pattern:is_type_of(pattern) then
error("wallpaper.set() called with an invalid argument")
end
root.wallpaper(pattern._native)
root.wallpaper(pattern)
end
--- Set a centered wallpaper.
@ -132,8 +121,10 @@ end
-- gears.color. The default is black.
-- @param scale The scale factor for the wallpaper. Default is 1 (original size).
-- @see gears.color
-- @staticfct gears.wallpaper.centered
-- @deprecated gears.wallpaper.centered
function wallpaper.centered(surf, s, background, scale)
debug.deprecate("Use `awful.wallpaper`", {deprecated_in=5})
local geom, cr = wallpaper.prepare_context(s)
local original_surf = surf
surf = surface.load_uncached(surf)
@ -172,8 +163,10 @@ end
-- @param s The screen whose wallpaper should be set. Can be nil, in which case
-- all screens are set.
-- @param offset This can be set to a table with entries x and y.
-- @staticfct gears.wallpaper.tiled
-- @deprecated gears.wallpaper.tiled
function wallpaper.tiled(surf, s, offset)
debug.deprecate("Use `awful.wallpaper`", {deprecated_in=5})
local _, cr = wallpaper.prepare_context(s)
if offset then
@ -202,8 +195,10 @@ end
-- @param ignore_aspect If this is true, the image's aspect ratio is ignored.
-- The default is to honor the aspect ratio.
-- @param offset This can be set to a table with entries x and y.
-- @staticfct gears.wallpaper.maximized
-- @deprecated gears.wallpaper.maximized
function wallpaper.maximized(surf, s, ignore_aspect, offset)
debug.deprecate("Use `awful.wallpaper`", {deprecated_in=5})
local geom, cr = wallpaper.prepare_context(s)
local original_surf = surf
surf = surface.load_uncached(surf)
@ -243,8 +238,10 @@ end
-- @param background The background color that should be used. Gets handled via
-- gears.color. The default is black.
-- @see gears.color
-- @staticfct gears.wallpaper.fit
-- @deprecated gears.wallpaper.fit
function wallpaper.fit(surf, s, background)
debug.deprecate("Use `awful.wallpaper`", {deprecated_in=5})
local geom, cr = wallpaper.prepare_context(s)
local original_surf = surf
surf = surface.load_uncached(surf)

View File

@ -1,733 +0,0 @@
--- Fetch information at a specific interval.
--
-- @author Emmanuel Lepage-Vallee &lt;elv1313@gmail.com&gt;
-- @copyright 2020 Emmanuel Lepage-Vallee
-- @classmod gears.watcher
local capi = {awesome = awesome}
local protected_call = require("gears.protected_call")
local gtimer = require("gears.timer")
local gtable = require("gears.table")
local lgi = require("lgi")
local Gio = lgi.Gio
local GLib = lgi.GLib
local module = {}
-- This is awful.util.shell
module._shell = os.getenv("SHELL") or "/bin/sh"
local end_of_file
do
-- API changes, bug fixes and lots of fun. Figure out how a EOF is signalled.
local input
if not pcall(function()
-- No idea when this API changed, but some versions expect a string,
-- others a table with some special(?) entries
input = Gio.DataInputStream.new(Gio.MemoryInputStream.new_from_data(""))
end) then
input = Gio.DataInputStream.new(Gio.MemoryInputStream.new_from_data({}))
end
local line, length = input:read_line()
if not line then
-- Fixed in 2016: NULL on the C side is transformed to nil in Lua
end_of_file = function(arg)
return not arg
end
elseif tostring(line) == "" and #line ~= length then
-- "Historic" behaviour for end-of-file:
-- - NULL is turned into an empty string
-- - The length variable is not initialized
-- It's highly unlikely that the uninitialized variable has value zero.
-- Use this hack to detect EOF.
end_of_file = function(arg1, arg2)
return #arg1 ~= arg2
end
else
assert(tostring(line) == "", "Cannot determine how to detect EOF")
-- The above uninitialized variable was fixed and thus length is
-- always 0 when line is NULL in C. We cannot tell apart an empty line and
-- EOF in this case.
require("gears.debug").print_warning("Cannot reliably detect EOF on an "
.. "GIOInputStream with this LGI version")
end_of_file = function(arg)
return tostring(arg) == ""
end
end
end
module._end_of_file = end_of_file
function module._read_lines(input_stream, line_callback, done_callback, close)
local stream = Gio.DataInputStream.new(input_stream)
local function done()
if close then
stream:close()
end
stream:set_buffer_size(0)
if done_callback then
protected_call(done_callback)
end
end
local start_read, finish_read
start_read = function()
stream:read_line_async(GLib.PRIORITY_DEFAULT, nil, finish_read)
end
finish_read = function(obj, res)
local line, length = obj:read_line_finish(res)
if type(length) ~= "number" then
-- Error
print("Error in awful.spawn.read_lines:", tostring(length))
done()
elseif end_of_file(line, length) then
-- End of file
done()
else
-- Read a line
-- This needs tostring() for older lgi versions which returned
-- "GLib.Bytes" instead of Lua strings (I guess)
protected_call(line_callback, tostring(line))
-- Read the next line
start_read()
end
end
start_read()
end
function module._with_line_callback(cmd, callbacks)
local stdout_callback, stderr_callback, done_callback, exit_callback =
callbacks.stdout, callbacks.stderr, callbacks.output_done, callbacks.exit
local have_stdout, have_stderr = stdout_callback ~= nil, stderr_callback ~= nil
local pid, _, stdin, stdout, stderr = capi.awesome.spawn(cmd,
false, false, have_stdout, have_stderr, exit_callback)
if type(pid) == "string" then
-- Error
return pid
end
local done_before = false
local function step_done()
if have_stdout and have_stderr and not done_before then
done_before = true
return
end
if done_callback then
done_callback()
end
end
if have_stdout then
module._read_lines(Gio.UnixInputStream.new(stdout, true),
stdout_callback, step_done, true)
end
if have_stderr then
module._read_lines(Gio.UnixInputStream.new(stderr, true),
stderr_callback, step_done, true)
end
assert(stdin == nil)
return pid
end
function module._easy_async(cmd, callback)
local stdout = ''
local stderr = ''
local exitcode, exitreason
local function parse_stdout(str)
stdout = stdout .. str .. "\n"
end
local function parse_stderr(str)
stderr = stderr .. str .. "\n"
end
local function done_callback()
return callback(stdout, stderr, exitreason, exitcode)
end
local exit_callback_fired = false
local output_done_callback_fired = false
local function exit_callback(reason, code)
exitcode = code
exitreason = reason
exit_callback_fired = true
if output_done_callback_fired then
return done_callback()
end
end
local function output_done_callback()
output_done_callback_fired = true
if exit_callback_fired then
return done_callback()
end
end
return module._with_line_callback(
cmd, {
stdout=parse_stdout,
stderr=parse_stderr,
exit=exit_callback,
output_done=output_done_callback
})
end
function module._read_async(path, callback, fail_callback)
local cancel = Gio.Cancellable()
Gio.File.new_for_path(path):load_contents_async(cancel, function(file, task)
local content = file:load_contents_finish(task)
if content then
callback(path, content)
elseif fail_callback then
fail_callback(path)
end
end)
return cancel
end
-- Posix files and commands always end with a newline.
-- It is nearly always unwanted, so we strip it by default.
local function remove_posix_extra_newline(content)
-- Remove the trailing `\n`
if content:sub(-1) == '\n' then
content = content:sub(1, -2)
end
return content
end
--- Make sure we sort transactions in a way obsolete ones are
-- not used.
local function add_transaction(self, transaction)
table.insert(self._private.transactions, transaction)
end
local function remove_transaction(self, transaction)
-- Too late, abort.
for _, cancel in pairs(transaction.pending) do
cancel:cancel()
end
for k, t in ipairs(self._private.transactions) do
if t == transaction then
table.remove(self._private.transactions, k)
end
end
end
-- Keys can also be labels or object, # wont work.
local function count_files(files)
local ret = 0
for _ in pairs(files) do
ret = ret + 1
end
return ret
end
-- When there is multiple files, we need to wait
-- until are of them are read.
local function gen_file_transaction(self)
if count_files(self.files) == 0 then return nil end
local ret = {
counter = count_files(self.files),
files = gtable.clone(self.files, false),
filter = self.filter,
labels = {},
content = {},
failed = {},
pending = {},
}
local function finished(file, content)
assert(not ret.content[file])
self:emit_signal("file::acquired", file, content)
ret.pending[file] = nil
ret.counter = ret.counter - 1
ret.content[file] = content
if ret.counter > 0 then return end
self:emit_signal("files::acquired", ret.content, ret.failed)
-- Make the final table using the stored keys.
local contents = {}
for path, ctn in pairs(ret.content) do
if self.strip_newline then
ctn = remove_posix_extra_newline(ctn)
end
contents[self._private.file_keys[path]] = ctn
if self.labels_as_properties and type(ret.labels[path]) == "string" then
local val
if ret.filter then
val = ret.filter(ret.labels[path], ctn)
else
val = ctn
end
-- Make sure the signals are not fired for nothing.
if val ~= self[ret.labels[path]] then
self[ret.labels[path]] = val
end
end
end
local ctn = count_files(ret.files) == 1 and contents[next(contents)] or contents
if ret.filter and not self.labels_as_properties then
self._private.value = ret.filter(ctn, ret.failed)
else
self._private.value = ctn
end
self:emit_signal("property::value", self._private.value)
remove_transaction(self, ret)
end
local function read_error(file)
ret.pending[file] = nil
table.insert(ret.failed, file)
self:emit_signal("file::failed", file)
end
for label, file in pairs(ret.files) do
ret.labels[file] = label
local cancel = module._read_async(file, finished, read_error)
ret.pending[file] = cancel
end
return ret
end
local modes_start, modes_abort = {}, {}
modes_start["none"] = function() --[[nop]] end
modes_abort["none"] = function() --[[nop]] end
modes_start["files"] = function(self)
if not self._private.init then return end
local t = gen_file_transaction(self)
add_transaction(self, t)
end
modes_abort["files"] = function(self)
for _, t in ipairs(self._private.transactions) do
remove_transaction(self, t)
end
end
modes_start["command"] = function(self)
if not self._private.init then return end
local com = self._private.command
if self._private.shell then
assert(
type(com) == "string",
"When using `gears.watcher` with `shell = true`, "..
"the command must be a string"
)
com = {module._shell, '-c', com}
end
module._easy_async(com, function(lines, stderr, err, errcode)
if self.strip_newline then
lines = remove_posix_extra_newline(lines)
end
if self.filter then
self._private.value = self.filter(lines, stderr, err, errcode)
else
self._private.value = lines
end
end)
end
modes_abort["command"] = function()
--TODO
end
function module:_set_declarative_handler(parent, key, ids)
assert(type(key) == "string", "A watcher can only be attached to properties")
table.insert(self._private.targets, {
ids = ids,
parent = parent,
property = key
})
if self._private.value then
parent[key] = self._private.value
end
end
--- Abort the current transactions.
--
-- If files are currently being read or commands executed,
-- abort it. This does prevent new transaction from being
-- started. Use `:stop()` for that.
--
-- @method abort
-- @see stop
function module:abort()
modes_abort[self._private.mode](self)
end
--- Emitted when a file is read.
--
-- @signal file::acquired
-- @tparam string path The path.
-- @tparam string content The file content.
-- @see files::acquired
-- @see file::failed
--- When reading a file failed.
-- @signal file::failed
-- @tparam string path The path.
--- Emitted when all files are read.
--
-- @signal files::acquired
-- @tparam table contents Path as keys and content as values.
-- @tparam table failed The list of files which could not be read.
--- A file path.
--
-- @DOC_text_gears_watcher_simple_EXAMPLE@
--
-- @property file
-- @tparam string file
-- @propemits true false
-- @see files
--- A list or map of files.
--
-- It is often necessary to query multiple files. When reading from `proc`,
-- some data is split across multiple files, such as the battery charge.
--
-- This property accepts 2 format. One uses is a plain table of paths while
-- the other is a label->path map. Depending on what is being read, both make
-- sense.
--
-- **Simple path list:**
--
-- @DOC_text_gears_watcher_files1_EXAMPLE@
--
-- **With labels:**
--
-- @DOC_text_gears_watcher_files2_EXAMPLE@
--
-- @property files
-- @tparam table files
-- @propemits true false
-- @see labels_as_properties
function module:get_file()
return self._private.files[1]
end
function module:set_file(path)
self:set_files({path})
end
function module:get_files()
return self._private.files
end
function module:set_files(paths)
self:abort()
self._private.files = paths or {}
self._private.mode = "files"
self:emit_signal("property::files", self._private.files )
self:emit_signal("property::file" , select(2, next(self._private.files)))
-- It is possible to give names to each files. For modules like
-- battery widgets, which require reading multiple long paths from
-- `proc`, it makes the user code more readable.
self._private.file_keys = {}
for k, v in pairs(self._private.files) do
self._private.file_keys[v] = type(k) == "number" and v or k
end
modes_start["files"](self)
end
--- Add a file to the files list.
--
-- @method append_file
-- @tparam string path The path.
-- @tparam[opt] string The key.
function module:append_file(path, key)
self:abort()
if self._private.files[path] then return end
key = key or (#self._private.files + 1)
self._private.mode = "files"
self._private.files[key] = path
self._private.file_keys[path] = key
self:emit_signal("property::files", self._private.files )
self:emit_signal("property::file" , select(2, next(self._private.files)))
modes_start["files"](self)
end
--- Remove a file to the files list.
--
-- @method remove_file
-- @tparam string path The path or the key.
--- A filter to post-process the file or command.
--
-- It can be used, for example, to convert the string to a number or turn
-- the various file content into the final value. The filter argument
-- depend on various `gears.watcher` properties (as documented below).
--
-- **The callback parameters for a single file:**
-- (1) The file content (string)
--
-- **The callback parameters for a multiple files (paths):**
-- (1) Tables with the paths as keys and string content as value.
--
-- **The callback parameters for a multiple files (labels):**
-- (1) Tables with the keys as keys and string content as value.
--
-- **The callback when `labels_as_properties` is true:
-- (1) The label name
-- (2) The content
--
-- **The callback parameters for a command:**
-- (1) Stdout as first parameter
-- (2) Stderr as second parameter
-- (3) Exitreason
-- (4) Exitcode
--
-- @property filter
-- @tparam function filter
-- @propemits true false
function module:get_filter()
return self._private.filter
end
function module:set_filter(filter)
self:abort()
self._private.filter = filter
self:emit_signal("property::filter", filter)
modes_start[self._private.mode](self)
end
--- A command.
--
-- If you plan to use pipes or any shell features, do not
-- forget to also set `shell` to `true`.
--
-- @DOC_text_gears_watcher_command1_EXAMPLE@
--
-- @property command
-- @tparam table|string command
-- @propemits true false
function module:get_command()
return self._private.command
end
function module:set_command(command)
self._private.command = command
self._private.mode = "command"
self:emit_signal("property::command")
modes_start["command"](self)
end
--- Use a shell when calling the command.
--
-- This means you can use `|`, `&&`, `$?` in your command.
--
-- @DOC_text_gears_watcher_command2_EXAMPLE@
---
-- @property shell
-- @tparam[opt=false] boolean|string shell
-- @propemits true false
function module:get_shell()
return self._private.shell or false
end
function module:set_shell(shell)
self._private.shell = shell
self:emit_signal("property::shell")
end
--- In files mode, when paths have labels, apply them as properties.
--
-- @DOC_wibox_widget_declarative_watcher_EXAMPLE@
--
-- @property labels_as_properties
-- @tparam[opt=false] boolean labels_as_properties
-- @see files
--- The interval between the content refresh.
--
-- (in seconds)
--
-- @property interval
-- @tparam number interval
-- @see gears.timer.timeout
-- @propemits true false
-- There is not get_timeout/set_timeout, so we can't make aliases.
module.get_interval = gtimer.get_timeout
module.set_interval = gtimer.set_timeout
--- The current value of the watcher.
--
-- If there is no filter, this will be a string. If a filter is used,
-- then it is whatever it returns.
--
-- @property value
-- @propemits false false
function module:get_value()
return self._private.value
end
--- Strip the extra trailing newline.
--
-- All posix compliant text file and commands end with a newline.
-- Most of the time, this is inconvinient, so `gears.watcher` removes
-- them by default. Set this to `false` if this isn't the desired
-- behavior.
--
-- @property strip_newline
-- @tparam[opt=true] boolean strip_newline
-- @propemits true false
function module:get_strip_newline()
return self._private.newline
end
function module:set_strip_newline(value)
self._private.newline = value
self:emit_signal("property::strip_newline", value)
end
--- Start the timer.
-- @method start
-- @emits start
-- @baseclass gears.timer
-- @see stop
--- Stop the timer.
-- @method stop
-- @emits stop
-- @baseclass gears.timer
-- @see abort
-- @see start
--- The timer is started.
-- @property started
-- @tparam boolean started
-- @propemits false false
-- @baseclass gears.timer
--- Restart the timer.
-- This is equivalent to stopping the timer if it is running and then starting
-- it.
-- @method again
-- @baseclass gears.timer
-- @emits start
-- @emits stop
--- Create a new `gears.watcher` object.
--
-- @constructorfct gears.watcher
-- @tparam table args
-- @tparam string args.file A file path.
-- @tparam table args.files A list or map of files.
-- @tparam function args.filter A filter to post-process the file or command.
-- @tparam table|string args.command A command (without a shell).
-- @tparam[opt=false] boolean args.shell Use a shell when calling the command.
-- @param args.initial_value The value to use before the first "real" value is acquired.
-- @tparam number args.interval The interval between the content refresh.
-- @tparam boolean args.labels_as_properties Set the file labels as properties.
-- @tparam boolean args.started The timer is started.
local function new(_, args)
local ret = gtimer()
ret.timeout = 5000
local newargs = gtable.clone(args or {}, false)
ret._private.mode = "none"
ret._private.transactions = {}
ret._private.targets = {}
ret._private.files = {}
if newargs.autostart == nil then
newargs.autostart = true
end
if newargs.strip_newline == nil then
newargs.strip_newline = true
end
gtable.crush(ret, module , true )
gtable.crush(ret, newargs, false)
ret.shell = args.shell
-- ret:set_shell(args.shell)
local function value_callback()
for _, target in ipairs(ret._private.targets) do
target.parent[target.property] = ret.value
end
end
local function update_callback()
modes_start[ret._private.mode](ret)
end
ret:connect_signal("property::value", value_callback)
ret:connect_signal("timeout", update_callback)
if args.initial_value then
ret._private.value = args.initial_value
end
ret._private.init = true
if newargs.autostart then
ret:start()
modes_start[ret._private.mode](ret)
end
return ret
end
--@DOC_object_COMMON@
return setmetatable(module, {__call = new})

View File

@ -70,12 +70,15 @@ end
-- @beautiful beautiful.menubar_bg_focus
-- @param color
--- Menubar font.
-- @beautiful beautiful.menubar_font
-- @param[opt=beautiful.font] font
-- menubar
local menubar = { menu_entries = {} }
menubar.menu_gen = require("menubar.menu_gen")
menubar.utils = require("menubar.utils")
local compute_text_width = menubar.utils.compute_text_width
-- Options section
@ -119,10 +122,9 @@ menubar.right_label = "▶▶"
-- @tfield[opt="◀◀"] string left_label
menubar.left_label = "◀◀"
-- awful.widget.common.list_update adds three times a margin of dpi(4)
-- for each item:
-- @tfield number list_interspace
local list_interspace = theme.xresources.apply_dpi(4) * 3
-- awful.widget.common.list_update adds spacing of dpi(4) between items.
-- @tfield number list_spacing
local list_spacing = theme.xresources.apply_dpi(4)
--- Allows user to specify custom parameters for prompt.run function
-- (like colors). This will merge with the default parameters, overriding affected values.
@ -149,7 +151,7 @@ end
--- Get how the menu item should be displayed.
-- @param o The menu item.
-- @return item name, item background color, background image, item icon.
-- @return item name, item background color, background image, item icon, item args.
local function label(o)
local fg_color = theme.menubar_fg_normal or theme.menu_fg_normal or theme.fg_normal
local bg_color = theme.menubar_bg_normal or theme.menu_bg_normal or theme.bg_normal
@ -160,7 +162,8 @@ local function label(o)
return colortext(gstring.xml_escape(o.name), fg_color),
bg_color,
nil,
o.icon
o.icon,
o.icon and {icon_size=instance.geometry.height}
end
local function load_count_table()
@ -225,6 +228,11 @@ end
-- @tparam number|screen scr Screen
-- @return table List of items for current page.
local function get_current_page(all_items, query, scr)
local compute_text_width = function(text, s)
return wibox.widget.textbox.get_markup_geometry(text, s, instance.font)['width']
end
scr = get_screen(scr)
if not instance.prompt.width then
instance.prompt.width = compute_text_width(instance.prompt.prompt, scr)
@ -235,16 +243,19 @@ local function get_current_page(all_items, query, scr)
if not menubar.right_label_width then
menubar.right_label_width = compute_text_width(menubar.right_label, scr)
end
local border_width = theme.menubar_border_width or theme.menu_border_width or 0
local available_space = instance.geometry.width - menubar.right_margin -
menubar.right_label_width - menubar.left_label_width -
compute_text_width(query, scr) - instance.prompt.width
compute_text_width(query..' ', scr) - instance.prompt.width - border_width * 2
-- space character is added as input cursor placeholder
local width_sum = 0
local current_page = {}
for i, item in ipairs(all_items) do
item.width = item.width or
compute_text_width(item.name, scr) +
(item.icon and instance.geometry.height or 0) + list_interspace
item.width = item.width or (
compute_text_width(label(item), scr) +
(item.icon and (instance.geometry.height + list_spacing) or 0) + list_spacing * 2
)
if width_sum + item.width > available_space then
if current_item < i then
table.insert(current_page, { name = menubar.right_label, icon = nil })
@ -449,6 +460,7 @@ function menubar.show(scr)
local bg_color = theme.menubar_bg_normal or theme.menu_bg_normal or theme.bg_normal
local border_width = theme.menubar_border_width or theme.menu_border_width or 0
local border_color = theme.menubar_border_color or theme.menu_border_color
local font = theme.menubar_font or theme.font or "Monospace 10"
if not instance then
-- Add to each category the name of its key in all_categories
@ -467,11 +479,13 @@ function menubar.show(scr)
fg = fg_color,
border_width = border_width,
border_color = border_color,
font = font,
},
widget = common_args.w,
prompt = awful.widget.prompt(),
query = nil,
count_table = nil,
font = font,
}
local layout = wibox.layout.fixed.horizontal()
layout:add(instance.prompt)
@ -490,7 +504,7 @@ function menubar.show(scr)
local geometry = menubar.geometry
instance.geometry = {x = geometry.x or scrgeom.x,
y = geometry.y or scrgeom.y,
height = geometry.height or gmath.round(theme.get_font_height() * 1.5),
height = geometry.height or gmath.round(theme.get_font_height(font) * 1.5),
width = (geometry.width or scrgeom.width) - border_width * 2}
instance.wibox:geometry(instance.geometry)

View File

@ -19,7 +19,6 @@ local glib = lgi.GLib
local w_textbox = require("wibox.widget.textbox")
local gdebug = require("gears.debug")
local protected_call = require("gears.protected_call")
local gstring = require("gears.string")
local unpack = unpack or table.unpack -- luacheck: globals unpack (compatibility with Lua 5.1)
local utils = {}
@ -410,6 +409,8 @@ function utils.parse_dir(dir_path, callback)
end)
end
-- luacov: disable
function utils.compute_textbox_width(textbox, s)
gdebug.deprecate("Use 'width, _ = textbox:get_preferred_size(s)' directly.", {deprecated_in=4})
s = screen[s or mouse.screen]
@ -417,16 +418,13 @@ function utils.compute_textbox_width(textbox, s)
return w
end
--- Compute text width.
-- @tparam str text Text.
-- @tparam number|screen s Screen
-- @treturn int Text width.
-- @staticfct menubar.utils.compute_text_width
function utils.compute_text_width(text, s)
local w, _ = w_textbox(gstring.xml_escape(text)):get_preferred_size(s)
return w
function utils.compute_text_width(text, s, font)
gdebug.deprecate("Use 'width = textbox.get_markup_geometry(text, s, font)['width']'.", {deprecated_in=4})
return w_textbox.get_markup_geometry(text, s, font)['width']
end
-- luacov: enable
return utils
-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80

View File

@ -57,7 +57,13 @@ local action = {}
-- @propemits true false
--- When a notification is invoked.
--
-- Note that it is possible to call `:invoke()` without a notification object.
-- It is possible the `notification` parameter will be nil.
--
-- @signal invoked
-- @tparam naughty.action action The action.
-- @tparam naughty.notification|nil notification The notification, if known.
function action:get_selected()
return self._private.selected

View File

@ -191,7 +191,13 @@ naughty.notifications = { suspended = { }, _expired = {{}} }
naughty._active = {}
screen.connect_for_each_screen(function(s)
local function get_screen(s)
return s and capi.screen[s]
end
local function init_screen(s)
if naughty.notifications[s] then return end
naughty.notifications[s] = {
top_left = {},
top_middle = {},
@ -201,7 +207,9 @@ screen.connect_for_each_screen(function(s)
bottom_right = {},
middle = {},
}
end)
end
screen.connect_for_each_screen(init_screen)
capi.screen.connect_signal("removed", function(scr)
-- Allow the notifications to be moved to another screen.
@ -214,15 +222,18 @@ capi.screen.connect_signal("removed", function(scr)
naughty.emit_signal("request::screen", n, "removed", {})
end
end
for _, n in ipairs(naughty._active) do
if n._private.args and get_screen(n._private.args.screen) == scr then
n._private.args.screen = nil
end
end
-- Destroy all notifications on this screen
naughty.destroy_all_notifications({scr})
naughty.notifications[scr] = nil
end)
local function get_screen(s)
return s and capi.screen[s]
end
local function remove_from_index(n)
for _, positions in pairs(naughty.notifications) do
for _, ns in pairs(positions) do
@ -646,6 +657,10 @@ local function register(notification, args)
assert(s)
if not naughty.notifications[s] then
init_screen(get_screen(s))
end
-- insert the notification to the table
table.insert(naughty._active, notification)
table.insert(naughty.notifications[s][notification.position], notification)

View File

@ -8,6 +8,7 @@
-- @author Emmanuel Lepage Vallee &lt;elv1313@gmail.com&gt;
-- @copyright 2017 Emmanuel Lepage Vallee
-- @popupmod naughty.layout.box
-- @supermodule awful.popup
----------------------------------------------------------------------------
local capi = {screen=screen}

View File

@ -62,6 +62,12 @@ screen.connect_for_each_screen(function(s)
}
end)
capi.screen.connect_signal("removed", function(s)
timer.delayed_call(function()
current_notifications[s] = nil
end)
end)
--- Sum heights of notifications at position
--
-- @param s Screen to use
@ -128,7 +134,7 @@ local function get_offset(s, position, idx, width, height)
local find_old_to_replace = function()
for i = 1, idx-1 do
local n = current_notifications[s][position][i]
if n.timeout > 0 then
if n and n.timeout > 0 then
return n
end
end

View File

@ -337,7 +337,10 @@ local function new(_, args)
update_style(wdg)
wdg._private.default_buttons = gtable.join(
abutton({ }, 1, function(a) a:invoke(args.notification) end)
abutton({ }, 1, function(a)
local notif = wdg._private.notification or args.notification
a:invoke(notif)
end)
)
return wdg

View File

@ -570,6 +570,27 @@ function notification:set_timeout(timeout)
self:emit_signal("property::timeout", timeout)
end
function notification:get_message()
-- This property was called "text" in older versions.
-- Some modules like `lain` abused of the presets (they
-- had little choice at the time) to set the message on
-- an existing popup.
local p = rawget(self, "preset") or {}
local message = self._private.message or p.message or ""
if message == "" and p.text and p.text ~= "" then
gdebug.deprecate(
"Using the preset configuration to set the notification "..
"message is not supported. Please use `n.message = 'text'`.",
{deprecated_in=5}
)
return p.text
end
return message
end
function notification:set_text(txt)
gdebug.deprecate(
"The `text` attribute is deprecated, use `message`",
@ -861,7 +882,15 @@ local function select_legacy_preset(n, args)
))
for k, v in pairs(n.preset) do
n._private[k] = v
-- Don't keep a strong reference to the screen, Lua 5.1 GC wont be
-- smart enough to unwind the mess of circular weak references.
if k ~= "screen" then
n._private[k] = v
end
end
if n.preset.screen then
n._private.weak_screen[1] = capi.screen[n.preset.screen]
end
end

View File

@ -160,6 +160,15 @@ end
-- @staticfct ruled.notification.apply
function module.apply(n)
local callbacks, props = {}, {}
if n.preset then
for k, v in pairs(n.preset) do
if not n._private[v] then
props[k] = v
end
end
end
for _, v in ipairs(nrules._matching_source) do
v.callback(nrules, n, props, callbacks)
end

View File

@ -8,6 +8,7 @@
-- @author Emmanuel Lepage Vallee &lt;elv1313@gmail.com&gt;
-- @copyright 2013 Emmanuel Lepage Vallee
-- @containermod wibox.container.arcchart
-- @supermodule wibox.widget.base
---------------------------------------------------------------------------
local setmetatable = setmetatable
@ -357,10 +358,6 @@ function arcchart.mt:__call(...)
return new(...)
end
--@DOC_widget_COMMON@
--@DOC_object_COMMON@
return setmetatable(arcchart, arcchart.mt)
-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80

View File

@ -6,6 +6,7 @@
-- @author Uli Schlachter
-- @copyright 2010 Uli Schlachter
-- @containermod wibox.container.background
-- @supermodule wibox.widget.base
---------------------------------------------------------------------------
local base = require("wibox.widget.base")
@ -484,10 +485,6 @@ function background.mt:__call(...)
return new(...)
end
--@DOC_widget_COMMON@
--@DOC_object_COMMON@
return setmetatable(background, background.mt)
-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80

View File

@ -5,6 +5,7 @@
-- @author Lukáš Hrázký
-- @copyright 2012 Lukáš Hrázký
-- @containermod wibox.container.constraint
-- @supermodule wibox.widget.base
---------------------------------------------------------------------------
local setmetatable = setmetatable
@ -176,10 +177,6 @@ function constraint.mt:__call(...)
return new(...)
end
--@DOC_widget_COMMON@
--@DOC_object_COMMON@
return setmetatable(constraint, constraint.mt)
-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80

View File

@ -17,6 +17,7 @@ return setmetatable({
radialprogressbar = require("wibox.container.radialprogressbar");
arcchart = require("wibox.container.arcchart");
place = require("wibox.container.place");
tile = require("wibox.container.tile");
}, {__call = function(_, args) return base.make_widget_declarative(args) end})
-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80

View File

@ -5,6 +5,7 @@
-- @author Uli Schlachter
-- @copyright 2010 Uli Schlachter
-- @containermod wibox.container.margin
-- @supermodule wibox.widget.base
---------------------------------------------------------------------------
local pairs = pairs
@ -238,10 +239,6 @@ function margin.mt:__call(...)
return new(...)
end
--@DOC_widget_COMMON@
--@DOC_object_COMMON@
return setmetatable(margin, margin.mt)
-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80

View File

@ -5,6 +5,7 @@
-- @author dodo
-- @copyright 2012 dodo
-- @containermod wibox.container.mirror
-- @supermodule wibox.widget.base
---------------------------------------------------------------------------
local type = type
@ -129,10 +130,6 @@ function mirror.mt:__call(...)
return new(...)
end
--@DOC_widget_COMMON@
--@DOC_object_COMMON@
return setmetatable(mirror, mirror.mt)
-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80

View File

@ -5,6 +5,7 @@
-- @author Emmanuel Lepage Vallee &lt;elv1313@gmail.com&gt;
-- @copyright 2016 Emmanuel Lepage Vallee
-- @containermod wibox.container.place
-- @supermodule wibox.widget.base
---------------------------------------------------------------------------
local setmetatable = setmetatable
@ -22,13 +23,8 @@ local align_fct = {
}
align_fct.top, align_fct.bottom = align_fct.left, align_fct.right
-- Layout this layout
function place:layout(context, width, height)
if not self._private.widget then
return
end
-- Shared with some subclasses like the `tiled` and `scaled` modules.
function place:_layout(context, width, height)
local w, h = base.fit_widget(self, context, self._private.widget, width, height)
if self._private.content_fill_horizontal then
@ -44,6 +40,21 @@ function place:layout(context, width, height)
local x, y = align_fct[halign](w, width), align_fct[valign](h, height)
-- Sub pixels makes everything blurry. This is now what people expect.
x, y = math.floor(x), math.floor(y)
return x, y, w, h
end
-- Layout this layout
function place:layout(context, width, height)
if not self._private.widget then
return
end
local x, y, w, h = self:_layout(context, width, height)
return { base.place_widget_at(self._private.widget, x, y, w, h) }
end
@ -96,6 +107,8 @@ end
-- * *center* (default)
-- * *bottom*
--
--@DOC_wibox_container_place_valign_EXAMPLE@
--
-- @property valign
-- @tparam[opt="center"] string valign
-- @propemits true false
@ -108,6 +121,8 @@ end
-- * *center* (default)
-- * *right*
--
--@DOC_wibox_container_place_halign_EXAMPLE@
--
-- @property halign
-- @tparam[opt="center"] string halign
-- @propemits true false
@ -158,6 +173,8 @@ end
--- Stretch the contained widget so it takes all the vertical space.
--
--@DOC_wibox_container_place_content_fill_vertical_EXAMPLE@
--
-- @property content_fill_vertical
-- @tparam[opt=false] boolean content_fill_vertical
-- @propemits true false
@ -170,6 +187,8 @@ end
--- Stretch the contained widget so it takes all the horizontal space.
--
--@DOC_wibox_container_place_content_fill_horizontal_EXAMPLE@
--
-- @property content_fill_horizontal
-- @tparam[opt=false] boolean content_fill_horizontal
-- @propemits true false
@ -203,10 +222,6 @@ function place.mt:__call(...)
return new(...)
end
--@DOC_widget_COMMON@
--@DOC_object_COMMON@
return setmetatable(place, place.mt)
-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80

View File

@ -8,6 +8,7 @@
-- @author Emmanuel Lepage Vallee &lt;elv1313@gmail.com&gt;
-- @copyright 2013 Emmanuel Lepage Vallee
-- @containermod wibox.container.radialprogressbar
-- @supermodule wibox.widget.base
---------------------------------------------------------------------------
local setmetatable = setmetatable
@ -281,10 +282,6 @@ function radialprogressbar.mt:__call(...)
return new(...)
end
--@DOC_widget_COMMON@
--@DOC_object_COMMON@
return setmetatable(radialprogressbar, radialprogressbar.mt)
-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80

View File

@ -5,6 +5,7 @@
-- @author Uli Schlachter
-- @copyright 2010 Uli Schlachter
-- @containermod wibox.container.rotate
-- @supermodule wibox.widget.base
---------------------------------------------------------------------------
local error = error
@ -149,10 +150,6 @@ function rotate.mt:__call(...)
return new(...)
end
--@DOC_widget_COMMON@
--@DOC_object_COMMON@
return setmetatable(rotate, rotate.mt)
-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80

View File

@ -21,6 +21,7 @@
-- @author Uli Schlachter (based on ideas from Saleur Geoffrey)
-- @copyright 2015 Uli Schlachter
-- @containermod wibox.container.scroll
-- @supermodule wibox.widget.base
---------------------------------------------------------------------------
local cache = require("gears.cache")
@ -549,10 +550,6 @@ function scroll.step_functions.waiting_nonlinear_back_and_forth(elapsed, size, v
return (size - visible_size) * state
end
--@DOC_widget_COMMON@
--@DOC_object_COMMON@
return scroll
-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80

View File

@ -0,0 +1,212 @@
---------------------------------------------------------------------------
-- Replicate the content of the widget over and over.
--
-- This contained is intended to be used for wallpapers. It currently doesn't
-- support mouse input in the replicated tiles.
--
--@DOC_wibox_container_defaults_tile_EXAMPLE@
-- @author Emmanuel Lepage-Vallee
-- @copyright 2021 Emmanuel Lepage-Vallee
-- @containermod wibox.container.tile
-- @supermodule wibox.container.place
local place = require("wibox.container.place")
local cairo = require("lgi").cairo
local widget = require("wibox.widget")
local gtable = require("gears.table")
local module = {mt = {}}
function module:draw(context, cr, width, height)
if not self._private.tiled then return end
if not self._private.widget then return end
local x, y, w, h = self:_layout(context, width, height)
local vspace, hspace = self.vertical_spacing, self.horizontal_spacing
local vcrop, hcrop = self.vertical_crop, self.horizontal_crop
-- In theory we could avoid a few repaints by tracking the child widget
-- redraw independently from the container redraw. However it is nearly a
-- 1:1 march, so there's little reasons to do it.
if not self._private.surface then
self._private.surface = cairo.ImageSurface(cairo.Format.ARGB32, w+hspace, h+vspace)
self._private.cr = cairo.Context(self._private.surface)
self._private.cr:set_source(cr:get_source())
self._private.pattern = cairo.Pattern.create_for_surface(self._private.surface)
self._private.pattern.extend = cairo.Extend.REPEAT
self._private.cr:translate(math.ceil(hspace), math.ceil(vspace))
else
self._private.cr:set_operator(cairo.Operator.CLEAR)
self._private.cr:set_source_rgba(0,0,0,1)
self._private.cr:paint()
self._private.cr:set_operator(cairo.Operator.SOURCE)
end
widget.draw_to_cairo_context(self._private.widget, self._private.cr, w, h, context)
cr:save()
-- We do our own clip.
cr:reset_clip()
local x0, y0 = 0, 0
-- Avoid painting incomplete tiles
if hcrop and x ~= 0 then
x0 = x - math.floor(x/(w+hspace))*(w+hspace)
end
if hcrop then
width = x + w + hspace + math.floor((width - (x + w + hspace))/(w+hspace))*(w+hspace)
end
if vcrop and y ~= 0 then
y0 = y - math.floor(y/(h+vspace))*(h+vspace)
end
if vcrop then
height = (y+h+vspace) + math.floor((height - (y+h+vspace))/(h+vspace))*(h+vspace)
end
-- Create a clip around the "real" widget in case there is some transparency.
cr:rectangle(x0, y0, width-x0, y-y0)
cr:rectangle(x0, y0, x-hspace-x0, height-y0)
cr:rectangle(x+hspace+w, y0, width - (x+w+hspace), height-y0)
cr:rectangle(x, y+vspace+h, w+hspace, height - (y+h+vspace))
cr:clip()
-- Make sure the tiles are aligned with the child widget.
cr:translate(x - hspace, y - vspace)
-- Use OVER rather than SOURCE to preserve the alpha.
cr.operator = cairo.Operator.OVER
cr.source = self._private.pattern
cr:paint()
cr:restore()
end
--- The horizontal spacing between the tiled.
--
--@DOC_wibox_container_tile_horizontal_spacing_EXAMPLE@
--
-- @property horizontal_spacing
-- @tparam number horizontal_spacing
-- @propemits true false
-- @see vertical_spacing
--- The vertical spacing between the tiled.
--
--@DOC_wibox_container_tile_vertical_spacing_EXAMPLE@
--
-- @property vertical_spacing
-- @tparam number vertical_spacing
-- @propemits true false
-- @see horizontal_spacing
--- Avoid painting incomplete horizontal tiles.
--
--@DOC_wibox_container_tile_horizontal_crop_EXAMPLE@
--
-- @property horizontal_crop
-- @tparam[opt=false] boolean tiled
-- @see vertical_crop
--- Avoid painting incomplete vertical tiles.
--
--@DOC_wibox_container_tile_vertical_crop_EXAMPLE@
--
-- @property vertical_crop
-- @tparam[opt=false] boolean tiled
-- @see horizontal_crop
--- Enable or disable the tiling.
--
-- When set to `false`, this container behaves exactly like
-- `wibox.container.place`.
--
--@DOC_wibox_container_tile_tiled_EXAMPLE@
--
-- @property tiled
-- @tparam[opt=true] boolean tiled
local defaults = {
horizontal_spacing = 0,
vertical_spacing = 0,
tiled = true,
horizontal_crop = false,
vertical_crop = false,
}
for prop in pairs(defaults) do
module["set_"..prop] = function(self, value)
self._private[prop] = value
self:emit_signal("widget::redraw_needed", value)
end
module["get_"..prop] = function(self)
if self._private[prop] == nil then
return defaults[prop]
end
return self._private[prop]
end
end
local function new(_, args)
args = args or {}
local ret = place(args.widget, args.halign, args.valign)
gtable.crush(ret, module, true)
ret._private.tiled = true
local function redraw()
ret:emit_signal("widget::redraw_needed")
end
-- Resize the pattern as needed.
local function reset()
if ret._private.surface then
ret._private.surface:finish()
end
ret._private.cr = nil
ret._private.surface = nil
ret._private.pattern = nil
end
local w = nil
ret:connect_signal("property::widget", function()
reset()
if w then
w:disconnect_signal("widget::redraw_needed", redraw)
w:disconnect_signal("widget::layout_changed", reset)
end
w = ret._private.widget
if w then
w:connect_signal("widget::redraw_needed", redraw)
w:connect_signal("widget::layout_changed", reset)
end
end)
return ret
end
--- Create a new tile container.
-- @tparam table args
-- @tparam wibox.widget widget args.widget The widget to tile.
-- @tparam string args.halign Either `left`, `right` or `center`.
-- @tparam string args.valign Either `top`, `bottom` or `center`.
-- @constructorfct wibox.container.tile
function module.mt:__call(...)
return new(...)
end
return setmetatable(module, module.mt)
-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80

View File

@ -1,9 +1,29 @@
---------------------------------------------------------------------------
-- The `align` layout has three slots for child widgets. On its main axis, it
-- will use as much space as is available to it and distribute that to its child
-- widgets by stretching or shrinking them based on the chosen @{expand}
-- strategy.
-- On its secondary axis, the biggest child widget determines the size of the
-- layout, but smaller widgets will not be stretched to match it.
--
-- In its default configuration, the layout will give the first and third
-- widgets only the minimum space they ask for and it aligns them to the outer
-- edges. The remaining space between them is made available to the widget in
-- slot two.
--
-- This layout is most commonly used to split content into left/top, center and
-- right/bottom sections. As such, it is usually seen as the root layout in
-- @{awful.wibar}.
--
-- You may also fill just one or two of the widget slots, the @{expand} algorithm
-- will adjust accordingly.
--
--@DOC_wibox_layout_defaults_align_EXAMPLE@
--
-- @author Uli Schlachter
-- @copyright 2010 Uli Schlachter
-- @layoutmod wibox.layout.align
-- @supermodule wibox.widget.base
---------------------------------------------------------------------------
local table = table
@ -139,8 +159,10 @@ function align:layout(context, width, height)
return result
end
--- Set the layout's first widget.
-- This is the widget that is at the left/top
--- The widget in slot one.
--
-- This is the widget that is at the left/top.
--
-- @property first
-- @tparam widget first
-- @propemits true false
@ -154,7 +176,10 @@ function align:set_first(widget)
self:emit_signal("property::first", widget)
end
--- Set the layout's second widget. This is the centered one.
--- The widget in slot two.
--
-- This is the centered one.
--
-- @property second
-- @tparam widget second
-- @propemits true false
@ -168,8 +193,10 @@ function align:set_second(widget)
self:emit_signal("property::second", widget)
end
--- Set the layout's third widget.
-- This is the widget that is at the right/bottom
--- The widget in slot three.
--
-- This is the widget that is at the right/bottom.
--
-- @property third
-- @tparam widget third
-- @propemits true false
@ -226,23 +253,27 @@ function align:fit(context, orig_width, orig_height)
return used_in_dir, used_in_other
end
--- Set the expand mode which determines how sub widgets expand to take up
--- Set the expand mode, which determines how child widgets expand to take up
-- unused space.
--
-- The following values are valid:
--
-- * "inside" - Default option. Size of outside widgets is determined using
-- their fit function. Second, middle, or center widget expands to fill
-- remaining space.
-- * "outside" - Center widget is sized using its fit function and placed in
-- the center of the allowed space. Outside widgets expand (or contract) to
-- fill remaining space on their side.
-- * "none" - All widgets are sized using their fit function, drawn to only the
-- returned space, or remaining space, whichever is smaller. Center widget
-- gets priority.
-- * `"inside"`: The widgets in slot one and three are set to their minimal
-- required size. The widget in slot two is then given the remaining space.
-- This is the default behaviour.
-- * `"outside"`: The widget in slot two is set to its minimal required size and
-- placed in the center of the space available to the layout. The other
-- widgets are then given the remaining space on either side.
-- If the center widget requires all available space, the outer widgets are
-- not drawn at all.
-- * `"none"`: All widgets are given their minimal required size or the
-- remaining space, whichever is smaller. The center widget gets priority.
--
-- Attempting to set any other value than one of those three will fall back to
-- `"inside"`.
--
-- @property expand
-- @tparam[opt=inside] string mode How to use unused space.
-- @tparam[opt="inside"] string mode How to use unused space.
function align:set_expand(mode)
if mode == "none" or mode == "outside" then
@ -282,14 +313,16 @@ local function get_layout(dir, first, second, third)
return ret
end
--- Returns a new horizontal align layout. An align layout can display up to
-- three widgets. The widget set via :set_left() is left-aligned. :set_right()
-- sets a widget which will be right-aligned. The remaining space between those
-- two will be given to the widget set via :set_middle().
--- Returns a new horizontal align layout.
--
-- The three widget slots are aligned left, center and right.
--
-- Additionally, this creates the aliases `set_left`, `set_middle` and
-- `set_right` to assign @{first}, @{second} and @{third} respectively.
-- @constructorfct wibox.layout.align.horizontal
-- @tparam[opt] widget left Widget to be put to the left.
-- @tparam[opt] widget middle Widget to be put to the middle.
-- @tparam[opt] widget right Widget to be put to the right.
-- @tparam[opt] widget left Widget to be put in slot one.
-- @tparam[opt] widget middle Widget to be put in slot two.
-- @tparam[opt] widget right Widget to be put in slot three.
function align.horizontal(left, middle, right)
local ret = get_layout("x", left, middle, right)
@ -300,14 +333,16 @@ function align.horizontal(left, middle, right)
return ret
end
--- Returns a new vertical align layout. An align layout can display up to
-- three widgets. The widget set via :set_top() is top-aligned. :set_bottom()
-- sets a widget which will be bottom-aligned. The remaining space between those
-- two will be given to the widget set via :set_middle().
--- Returns a new vertical align layout.
--
-- The three widget slots are aligned top, center and bottom.
--
-- Additionally, this creates the aliases `set_top`, `set_middle` and
-- `set_bottom` to assign @{first}, @{second} and @{third} respectively.
-- @constructorfct wibox.layout.align.vertical
-- @tparam[opt] widget top Widget to be put to the top.
-- @tparam[opt] widget middle Widget to be put to the middle.
-- @tparam[opt] widget bottom Widget to be put to the right.
-- @tparam[opt] widget top Widget to be put in slot one.
-- @tparam[opt] widget middle Widget to be put in slot two.
-- @tparam[opt] widget bottom Widget to be put in slot three.
function align.vertical(top, middle, bottom)
local ret = get_layout("y", top, middle, bottom)
@ -320,10 +355,6 @@ end
--@DOC_fixed_COMMON@
--@DOC_widget_COMMON@
--@DOC_object_COMMON@
return align
-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80

View File

@ -1,9 +1,26 @@
---------------------------------------------------------------------------
-- A `fixed` layout may be initialized with any number of child widgets, and
-- during runtime widgets may be added and removed dynamically.
--
-- On the main axis, child widgets are given a fixed size of exactly as much
-- space as they ask for. The layout will then resize according to the sum of
-- all child widgets. If the space available to the layout is not enough to
-- include all child widgets, the excessive ones are not drawn at all.
--
-- Additionally, the layout allows adding empty spacing or even placing a custom
-- spacing widget between the child widget.
--
-- On its secondary axis, the layout's size is determined by the largest child
-- widget. Smaller child widgets are then placed with the same size.
-- Therefore, child widgets may ignore their `forced_width` or `forced_height`
-- properties for vertical and horizontal layouts respectively.
--
--@DOC_wibox_layout_defaults_fixed_EXAMPLE@
--
-- @author Uli Schlachter
-- @copyright 2010 Uli Schlachter
-- @layoutmod wibox.layout.fixed
-- @supermodule wibox.widget.base
---------------------------------------------------------------------------
local unpack = unpack or table.unpack -- luacheck: globals unpack (compatibility with Lua 5.1)
@ -20,45 +37,96 @@ local fixed = {}
-- @param height The available height.
function fixed:layout(context, width, height)
local result = {}
local pos,spacing = 0, self._private.spacing
local spacing_widget = self._private.spacing_widget
local spacing = self._private.spacing or 0
local is_y = self._private.dir == "y"
local is_x = not is_y
local abspace = math.abs(spacing)
local spoffset = spacing < 0 and 0 or spacing
local widgets_nr = #self._private.widgets
local spacing_widget
local x, y = 0, 0
spacing_widget = spacing ~= 0 and self._private.spacing_widget or nil
for index, widget in pairs(self._private.widgets) do
local w, h, local_spacing = width - x, height - y, spacing
-- Some widget might be zero sized either because this is their
-- minimum space or just because they are really empty. In this case,
-- they must still be added to the layout. Otherwise, if their size
-- change and this layout is resizable, they are lost "forever" until
-- a full relayout is called on this fixed layout object.
local zero = false
for k, v in pairs(self._private.widgets) do
local x, y, w, h, _
if is_y then
x, y = 0, pos
w, h = width, height - pos
if k ~= #self._private.widgets or not self._private.fill_space then
_, h = base.fit_widget(self, context, v, w, h);
if index ~= widgets_nr or not self._private.fill_space then
h = select(2, base.fit_widget(self, context, widget, w, h))
zero = h == 0
end
if y - spacing >= height then
-- pop the spacing widget added in previous iteration if used
if spacing_widget then
table.remove(result)
-- Avoid adding zero-sized widgets at an out-of-bound
-- position.
y = y - spacing
end
-- Never display "random" widgets as soon as a non-zero sized
-- one doesn't fit.
if not zero then
break
end
end
pos = pos + h + spacing
else
x, y = pos, 0
w, h = width - pos, height
if k ~= #self._private.widgets or not self._private.fill_space then
w, _ = base.fit_widget(self, context, v, w, h);
if index ~= widgets_nr or not self._private.fill_space then
w = select(1, base.fit_widget(self, context, widget, w, h))
zero = w == 0
end
if x - spacing >= width then
-- pop the spacing widget added in previous iteration if used
if spacing_widget then
table.remove(result)
-- Avoid adding zero-sized widgets at an out-of-bound
-- position.
x = x - spacing
end
-- Never display "random" widgets as soon as a non-zero sized
-- one doesn't fit.
if not zero then
break
end
end
pos = pos + w + spacing
end
if (is_y and pos-spacing > height) or
(is_x and pos-spacing > width) then
break
if zero then
local_spacing = 0
end
-- Add the spacing widget
if k > 1 and abspace > 0 and spacing_widget then
-- Place widget, even if it has zero width/height. Otherwise
-- any layout change for zero-sized widget would become invisible.
table.insert(result, base.place_widget_at(widget, x, y, w, h))
x = is_x and x + w + local_spacing or x
y = is_y and y + h + local_spacing or y
-- Add the spacing widget (if needed)
if index < widgets_nr and spacing_widget then
table.insert(result, base.place_widget_at(
spacing_widget, is_x and (x - spoffset) or x, is_y and (y - spoffset) or y,
is_x and abspace or w, is_y and abspace or h
spacing_widget,
is_x and (x - spoffset) or x,
is_y and (y - spoffset) or y,
is_x and abspace or w,
is_y and abspace or h
))
end
table.insert(result, base.place_widget_at(v, x, y, w, h))
end
return result
end
@ -217,9 +285,13 @@ function fixed:set(index, widget2)
return true
end
--- The widget used to fill the spacing between the layout elements.
--- A widget to insert as a separator between child widgets.
--
-- By default, no widget is used.
-- If this property is a valid widget and `spacing` is greater than `0`, a
-- copy of this widget is inserted between each child widget, with its size in
-- the layout's main direction determined by `spacing`.
--
-- By default no widget is used and any `spacing` is applied as an empty offset.
--
--@DOC_wibox_layout_fixed_spacing_widget_EXAMPLE@
--
@ -261,40 +333,61 @@ end
-- @param orig_width The available width.
-- @param orig_height The available height.
function fixed:fit(context, orig_width, orig_height)
local width, height = orig_width, orig_height
local used_in_dir, used_max = 0, 0
local width_left, height_left = orig_width, orig_height
local spacing = self._private.spacing or 0
local widgets_nr = #self._private.widgets
local is_y = self._private.dir == "y"
local used_max = 0
for _, v in pairs(self._private.widgets) do
local w, h = base.fit_widget(self, context, v, width, height)
local in_dir, max
if self._private.dir == "y" then
max, in_dir = w, h
height = height - in_dir
-- when no widgets exist the function can be called with orig_width or
-- orig_height equal to nil. Exit early in this case.
if widgets_nr == 0 then
return 0, 0
end
for k, v in pairs(self._private.widgets) do
local w, h = base.fit_widget(self, context, v, width_left, height_left)
local max
if is_y then
max = w
height_left = height_left - h
else
in_dir, max = w, h
width = width - in_dir
max = h
width_left = width_left - w
end
if max > used_max then
used_max = max
end
used_in_dir = used_in_dir + in_dir
if width <= 0 or height <= 0 then
if self._private.dir == "y" then
used_in_dir = orig_height
if k < widgets_nr then
if is_y then
height_left = height_left - spacing
else
used_in_dir = orig_width
width_left = width_left - spacing
end
end
if width_left <= 0 or height_left <= 0 then
-- this complicated two lines determine whether we're out-of-space
-- because of spacing, or if the last widget doesn't fit in
if is_y then
height_left = k < widgets_nr and height_left + spacing or height_left
height_left = height_left < 0 and 0 or height_left
else
width_left = k < widgets_nr and width_left + spacing or width_left
width_left = width_left < 0 and 0 or width_left
end
break
end
end
local spacing = self._private.spacing * (#self._private.widgets-1)
if self._private.dir == "y" then
return used_max, used_in_dir + spacing
if is_y then
return used_max, orig_height - height_left
end
return used_in_dir + spacing, used_max
return orig_width - width_left, used_max
end
function fixed:reset()
@ -336,29 +429,25 @@ local function get_layout(dir, widget1, ...)
return ret
end
--- Returns a new horizontal fixed layout. Each widget will get as much space as it
-- asks for and each widget will be drawn next to its neighboring widget.
-- Widgets can be added via :add() or as arguments to this function.
-- Note that widgets ignore `forced_height`. They will use the preferred/minimum width
-- on the horizontal axis, and a stretched height on the vertical axis.
--- Creates and returns a new horizontal fixed layout.
--
-- @tparam widget ... Widgets that should be added to the layout.
-- @constructorfct wibox.layout.fixed.horizontal
function fixed.horizontal(...)
return get_layout("x", ...)
end
--- Returns a new vertical fixed layout. Each widget will get as much space as it
-- asks for and each widget will be drawn next to its neighboring widget.
-- Widgets can be added via :add() or as arguments to this function.
-- Note that widgets ignore `forced_width`. They will use the preferred/minimum height
-- on the vertical axis, and a stretched width on the horizontal axis.
--- Creates and returns a new vertical fixed layout.
--
-- @tparam widget ... Widgets that should be added to the layout.
-- @constructorfct wibox.layout.fixed.vertical
function fixed.vertical(...)
return get_layout("y", ...)
end
--- Add spacing between each layout widgets.
--- The amount of space inserted between the child widgets.
--
-- If a `spacing_widget` is defined, this value is used for its size.
--
--@DOC_wibox_layout_fixed_spacing_EXAMPLE@
--
@ -381,10 +470,6 @@ end
--@DOC_fixed_COMMON@
--@DOC_widget_COMMON@
--@DOC_object_COMMON@
return fixed
-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80

View File

@ -1,9 +1,25 @@
---------------------------------------------------------------------------
-- A `flex` layout may be initialized with any number of child widgets, and
-- during runtime widgets may be added and removed dynamically.
--
-- On the main axis, the layout will divide the available space evenly between
-- all child widgets, without any regard to how much space these widgets might
-- be asking for.
--
-- Just like @{wibox.layout.fixed}, `flex` allows adding spacing between the
-- widgets, either as an ofset via @{spacing} or with a
-- @{spacing_widget}.
--
-- On its secondary axis, the layout's size is determined by the largest child
-- widget. Smaller child widgets are then placed with the same size.
-- Therefore, child widgets may ignore their `forced_width` or `forced_height`
-- properties for vertical and horizontal layouts respectively.
--
--@DOC_wibox_layout_defaults_flex_EXAMPLE@
-- @author Uli Schlachter
-- @copyright 2010 Uli Schlachter
-- @layoutmod wibox.layout.flex
-- @supermodule wibox.layout.fixed
---------------------------------------------------------------------------
local base = require("wibox.widget.base")
@ -15,6 +31,16 @@ local gtable = require("gears.table")
local flex = {}
-- {{{ Override inherited properties we want to hide
--- From `wibox.layout.fixed`.
-- @property fill_space
-- @tparam boolean fill_space
-- @propemits true false
-- @hidden
-- }}}
--- Add some widgets to the given fixed layout.
--
-- @tparam widget ... Widgets that should be added (must at least be one).
@ -45,14 +71,18 @@ local flex = {}
-- @treturn boolean If the operation is successful
-- @method insert
-- @emits widget::inserted
-- @emitstparam widget::inserted widget self The fixed layout.
-- @emitstparam widget::inserted widget widget index The inserted widget.
-- @emitstparam widget::inserted widget self The layout.
-- @emitstparam widget::inserted widget widget The inserted widget.
-- @emitstparam widget::inserted number count The widget count.
-- @interface layout
--- The widget used to fill the spacing between the layout elements.
--- A widget to insert as a separator between child widgets.
--
-- By default, no widget is used.
-- If this property is a valid widget and @{spacing} is greater than `0`, a
-- copy of this widget is inserted between each child widget, with its size in
-- the layout's main direction determined by @{spacing}.
--
-- By default no widget is used and any @{spacing} is applied as an empty offset.
--
--@DOC_wibox_layout_flex_spacing_widget_EXAMPLE@
--
@ -61,13 +91,16 @@ local flex = {}
-- @propemits true false
-- @interface layout
--- Add spacing between each layout widgets.
--- The amount of space inserted between the child widgets.
--
-- If a @{spacing_widget} is defined, this value is used for its size.
--
--@DOC_wibox_layout_flex_spacing_EXAMPLE@
--
-- @property spacing
-- @tparam number spacing Spacing between widgets.
-- @propemits true false
-- @interface layout
function flex:layout(_, width, height)
local result = {}
@ -184,10 +217,7 @@ local function get_layout(dir, widget1, ...)
return ret
end
--- Returns a new horizontal flex layout.
--
-- A flex layout shares the available space.
-- equally among all widgets. Widgets can be added via `:add(widget)`.
--- Creates and returns a new horizontal flex layout.
--
-- @tparam widget ... Widgets that should be added to the layout.
-- @constructorfct wibox.layout.flex.horizontal
@ -195,10 +225,7 @@ function flex.horizontal(...)
return get_layout("horizontal", ...)
end
--- Returns a new vertical flex layout.
--
-- A flex layout shares the available space
-- equally among all widgets. Widgets can be added via `:add(widget)`.
--- Creates and returns a new vertical flex layout.
--
-- @tparam widget ... Widgets that should be added to the layout.
-- @constructorfct wibox.layout.flex.vertical
@ -208,10 +235,6 @@ end
--@DOC_fixed_COMMON@
--@DOC_widget_COMMON@
--@DOC_object_COMMON@
return flex
-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80

View File

@ -14,6 +14,7 @@
-- @author getzze
-- @copyright 2017 getzze
-- @layoutmod wibox.layout.grid
-- @supermodule wibox.widget.base
---------------------------------------------------------------------------
local setmetatable = setmetatable
@ -961,10 +962,6 @@ end
--@DOC_fixed_COMMON@
--@DOC_widget_COMMON@
--@DOC_object_COMMON@
return setmetatable(grid, grid.mt)
-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80

View File

@ -8,6 +8,7 @@
-- @author Emmanuel Lepage Vallee
-- @copyright 2016 Emmanuel Lepage Vallee
-- @layoutmod wibox.layout.manual
-- @supermodule wibox.widget.base
---------------------------------------------------------------------------
local gtable = require("gears.table")
local base = require("wibox.widget.base")
@ -245,8 +246,4 @@ end
--@DOC_fixed_COMMON@
--@DOC_widget_COMMON@
--@DOC_object_COMMON@
return setmetatable(manual_layout, {__call=function(_,...) return new_manual(...) end})

View File

@ -7,6 +7,8 @@
-- @author Emmanuel Lepage Vallee
-- @copyright 2016 Emmanuel Lepage Vallee
-- @layoutmod wibox.layout.ratio
-- @supermodule wibox.layout.flex
-- @see 03-declarative-layout.md
---------------------------------------------------------------------------
local base = require("wibox.widget.base" )
@ -524,10 +526,6 @@ end
--@DOC_fixed_COMMON@
--@DOC_widget_COMMON@
--@DOC_object_COMMON@
return ratio
-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80

View File

@ -12,6 +12,7 @@
-- @author Emmanuel Lepage Vallee
-- @copyright 2016 Emmanuel Lepage Vallee
-- @layoutmod wibox.layout.stack
-- @supermodule wibox.layout.fixed
---------------------------------------------------------------------------
local base = require("wibox.widget.base" )
@ -210,9 +211,5 @@ end
--@DOC_fixed_COMMON@
--@DOC_widget_COMMON@
--@DOC_object_COMMON@
return setmetatable(stack, stack.mt)
-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80

View File

@ -2,6 +2,7 @@
-- @author Uli Schlachter
-- @copyright 2010 Uli Schlachter
-- @classmod wibox.widget.base
-- @supermodule gears.object
---------------------------------------------------------------------------
local object = require("gears.object")
@ -17,6 +18,184 @@ local table = table
local base = {}
-- {{{ Properties available on all widgets
--- Get or set the children elements.
-- @property children
-- @tparam table children The children.
-- @baseclass wibox.widget.base
--- Get all direct and indirect children widgets.
-- This will scan all containers recursively to find widgets
-- Warning: This method it prone to stack overflow if there is a loop in the
-- widgets hierarchy. A hierarchy loop is when a widget, or any of its
-- children, contain (directly or indirectly) itself.
-- @property all_children
-- @tparam table children The children.
-- @baseclass wibox.widget.base
--- Force a widget height.
-- @property forced_height
-- @tparam number|nil height The height (`nil` for automatic)
-- @baseclass wibox.widget.base
--- Force a widget width.
-- @property forced_width
-- @tparam number|nil width The width (`nil` for automatic)
-- @baseclass wibox.widget.base
--- The widget opacity (transparency).
-- @property opacity
-- @tparam[opt=1] number opacity The opacity (between 0 and 1)
-- @baseclass wibox.widget.base
--- The widget visibility.
-- @property visible
-- @param boolean
-- @baseclass wibox.widget.base
--- The widget buttons.
--
-- The table contains a list of `awful.button` objects.
-- @property buttons
-- @param table
-- @see awful.button
-- @baseclass wibox.widget.base
-- }}}
-- {{{ Signals available on the widgets.
--- When the layout (size) change.
-- This signal is emitted when the previous results of `:layout()` and `:fit()`
-- are no longer valid. Unless this signal is emitted, `:layout()` and `:fit()`
-- must return the same result when called with the same arguments.
-- @signal widget::layout_changed
-- @see widget::redraw_needed
-- @baseclass wibox.widget.base
--- When the widget content changed.
-- This signal is emitted when the content of the widget changes. The widget will
-- be redrawn, it is not re-layouted. Put differently, it is assumed that
-- `:layout()` and `:fit()` would still return the same results as before.
-- @signal widget::redraw_needed
-- @see widget::layout_changed
-- @baseclass wibox.widget.base
--- When a mouse button is pressed over the widget.
-- @signal button::press
-- @tparam table self The current object instance itself.
-- @tparam number lx The horizontal position relative to the (0,0) position in
-- the widget.
-- @tparam number ly The vertical position relative to the (0,0) position in the
-- widget.
-- @tparam number button The button number.
-- @tparam table mods The modifiers (mod4, mod1 (alt), Control, Shift)
-- @tparam table find_widgets_result The entry from the result of
-- @{wibox.drawable:find_widgets} for the position that the mouse hit.
-- @tparam wibox.drawable find_widgets_result.drawable The drawable containing
-- the widget.
-- @tparam widget find_widgets_result.widget The widget being displayed.
-- @tparam wibox.hierarchy find_widgets_result.hierarchy The hierarchy
-- managing the widget's geometry.
-- @tparam number find_widgets_result.x An approximation of the X position that
-- the widget is visible at on the surface.
-- @tparam number find_widgets_result.y An approximation of the Y position that
-- the widget is visible at on the surface.
-- @tparam number find_widgets_result.width An approximation of the width that
-- the widget is visible at on the surface.
-- @tparam number find_widgets_result.height An approximation of the height that
-- the widget is visible at on the surface.
-- @tparam number find_widgets_result.widget_width The exact width of the widget
-- in its local coordinate system.
-- @tparam number find_widgets_result.widget_height The exact height of the widget
-- in its local coordinate system.
-- @see mouse
-- @baseclass wibox.widget.base
--- When a mouse button is released over the widget.
-- @signal button::release
-- @tparam table self The current object instance itself.
-- @tparam number lx The horizontal position relative to the (0,0) position in
-- the widget.
-- @tparam number ly The vertical position relative to the (0,0) position in the
-- widget.
-- @tparam number button The button number.
-- @tparam table mods The modifiers (mod4, mod1 (alt), Control, Shift)
-- @tparam table find_widgets_result The entry from the result of
-- @{wibox.drawable:find_widgets} for the position that the mouse hit.
-- @tparam wibox.drawable find_widgets_result.drawable The drawable containing
-- the widget.
-- @tparam widget find_widgets_result.widget The widget being displayed.
-- @tparam wibox.hierarchy find_widgets_result.hierarchy The hierarchy
-- managing the widget's geometry.
-- @tparam number find_widgets_result.x An approximation of the X position that
-- the widget is visible at on the surface.
-- @tparam number find_widgets_result.y An approximation of the Y position that
-- the widget is visible at on the surface.
-- @tparam number find_widgets_result.width An approximation of the width that
-- the widget is visible at on the surface.
-- @tparam number find_widgets_result.height An approximation of the height that
-- the widget is visible at on the surface.
-- @tparam number find_widgets_result.widget_width The exact width of the widget
-- in its local coordinate system.
-- @tparam number find_widgets_result.widget_height The exact height of the widget
-- in its local coordinate system.
-- @see mouse
-- @baseclass wibox.widget.base
--- When the mouse enter a widget.
-- @signal mouse::enter
-- @tparam table self The current object instance itself.
-- @tparam table find_widgets_result The entry from the result of
-- @{wibox.drawable:find_widgets} for the position that the mouse hit.
-- @tparam wibox.drawable find_widgets_result.drawable The drawable containing
-- the widget.
-- @tparam widget find_widgets_result.widget The widget being displayed.
-- @tparam wibox.hierarchy find_widgets_result.hierarchy The hierarchy
-- managing the widget's geometry.
-- @tparam number find_widgets_result.x An approximation of the X position that
-- the widget is visible at on the surface.
-- @tparam number find_widgets_result.y An approximation of the Y position that
-- the widget is visible at on the surface.
-- @tparam number find_widgets_result.width An approximation of the width that
-- the widget is visible at on the surface.
-- @tparam number find_widgets_result.height An approximation of the height that
-- the widget is visible at on the surface.
-- @tparam number find_widgets_result.widget_width The exact width of the widget
-- in its local coordinate system.
-- @tparam number find_widgets_result.widget_height The exact height of the widget
-- in its local coordinate system.
-- @see mouse
-- @baseclass wibox.widget.base
--- When the mouse leave a widget.
-- @signal mouse::leave
-- @tparam table self The current object instance itself.
-- @tparam table find_widgets_result The entry from the result of
-- @{wibox.drawable:find_widgets} for the position that the mouse hit.
-- @tparam wibox.drawable find_widgets_result.drawable The drawable containing
-- the widget.
-- @tparam widget find_widgets_result.widget The widget being displayed.
-- @tparam wibox.hierarchy find_widgets_result.hierarchy The hierarchy
-- managing the widget's geometry.
-- @tparam number find_widgets_result.x An approximation of the X position that
-- the widget is visible at on the surface.
-- @tparam number find_widgets_result.y An approximation of the Y position that
-- the widget is visible at on the surface.
-- @tparam number find_widgets_result.width An approximation of the width that
-- the widget is visible at on the surface.
-- @tparam number find_widgets_result.height An approximation of the height that
-- the widget is visible at on the surface.
-- @tparam number find_widgets_result.widget_width The exact width of the widget
-- in its local coordinate system.
-- @tparam number find_widgets_result.widget_height The exact height of the widget
-- in its local coordinate system.
-- @see mouse
-- @baseclass wibox.widget.base
-- }}}
-- {{{ Functions on widgets
-- Functions available on all widgets.
@ -30,7 +209,8 @@ end, true)
--- Set a widget's visibility.
-- @tparam boolean b Whether the widget is visible.
-- @method set_visible
-- @method wibox.widget.base:set_visible
-- @hidden
function base.widget:set_visible(b)
if b ~= self._private.visible then
self._private.visible = b
@ -42,6 +222,7 @@ end
--- Add a new `awful.button` to this widget.
-- @tparam awful.button button The button to add.
-- @method wibox.widget.base:add_button
function base.widget:add_button(button)
if not button then return end
@ -65,7 +246,8 @@ end
--- Is the widget visible?
-- @treturn boolean
-- @method get_visible
-- @method wibox.widget.base:get_visible
-- @hidden
function base.widget:get_visible()
return self._private.visible or false
end
@ -73,17 +255,19 @@ end
--- Set a widget's opacity.
-- @tparam number o The opacity to use (a number from 0 (transparent) to 1
-- (opaque)).
-- @method set_opacity
-- @method wibox.widget.base:set_opacity
-- @hidden
function base.widget:set_opacity(o)
if o ~= self._private.opacity then
self._private.opacity = o
self:emit_signal("widget::redraw")
self:emit_signal("widget::redraw_needed")
end
end
--- Get the widget's opacity.
-- @treturn number The opacity (between 0 (transparent) and 1 (opaque)).
-- @method get_opacity
-- @method wibox.widget.base:get_opacity
-- @hidden
function base.widget:get_opacity()
return self._private.opacity
end
@ -91,8 +275,9 @@ end
--- Set the widget's forced width.
-- @tparam[opt] number width With `nil` the default mechanism of calling the
-- `:fit` method is used.
-- @see fit_widget
-- @method set_forced_width
-- @see wibox.widget.base:fit_widget
-- @method wibox.widget.base:set_forced_width
-- @hidden
function base.widget:set_forced_width(width)
if width ~= self._private.forced_width then
self._private.forced_width = width
@ -108,7 +293,8 @@ end
-- actual size is during a `mouse::enter`, `mouse::leave` or button event.
-- @treturn[opt] number The forced width (nil if automatic).
-- @see fit_widget
-- @method get_forced_width
-- @method wibox.widget.base:get_forced_width
-- @hidden
function base.widget:get_forced_width()
return self._private.forced_width
end
@ -116,8 +302,9 @@ end
--- Set the widget's forced height.
-- @tparam[opt] number height With `nil` the default mechanism of calling the
-- `:fit` method is used.
-- @see fit_widget
-- @method set_height
-- @see wibox.widget.base:fit_widget
-- @method wibox.widget.base:set_height
-- @hidden
function base.widget:set_forced_height(height)
if height ~= self._private.forced_height then
self._private.forced_height = height
@ -132,7 +319,8 @@ end
-- If there is no forced width/height, then the only way to get the widget's
-- actual size is during a `mouse::enter`, `mouse::leave` or button event.
-- @treturn[opt] number The forced height (nil if automatic).
-- @method get_forced_height
-- @method wibox.widget.base:get_forced_height
-- @hidden
function base.widget:get_forced_height()
return self._private.forced_height
end
@ -141,7 +329,8 @@ end
--
-- This method should be re-implemented by the relevant widgets.
-- @treturn table children The children.
-- @method get_children
-- @method wibox.widget.base:get_children
-- @hidden
function base.widget:get_children()
return {}
end
@ -151,7 +340,8 @@ end
-- The default implementation does nothing, this must be re-implemented by
-- all layout and container widgets.
-- @tparam table children A table composed of valid widgets.
-- @method set_children
-- @method wibox.widget.base:set_children
-- @hidden
function base.widget:set_children(children) -- luacheck: no unused
-- Nothing on purpose
end
@ -171,7 +361,8 @@ end
-- *Warning*: This method it prone to stack overflow if the widget, or any of
-- its children, contains (directly or indirectly) itself.
-- @treturn table children The children.
-- @method get_all_children
-- @method wibox.widget.base:get_all_children
-- @hidden
function base.widget:get_all_children()
local ret = {}
digg_children(ret, self)
@ -193,12 +384,17 @@ function base.set_widget_common(self, widget)
end
self._private.widget = w
self:emit_signal("property::widget")
self:emit_signal("widget::layout_changed")
end
--- Emit a signal and ensure all parent widgets in the hierarchies also
-- forward the signal. This is useful to track signals when there is a dynamic
-- set of containers and layouts wrapping the widget.
-- forward the signal.
--
-- This is useful to track signals when there is a dynamic set of containers
-- and layouts wrapping the widget.
--
-- Note that this function has some flaws:
--
@ -213,7 +409,7 @@ end
--
-- @tparam string signal_name
-- @param ... Other arguments
-- @method emit_signal_recursive
-- @method wibox.widget.base:emit_signal_recursive
function base.widget:emit_signal_recursive(signal_name, ...)
-- This is a convenience wrapper, the real implementation is in the
-- hierarchy.
@ -223,12 +419,14 @@ end
--- Get the index of a widget.
-- @tparam widget widget The widget to look for.
-- @tparam[opt] boolean recursive Also check sub-widgets?
-- @tparam[opt] widget ... Additional widgets to add at the end of the "path"
-- @treturn number The index.
-- @tparam[opt] boolean recursive Recursively check accross the sub-widgets
-- hierarchy.
-- @tparam[opt] widget ... Additional widgets to add at the end of the
-- sub-widgets hierarchy "path".
-- @treturn number The widget index.
-- @treturn widget The parent widget.
-- @treturn table The path between "self" and "widget".
-- @method index
-- @treturn table The hierarchy path between "self" and "widget".
-- @method wibox.widget.base:index
function base.widget:index(widget, recursive, ...)
local widgets = self:get_children()
for idx, w in ipairs(widgets) do
@ -471,8 +669,7 @@ end
-- Read the table, separate attributes from widgets.
local function parse_table(t, leave_empty)
local max = 0
local attributes, widgets, with_handlers = {}, {}, {}
local attributes, widgets = {}, {}
for k,v in pairs(t) do
if type(k) == "number" then
if v then
@ -482,35 +679,20 @@ local function parse_table(t, leave_empty)
max = k
end
-- Those are not added "normally". They have their own callback
-- to add them.
if rawget(v, "_set_declarative_handler") then
with_handlers[k] = v
else
widgets[k] = v
end
widgets[k] = v
end
elseif type(v) == "table" and rawget(v, "_set_declarative_handler") then
with_handlers[k] = v
else
attributes[k] = v
end
end
-- Make sure there is no holes in the widgets table.
if #with_handlers > 0 then
for k in pairs(with_handlers) do
table.remove(widgets, k)
end
end
-- Pack the sparse table, if the container doesn't support sparse tables.
if not leave_empty then
widgets = gtable.from_sparse(widgets)
max = #widgets
end
return max, attributes, widgets, with_handlers
return max, attributes, widgets
end
-- Recursively build a container from a declarative table.
@ -526,19 +708,8 @@ local function drill(ids, content)
-- Create layouts based on metatable's __call.
local l = layout.is_widget and layout or layout()
-- Check if the widget added it's own element by ids
--FIXME #2181
if l and l._private and l._private.by_id then
for k, v in pairs(l._private.by_id) do
ids[k] = ids[k] or {}
for _, v2 in ipairs(v) do
table.insert(ids[k], v2)
end
end
end
-- Get the number of children widgets (including nil widgets).
local max, attributes, widgets, with_handlers = parse_table(content, l.allow_empty_widget)
local max, attributes, widgets = parse_table(content, l.allow_empty_widget)
-- Get the optional identifier to create a virtual widget tree to place
-- in an "access table" to be able to retrieve the widget.
@ -560,48 +731,34 @@ local function drill(ids, content)
end
end
-- Let some containers handle the template themselves.
-- This can be used for use cases such as lazy loading, repeaters or
-- conditional loaders.
if l._accept_templates and l.set_templates then
l:set_templates(widgets)
return l, id
end
if widgets and max > 0 then
-- Add all widgets.
for k = 1, max do
-- ipairs cannot be used on sparse tables.
local v, id2, e = widgets[k], id, nil
if v then
-- It is another declarative container, parse it.
if (not v.is_widget) and (v.widget or v.layout) then
e, id2 = drill(ids, v)
widgets[k] = e
elseif (not v.is_widget) and is_callable(v) then
widgets[k] = v()
end
base.check_widget(widgets[k])
-- Add all widgets.
for k = 1, max do
-- ipairs cannot be used on sparse tables.
local v, id2, e = widgets[k], id, nil
if v then
-- It is another declarative container, parse it.
if (not v.is_widget) and (v.widget or v.layout) then
e, id2 = drill(ids, v)
widgets[k] = e
elseif (not v.is_widget) and is_callable(v) then
widgets[k] = v()
end
base.check_widget(widgets[k])
-- Place the widget in the access table.
if id2 then
l [id2] = e
ids[id2] = ids[id2] or {}
table.insert(ids[id2], e)
-- Place the widget in the access table.
if id2 then
l [id2] = e
ids[id2] = ids[id2] or {}
table.insert(ids[id2], e)
end
end
end
end
-- Replace all children (if any) with the new ones.
if widgets then
-- Replace all children (if any) with the new ones.
l:set_children(widgets)
end
-- Now that all the children are set, call the custom handlers.
-- The order is undefined.
for k, v in pairs(with_handlers) do
v:_set_declarative_handler(l, k, ids)
end
return l, id
end
@ -618,7 +775,8 @@ end
--
-- See [The declarative layout system](../documentation/03-declarative-layout.md.html).
-- @tparam table args A table containing the widget's disposition.
-- @method setup
-- @method wibox.widget.base:setup
-- @hidden
function base.widget:setup(args)
local f,ids = self.set_widget or self.add or self.set_first,{}
local w, id = drill(ids, args)

View File

@ -37,8 +37,9 @@ local beautiful = require("beautiful")
local calendar = { mt = {} }
local properties = { "date", "font", "spacing", "week_numbers", "start_sunday", "long_weekdays", "fn_embed" }
local properties = { "date" , "font" , "spacing" , "week_numbers",
"start_sunday", "long_weekdays", "fn_embed", "flex_height",
}
--- The calendar font.
-- @beautiful beautiful.calendar_font
@ -60,6 +61,11 @@ local properties = { "date", "font", "spacing", "week_numbers", "start_sunday",
-- @beautiful beautiful.calendar_long_weekdays
-- @param boolean Use three characters for the weekdays instead of two
--- Allow cells to have flexible height.
-- Flexible height allow cells to adapt their height to fill the empty space at the bottom of the widget.
-- @beautiful beautiful.flex_height
-- @param boolean Cells can skretch to fill the empty space.
--- The calendar date.
--
-- A table representing the date {day=[number|nil], month=[number|nil], year=[number]}.
@ -116,6 +122,12 @@ local properties = { "date", "font", "spacing", "week_numbers", "start_sunday",
-- @param function Function to embed the widget depending on its flag
-- @property fn_embed
--- Allow cells to have flexible height
--
--@DOC_wibox_widget_calendar_flex_height_EXAMPLE@
--
-- @param[opt=false] boolean Allow flex height.
-- @property flex_height
--- Make a textbox
-- @tparam string text Text of the textbox
@ -139,25 +151,47 @@ end
-- @tparam number|nil date.day Date day
-- @treturn widget Grid layout
local function create_month(props, date)
local num_rows = 8
local num_columns = props.week_numbers and 8 or 7
-- Create grid layout
local layout = grid()
layout:set_expand(true)
layout:set_expand(true)
layout:set_homogeneous(true)
layout:set_spacing(props.spacing)
layout:set_forced_num_rows(num_rows)
layout:set_forced_num_cols(num_columns)
local start_row = 3
local start_column = num_columns - 6
local week_start = props.start_sunday and 1 or 2
local last_day = os.date("*t", os.time{year=date.year, month=date.month+1, day=0})
local month_days = last_day.day
local column_fday = (last_day.wday - month_days + 1 - week_start ) % 7
local num_columns = props.week_numbers and 8 or 7
local start_column = num_columns - 6
-- Compute number of rows
-- There are at least 4 weeks in a month
local num_rows = 4
-- On every month but february on non bisextile years
if last_day.day > 28 then
-- The number of days span over at least 5 weeks
num_rows = num_rows + 1
-- On month with 30+ days add 1 week if:
-- - if 30 days and the first day is the last day of the week
-- - if 31 days and the first days is at least the second to last day
if column_fday >= 5 then
if last_day.day == 30 and column_fday == 6 or last_day.day == 31 then
num_rows = num_rows + 1
end
end
-- If the first day of february is anything but the first day of the week
elseif column_fday > 1 then
-- Span over 5 weeks
num_rows = num_rows + 1
end
-- Create grid layout
local layout = grid()
if props.flex_height then
layout:set_expand(true)
end
layout:set_homogeneous(true)
layout:set_spacing(props.spacing)
layout:set_forced_num_rows(num_rows)
layout:set_forced_num_cols(num_columns)
--local flags = {"header", "weekdays", "weeknumber", "normal", "focus"}
local cell_date, t, i, j, w, flag, text
@ -331,6 +365,7 @@ local function get_calendar(type, date, font)
ret._private.week_numbers = beautiful.calendar_week_numbers or false
ret._private.start_sunday = beautiful.calendar_start_sunday or false
ret._private.long_weekdays = beautiful.calendar_long_weekdays or false
ret._private.flex_height = beautiful.calendar_flex_height or false
ret._private.fn_embed = function (w, _) return w end
-- header specific

View File

@ -9,6 +9,7 @@
-- @author Emmanuel Lepage Valle
-- @copyright 2010 Emmanuel Lepage Vallee
-- @widgetmod wibox.widget.checkbox
-- @supermodule wibox.widget.base
---------------------------------------------------------------------------
local color = require( "gears.color" )
@ -303,10 +304,6 @@ local function new(checked, args)
return ret
end
--@DOC_widget_COMMON@
--@DOC_object_COMMON@
return setmetatable({}, { __call = function(_, ...) return new(...) end})
-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80

File diff suppressed because it is too large Load Diff

View File

@ -1,26 +1,27 @@
---------------------------------------------------------------------------
-- A widget to display image.
-- A widget to display an image.
--
-- The `wibox.widget.imagebox` is part of the Awesome WM's wiboxes system
-- The `wibox.widget.imagebox` is part of the Awesome WM's widget system
-- (see @{03-declarative-layout.md}).
--
-- This widget displays an image. The image can be a file,
-- a cairo image surface, or an rsvg handle object (see the
-- [image property](#image)).
--
-- Use a `wibox.widget.imagebox`
-- Examples using a `wibox.widget.imagebox`:
-- ---
--
-- @DOC_wibox_widget_defaults_imagebox_EXAMPLE@
--
-- Alternatively, you can declare the `imagebox` widget using the
-- declarative pattern (Both codes are strictly equivalent):
-- declarative pattern (both variants are strictly equivalent):
--
-- @DOC_wibox_widget_declarative-pattern_imagebox_EXAMPLE@
--
-- @author Uli Schlachter
-- @copyright 2010 Uli Schlachter
-- @widgetmod wibox.widget.imagebox
-- @supermodule wibox.widget.base
---------------------------------------------------------------------------
local lgi = require("lgi")
@ -47,67 +48,40 @@ end
local imagebox = { mt = {} }
local rsvg_handle_cache = setmetatable({}, { __mode = 'v' })
local rsvg_handle_cache = setmetatable({}, { __mode = 'k' })
---Load rsvg handle form image file
---@tparam string file Path to svg file.
---@return Rsvg handle
-- @tparam string file Path to svg file.
-- @return Rsvg handle
-- @treturn table A table where cached data can be stored.
local function load_rsvg_handle(file)
if not Rsvg then return end
local cache = rsvg_handle_cache[file]
local cache = (rsvg_handle_cache[file] or {})["handle"]
if cache then
return cache
return cache, rsvg_handle_cache[file]
end
local handle, err = Rsvg.Handle.new_from_file(file)
if not err then
rsvg_handle_cache[file] = handle
return handle
end
end
local handle, err
-- Draw an imagebox with the given cairo context in the given geometry.
function imagebox:draw(_, cr, width, height)
if width == 0 or height == 0 or not self._private.default then return end
-- Set the clip
if self._private.clip_shape then
cr:clip(self._private.clip_shape(cr, width, height, unpack(self._private.clip_args)))
end
if not self._private.resize_forbidden then
-- Let's scale the image so that it fits into (width, height)
local w, h = self._private.default.width, self._private.default.height
local aspect = math.min(width / w, height / h)
cr:scale(aspect, aspect)
end
if self._private.handle then
self._private.handle:render_cairo(cr)
if file:match("<[?]?xml") or file:match("<svg") then
handle, err = Rsvg.Handle.new_from_data(file)
else
cr:set_source_surface(self._private.image, 0, 0)
cr:paint()
end
end
-- Fit the imagebox into the given geometry
function imagebox:fit(_, width, height)
if not self._private.default then return 0, 0 end
local w, h = self._private.default.width, self._private.default.height
if not self._private.resize_forbidden or w > width or h > height then
local aspect = math.min(width / w, height / h)
return w * aspect, h * aspect
handle, err = Rsvg.Handle.new_from_file(file)
end
return w, h
if not err then
rsvg_handle_cache[file] = rsvg_handle_cache[file] or {}
rsvg_handle_cache[file]["handle"] = handle
return handle, rsvg_handle_cache[file]
end
end
---Apply cairo surface for given imagebox widget
local function set_surface(ib, surf)
local is_surd_valid = surf.width > 0 and surf.height > 0
if not is_surd_valid then return end
local is_surf_valid = surf.width > 0 and surf.height > 0
if not is_surf_valid then return false end
ib._private.default = { width = surf.width, height = surf.height }
ib._private.handle = nil
@ -116,14 +90,16 @@ local function set_surface(ib, surf)
end
---Apply RsvgHandle for given imagebox widget
local function set_handle(ib, handle)
local function set_handle(ib, handle, cache)
local dim = handle:get_dimensions()
local is_handle_valid = dim.width > 0 and dim.height > 0
if not is_handle_valid then return end
if not is_handle_valid then return false end
ib._private.default = { width = dim.width, height = dim.height }
ib._private.handle = handle
ib._private.cache = cache
ib._private.image = nil
return true
end
@ -135,32 +111,208 @@ end
---@treturn boolean True if image was successfully applied
local function load_and_apply(ib, file, image_loader, image_setter)
local image_applied
local object = image_loader(file)
local object, cache = image_loader(file)
if object then
image_applied = image_setter(ib, object)
image_applied = image_setter(ib, object, cache)
end
return image_applied
end
---Update the cached size depending on the stylesheet and dpi.
--
-- It's necessary because a single RSVG handle can be used by
-- many imageboxes. So DPI and Stylesheet need to be set each time.
local function update_dpi(self, ctx)
if not self._private.handle then return end
local dpi = self._private.auto_dpi and
ctx.dpi or
self._private.dpi or
nil
local need_dpi = dpi and
self._private.last_dpi ~= dpi
local need_style = self._private.handle.set_stylesheet and
self._private.stylesheet
local old_size = self._private.default and self._private.default.width
if dpi and dpi ~= self._private.cache.dpi then
if type(dpi) == "table" then
self._private.handle:set_dpi_x_y(dpi.x, dpi.y)
else
self._private.handle:set_dpi(dpi)
end
end
if need_style and self._private.cache.stylesheet ~= self._private.stylesheet then
self._private.handle:set_stylesheet(self._private.stylesheet)
end
-- Reload the size.
if need_dpi or (need_style and self._private.stylesheet ~= self._private.last_stylesheet) then
set_handle(self, self._private.handle, self._private.cache)
end
self._private.last_dpi = dpi
self._private.cache.dpi = dpi
self._private.last_stylesheet = self._private.stylesheet
self._private.cache.stylesheet = self._private.stylesheet
-- This can happen in the constructor when `dpi` is set after `image`.
if old_size and old_size ~= self._private.default.width then
self:emit_signal("widget::redraw_needed")
self:emit_signal("widget::layout_changed")
end
end
-- Draw an imagebox with the given cairo context in the given geometry.
function imagebox:draw(ctx, cr, width, height)
if width == 0 or height == 0 or not self._private.default then return end
-- For valign = "top" and halign = "left"
local translate = {
x = 0,
y = 0,
}
update_dpi(self, ctx)
local w, h = self._private.default.width, self._private.default.height
if self._private.resize then
-- That's for the "fit" policy.
local aspects = {
w = width / w,
h = height / h
}
local policy = {
w = self._private.horizontal_fit_policy or "auto",
h = self._private.vertical_fit_policy or "auto"
}
for _, aspect in ipairs {"w", "h"} do
if self._private.upscale == false and (w < width and h < height) then
aspects[aspect] = 1
elseif self._private.downscale == false and (w >= width and h >= height) then
aspects[aspect] = 1
elseif policy[aspect] == "none" then
aspects[aspect] = 1
elseif policy[aspect] == "auto" then
aspects[aspect] = math.min(width / w, height / h)
end
end
if self._private.halign == "center" then
translate.x = math.floor((width - w*aspects.w)/2)
elseif self._private.halign == "right" then
translate.x = math.floor(width - (w*aspects.w))
end
if self._private.valign == "center" then
translate.y = math.floor((height - h*aspects.h)/2)
elseif self._private.valign == "bottom" then
translate.y = math.floor(height - (h*aspects.h))
end
cr:translate(translate.x, translate.y)
-- Before using the scale, make sure it is below the threshold.
local threshold, max_factor = self._private.max_scaling_factor, math.max(aspects.w, aspects.h)
if threshold and threshold > 0 and threshold < max_factor then
aspects.w = (aspects.w*threshold)/max_factor
aspects.h = (aspects.h*threshold)/max_factor
end
-- Set the clip
if self._private.clip_shape then
cr:clip(self._private.clip_shape(cr, w*aspects.w, h*aspects.h, unpack(self._private.clip_args)))
end
cr:scale(aspects.w, aspects.h)
else
if self._private.halign == "center" then
translate.x = math.floor((width - w)/2)
elseif self._private.halign == "right" then
translate.x = math.floor(width - w)
end
if self._private.valign == "center" then
translate.y = math.floor((height - h)/2)
elseif self._private.valign == "bottom" then
translate.y = math.floor(height - h)
end
cr:translate(translate.x, translate.y)
-- Set the clip
if self._private.clip_shape then
cr:clip(self._private.clip_shape(cr, w, h, unpack(self._private.clip_args)))
end
end
if self._private.handle then
self._private.handle:render_cairo(cr)
else
cr:set_source_surface(self._private.image, 0, 0)
local filter = self._private.scaling_quality
if filter then
cr:get_source():set_filter(cairo.Filter[filter:upper()])
end
cr:paint()
end
end
-- Fit the imagebox into the given geometry
function imagebox:fit(ctx, width, height)
if not self._private.default then return 0, 0 end
update_dpi(self, ctx)
local w, h = self._private.default.width, self._private.default.height
if w <= width and h <= height and self._private.upscale == false then
return w, h
end
if (w < width or h < height) and self._private.downscale == false then
return w, h
end
if self._private.resize or w > width or h > height then
local aspect = math.min(width / w, height / h)
return w * aspect, h * aspect
end
return w, h
end
--- The image rendered by the `imagebox`.
--
-- It can can be any of the following:
--
-- * A `string` : Interpreted as the path to an image file,
-- * A cairo image surface : Directly used as is,
-- * An rsvg handle object : Directly used as is,
-- * `nil` : Unset the image.
-- * A `string`: Interpreted as a path to an image file
-- * A cairo image surface: Directly used as-is
-- * A librsvg handle object: Directly used as-is
-- * `nil`: Unset the image.
--
-- @property image
-- @tparam image image The image to render.
-- @propemits false false
-- @see set_image
--- Set the `imagebox` image.
--
-- The image can be a file, a cairo image surface, or an rsvg handle object
-- (see the [image property](#image)).
-- @method imagebox:set_image
-- @method set_image
-- @hidden
-- @tparam image image The image to render.
-- @treturn boolean `true` on success, `false` if the image cannot be used.
-- @usage my_imagebox:set_image(beautiful.awesome_icon)
@ -169,7 +321,10 @@ end
function imagebox:set_image(image)
local setup_succeed
if type(image) == "userdata" then
-- Keep the original to prevent the cache from being GCed.
self._private.original_image = image
if type(image) == "userdata" and not (Rsvg and Rsvg.Handle:is_type_of(image)) then
-- This function is not documented to handle userdata objects, but
-- historically it did, and it did by just assuming they refer to a
-- cairo surface.
@ -186,7 +341,8 @@ function imagebox:set_image(image)
end
elseif Rsvg and Rsvg.Handle:is_type_of(image) then
-- try to apply given rsvg handle
setup_succeed = set_handle(self, image)
rsvg_handle_cache[image] = rsvg_handle_cache[image] or {}
setup_succeed = set_handle(self, image, rsvg_handle_cache[image])
elseif cairo.Surface:is_type_of(image) then
-- try to apply given cairo surface
setup_succeed = set_surface(self, image)
@ -207,23 +363,27 @@ function imagebox:set_image(image)
end
--- Set a clip shape for this imagebox.
-- A clip shape define an area where the content is displayed and one where it
-- is trimmed.
--
-- A clip shape defines an area and dimension to which the content should be
-- trimmed.
--
-- @DOC_wibox_widget_imagebox_clip_shape_EXAMPLE@
--
-- @property clip_shape
-- @tparam function|gears.shape clip_shape A `gears.shape` compatible shape function.
-- @propemits true false
-- @see gears.shape
-- @see set_clip_shape
--- Set a clip shape for this imagebox.
-- A clip shape define an area where the content is displayed and one where it
-- is trimmed.
--
-- Any other parameters will be passed to the clip shape function.
-- A clip shape defines an area and dimensions to which the content should be
-- trimmed.
--
-- Additional parameters will be passed to the clip shape function.
--
-- @tparam function|gears.shape clip_shape A `gears_shape` compatible shape function.
-- @method imagebox:set_clip_shape
-- @method set_clip_shape
-- @hidden
-- @see gears.shape
-- @see clip_shape
function imagebox:set_clip_shape(clip_shape, ...)
@ -234,22 +394,282 @@ function imagebox:set_clip_shape(clip_shape, ...)
end
--- Should the image be resized to fit into the available space?
--
-- Note that `upscale` and `downscale` can affect the value of `resize`.
-- If conflicting values are passed to the constructor, then the result
-- is undefined.
--
-- @DOC_wibox_widget_imagebox_resize_EXAMPLE@
-- @property resize
-- @propemits true false
-- @tparam boolean resize
--- Should the image be resized to fit into the available space?
-- @tparam boolean allowed If `false`, the image will be clipped, else it will
-- be resized to fit into the available space.
-- @method imagebox:set_resize
--- Allow the image to be upscaled (made bigger).
--
-- Note that `upscale` and `downscale` can affect the value of `resize`.
-- If conflicting values are passed to the constructor, then the result
-- is undefined.
--
-- @DOC_wibox_widget_imagebox_upscale_EXAMPLE@
-- @property upscale
-- @tparam boolean upscale
-- @see downscale
-- @see resize
--- Allow the image to be downscaled (made smaller).
--
-- Note that `upscale` and `downscale` can affect the value of `resize`.
-- If conflicting values are passed to the constructor, then the result
-- is undefined.
--
-- @DOC_wibox_widget_imagebox_downscale_EXAMPLE@
-- @property downscale
-- @tparam boolean downscale
-- @see upscale
-- @see resize
--- Set the SVG CSS stylesheet.
--
-- If the image is an SVG (vector graphics), this property allows to set
-- a CSS stylesheet. It can be used to set colors and much more.
--
-- Note that this property is a string, not a path. If the stylesheet is
-- stored on disk, read the content first.
--
--@DOC_wibox_widget_imagebox_stylesheet_EXAMPLE@
--
-- @property stylesheet
-- @tparam string stylesheet
-- @propemits true false
--- Set the SVG DPI (dot per inch).
--
-- Force a specific DPI when rendering the `.svg`. For other file formats,
-- this does nothing.
--
-- It can either be a number of a table containing the `x` and `y` keys.
--
-- Please note that DPI and `resize` can "fight" each other and end up
-- making the image smaller instead of bigger.
--
--@DOC_wibox_widget_imagebox_dpi_EXAMPLE@
--
-- @property dpi
-- @tparam number|table dpi
-- @propemits true false
-- @see auto_dpi
--- Use the object DPI when rendering the SVG.
--
-- By default, the SVG are interpreted as-is. When this property is set,
-- the screen DPI will be passed to the SVG renderer. Depending on which
-- tool was used to create the `.svg`, this may do nothing at all. However,
-- for example, if the `.svg` uses `<text>` elements and doesn't have an
-- hardcoded stylesheet, the result will differ.
--
-- @property auto_dpi
-- @tparam[opt=false] boolean auto_dpi
-- @propemits true false
-- @see dpi
for _, prop in ipairs {"stylesheet", "dpi", "auto_dpi"} do
imagebox["set_" .. prop] = function(self, value)
-- It will be set in :fit and :draw. The handle is shared
-- by multiple imagebox, so it cannot be set just once.
self._private[prop] = value
self:emit_signal("widget::redraw_needed")
self:emit_signal("widget::layout_changed")
self:emit_signal("property::" .. prop)
end
end
function imagebox:set_resize(allowed)
self._private.resize_forbidden = not allowed
self._private.resize = allowed
if allowed then
self._private.downscale = true
self._private.upscale = true
self:emit_signal("property::downscale", allowed)
self:emit_signal("property::upscale", allowed)
end
self:emit_signal("widget::redraw_needed")
self:emit_signal("widget::layout_changed")
self:emit_signal("property::resize", allowed)
end
for _, prop in ipairs {"downscale", "upscale" } do
imagebox["set_" .. prop] = function(self, allowed)
self._private[prop] = allowed
if self._private.resize ~= (self._private.upscale or self._private.downscale) then
self._private.resize = self._private.upscale or self._private.downscale
self:emit_signal("property::resize", self._private.resize)
end
self:emit_signal("widget::redraw_needed")
self:emit_signal("widget::layout_changed")
self:emit_signal("property::"..prop, allowed)
end
end
--- Set the horizontal fit policy.
--
-- Valid values are:
--
-- * `"auto"`: Honor the `resize` variable and preserve the aspect ratio.
-- This is the default behaviour.
-- * `"none"`: Do not resize at all.
-- * `"fit"`: Resize to the widget width.
--
-- Here is the result for a 22x32 image:
--
-- @DOC_wibox_widget_imagebox_horizontal_fit_policy_EXAMPLE@
--
-- @property horizontal_fit_policy
-- @tparam[opt="auto"] string horizontal_fit_policy
-- @propemits true false
-- @see vertical_fit_policy
-- @see resize
--- Set the vertical fit policy.
--
-- Valid values are:
--
-- * `"auto"`: Honor the `resize` varible and preserve the aspect ratio.
-- This is the default behaviour.
-- * `"none"`: Do not resize at all.
-- * `"fit"`: Resize to the widget height.
--
-- Here is the result for a 32x22 image:
--
-- @DOC_wibox_widget_imagebox_vertical_fit_policy_EXAMPLE@
--
-- @property vertical_fit_policy
-- @tparam[opt="auto"] string horizontal_fit_policy
-- @propemits true false
-- @see horizontal_fit_policy
-- @see resize
--- The vertical alignment.
--
-- Possible values are:
--
-- * `"top"`
-- * `"center"` (default)
-- * `"bottom"`
--
-- @DOC_wibox_widget_imagebox_valign_EXAMPLE@
--
-- @property valign
-- @tparam[opt="center"] string valign
-- @propemits true false
-- @see wibox.container.place
-- @see halign
--- The horizontal alignment.
--
-- Possible values are:
--
-- * `"left"`
-- * `"center"` (default)
-- * `"right"`
--
-- @DOC_wibox_widget_imagebox_halign_EXAMPLE@
--
-- @property halign
-- @tparam[opt="center"] string halign
-- @propemits true false
-- @see wibox.container.place
-- @see valign
--- The maximum scaling factor.
--
-- If an image is scaled too much, it gets very blurry. This
-- property allows to limit the scaling.
-- Use the properties `valign` and `halign` to control how the image will be
-- aligned.
--
-- In the example below, the original size is 22x22
--
-- @DOC_wibox_widget_imagebox_max_scaling_factor_EXAMPLE@
--
-- @property max_scaling_factor
-- @tparam number max_scaling_factor
-- @propemits true false
-- @see valign
-- @see halign
-- @see scaling_quality
--- Set the scaling aligorithm.
--
-- Depending on how the image is used, what is the "correct" way to
-- scale can change. For example, upscaling a pixel art image should
-- not make it blurry. However, scaling up a photo should not make it
-- blocky.
--
--<table class='widget_list' border=1>
-- <tr style='font-weight: bold;'>
-- <th align='center'>Value</th>
-- <th align='center'>Description</th>
-- </tr>
-- <tr><td>fast</td><td>A high-performance filter</td></tr>
-- <tr><td>good</td><td>A reasonable-performance filter</td></tr>
-- <tr><td>best</td><td>The highest-quality available</td></tr>
-- <tr><td>nearest</td><td>Nearest-neighbor filtering (blocky)</td></tr>
-- <tr><td>bilinear</td><td>Linear interpolation in two dimensions</td></tr>
--</table>
--
-- The image used in the example below has a resolution of 32x22 and is
-- intentionally blocky to highlight the difference.
-- It is zoomed by a factor of 3.
--
-- @DOC_wibox_widget_imagebox_scaling_quality_EXAMPLE@
--
-- @property scaling_quality
-- @tparam string scaling_quality Either `"fast"`, `"good"`, `"best"`,
-- `"nearest"` or `"bilinear"`.
-- @propemits true false
-- @see resize
-- @see horizontal_fit_policy
-- @see vertical_fit_policy
-- @see max_scaling_factor
local defaults = {
halign = "left",
valign = "top",
horizontal_fit_policy = "auto",
vertical_fit_policy = "auto",
max_scaling_factor = 0,
scaling_quality = "good"
}
local function get_default(prop, value)
if value == nil then return defaults[prop] end
return value
end
for prop in pairs(defaults) do
imagebox["set_"..prop] = function(self, value)
if value == self._private[prop] then return end
self._private[prop] = get_default(prop, value)
self:emit_signal("widget::redraw_needed")
self:emit_signal("property::"..prop, self._private[prop])
end
imagebox["get_"..prop] = function(self)
if self._private[prop] == nil then
return defaults[prop]
end
return self._private[prop]
end
end
--- Returns a new `wibox.widget.imagebox` instance.
--
-- This is the constructor of `wibox.widget.imagebox`. It creates a new
@ -272,12 +692,14 @@ local function new(image, resize_allowed, clip_shape, ...)
local ret = base.make_widget(nil, nil, {enable_properties = true})
gtable.crush(ret, imagebox, true)
ret._private.resize = true
if image then
ret:set_image(image)
end
if resize_allowed ~= nil then
ret:set_resize(resize_allowed)
ret.resize = resize_allowed
end
ret._private.clip_shape = clip_shape
@ -290,10 +712,6 @@ function imagebox.mt:__call(...)
return new(...)
end
--@DOC_widget_COMMON@
--@DOC_object_COMMON@
return setmetatable(imagebox, imagebox.mt)
-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80

View File

@ -8,6 +8,7 @@
-- @author Emmanuel Lepage Valle
-- @copyright 2012 Emmanuel Lepage Vallee
-- @widgetmod wibox.widget.piechart
-- @supermodule wibox.widget.base
---------------------------------------------------------------------------
local color = require( "gears.color" )
@ -260,9 +261,5 @@ local function new(data_list)
return ret
end
--@DOC_widget_COMMON@
--@DOC_object_COMMON@
return setmetatable(module, { __call = function(_, ...) return new(...) end })
-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80

View File

@ -18,6 +18,7 @@
-- @author Julien Danjou &lt;julien@danjou.info&gt;
-- @copyright 2009 Julien Danjou
-- @widgetmod wibox.widget.progressbar
-- @supermodule wibox.widget.base
---------------------------------------------------------------------------
local setmetatable = setmetatable
@ -520,10 +521,6 @@ function progressbar.mt:__call(...)
return progressbar.new(...)
end
--@DOC_widget_COMMON@
--@DOC_object_COMMON@
return setmetatable(progressbar, progressbar.mt)
-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80

View File

@ -20,6 +20,7 @@
-- @author Emmanuel Lepage Vallee &lt;elv1313@gmail.com&gt;
-- @copyright 2014, 2017 Emmanuel Lepage Vallee
-- @widgetmod wibox.widget.separator
-- @supermodule wibox.widget.base
---------------------------------------------------------------------------
local beautiful = require( "beautiful" )
local base = require( "wibox.widget.base" )
@ -215,9 +216,5 @@ local function new(args)
return ret
end
--@DOC_widget_COMMON@
--@DOC_object_COMMON@
return setmetatable(separator, { __call = function(_, ...) return new(...) end })
-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80

View File

@ -7,6 +7,7 @@
-- @author Emmanuel Lepage Vallee &lt;elv1313@gmail.com&gt;
-- @copyright 2015 Grigory Mishchenko, 2016 Emmanuel Lepage Vallee
-- @widgetmod wibox.widget.slider
-- @supermodule wibox.widget.base
---------------------------------------------------------------------------
local setmetatable = setmetatable
@ -324,7 +325,7 @@ function slider:draw(_, cr, width, height)
local handle_height, handle_width = height, self._private.handle_width
or beautiful.slider_handle_width
or height/2
or math.floor(height/2)
local handle_border_width = self._private.handle_border_width
or beautiful.slider_handle_border_width
@ -362,7 +363,7 @@ function slider:draw(_, cr, width, height)
end
else
bar_height = bar_height or beautiful.slider_bar_height or height
y_offset = (height - bar_height)/2
y_offset = math.floor((height - bar_height)/2)
end
@ -379,8 +380,10 @@ function slider:draw(_, cr, width, height)
bar_shape(cr, width - x_offset - right_margin, bar_height or height)
if bar_active_color and type(bar_color) == "string" and type(bar_active_color) == "string" then
local bar_active_width = active_rate * (width - x_offset - right_margin)
local bar_active_width = math.floor(
active_rate * (width - x_offset - right_margin)
- (handle_width - handle_border_width/2) * (active_rate - 0.5)
)
cr:set_source(color.create_pattern{
type = "linear",
from = {0,0},
@ -452,7 +455,7 @@ function slider:draw(_, cr, width, height)
-- Get the widget size back to it's non-transfored value
local min, _, interval = get_extremums(self)
local rel_value = ((value-min)/interval) * (width-handle_width)
local rel_value = math.floor(((value-min)/interval) * (width-handle_width))
cr:translate(x_offset + rel_value, y_offset)
@ -538,10 +541,6 @@ function slider.mt:__call(...)
return new(...)
end
--@DOC_widget_COMMON@
--@DOC_object_COMMON@
return setmetatable(slider, slider.mt)
-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80

View File

@ -2,6 +2,7 @@
-- @author Uli Schlachter
-- @copyright 2010 Uli Schlachter
-- @widgetmod wibox.widget.systray
-- @supermodule wibox.widget.base
---------------------------------------------------------------------------
local wbase = require("wibox.widget.base")
@ -224,10 +225,6 @@ function systray.mt:__call(...)
return instance
end
--@DOC_widget_COMMON@
--@DOC_object_COMMON@
return setmetatable(systray, systray.mt)
-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80

View File

@ -1,10 +1,12 @@
---------------------------------------------------------------------------
--
--@DOC_wibox_widget_defaults_textbox_EXAMPLE@
--
-- @author Uli Schlachter
-- @author dodo
-- @copyright 2010, 2011 Uli Schlachter, dodo
-- @widgetmod wibox.widget.textbox
-- @supermodule wibox.widget.base
---------------------------------------------------------------------------
local base = require("wibox.widget.base")
@ -146,7 +148,7 @@ function textbox:get_height_for_width_at_dpi(width, dpi)
end
--- Set the text of the textbox.(with
-- [Pango markup](https://developer.gnome.org/pango/stable/pango-Markup.html)).
-- [Pango markup](https://docs.gtk.org/Pango/pango_markup.html)).
--
-- @tparam string text The text to set. This can contain pango markup (e.g.
-- `<b>bold</b>`). You can use `gears.string.escape` to escape
@ -176,7 +178,7 @@ function textbox:set_markup_silently(text)
end
--- Set the text of the textbox (with
-- [Pango markup](https://developer.gnome.org/pango/stable/pango-Markup.html)).
-- [Pango markup](https://docs.gtk.org/Pango/pango_markup.html)).
--
-- @property markup
-- @tparam string text The text to set. This can contain pango markup (e.g.
@ -220,17 +222,22 @@ function textbox:get_text()
return self._private.layout.text
end
--- Set a textbox ellipsize mode.
--- Set the text ellipsize mode.
--
-- Valid values are:
--
-- * **start**
-- * **middle**
-- * **end**
-- * `"start"`
-- * `"middle"`
-- * `"end"`
-- * `"none"`
--
-- See Pango for additional details:
-- [Layout.set_ellipsize](https://docs.gtk.org/Pango/method.Layout.set_ellipsize.html)
--
--@DOC_wibox_widget_textbox_ellipsize_EXAMPLE@
--
-- @property ellipsize
-- @tparam string mode Where should long lines be shortened? "start", "middle"
-- or "end".
-- @tparam string mode The ellipsize mode.
-- @propemits true false
function textbox:set_ellipsize(mode)
@ -373,9 +380,26 @@ function textbox.mt.__call(_, ...)
return new(...)
end
--@DOC_widget_COMMON@
--@DOC_object_COMMON@
--- Get geometry of text label, as if textbox would be created for it on the screen.
--
-- @tparam string text The text content, pango markup supported.
-- @tparam[opt=nil] integer|screen s The screen on which the textbox would be displayed.
-- @tparam[opt=beautiful.font] string font The font description as string.
-- @treturn table Geometry (width, height) hashtable.
-- @staticfct wibox.widget.textbox.get_markup_geometry
function textbox.get_markup_geometry(text, s, font)
font = font or beautiful.font
local pctx = PangoCairo.font_map_get_default():create_context()
local playout = Pango.Layout.new(pctx)
playout:set_font_description(beautiful.get_font(font))
local dpi_scale = beautiful.xresources.get_dpi(s)
pctx:set_resolution(dpi_scale)
playout:context_changed()
local attr, parsed = Pango.parse_markup(text, -1, 0)
playout.attributes, playout.text = attr, parsed
local _, logical = playout:get_pixel_extents()
return logical
end
return setmetatable(textbox, textbox.mt)

View File

@ -1,9 +1,46 @@
---------------------------------------------------------------------------
--- Text clock widget.
--
-- The `wibox.widget.textclock` widget is part of the Awesome WM's widget
-- system (see @{03-declarative-layout.md}).
--
-- This widget displays a text clock formatted by the
-- [GLib Date Time format](https://developer.gnome.org/glib/stable/glib-GDateTime.html#g-date-time-format)
-- and [GTimeZone](https://developer.gnome.org/glib/stable/glib-GTimeZone.html#g-time-zone-new).
--
-- The `wibox.widget.textclock` inherits from `wibox.widget.textbox`. It means
-- that, once created, the user will receive a derivated instance of
-- `wibox.widget.textbox` associated with a private `gears.timer` to manage
-- timed updates of the displayed clock.
--
-- Use a `wibox.widget.textclock`
-- ---
--
-- @DOC_wibox_widget_defaults_textclock_EXAMPLE@
--
-- Alternatively, you can declare the `textclock` widget using the
-- declarative pattern (Both codes are strictly equivalent):
--
-- @DOC_wibox_widget_declarative-pattern_textclock_EXAMPLE@
--
-- The GLib DateTime format
-- ---
--
-- The time displayed by the textclock widget can be formated by the GLib
-- DateTime format.
--
-- Here is a short list with commonly used format specifiers (extracted from
-- the Glib API references):
--
--@DOC_glib_timedate_format_COMMON@
--
-- You can read more on the GLib DateTime format in the
-- [GLib documentation](https://developer.gnome.org/glib/stable/glib-GDateTime.html#g-date-time-format).
--
-- @author Julien Danjou &lt;julien@danjou.info&gt;
-- @copyright 2009 Julien Danjou
-- @widgetmod wibox.widget.textclock
-- @supermodule wibox.widget.textbox
---------------------------------------------------------------------------
local setmetatable = setmetatable
@ -126,10 +163,6 @@ function textclock.mt:__call(...)
return new(...)
end
--@DOC_widget_COMMON@
--@DOC_object_COMMON@
return setmetatable(textclock, textclock.mt)
-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80

4
luaa.h
View File

@ -31,6 +31,10 @@
#include "common/lualib.h"
#include "common/luaclass.h"
#if !(501 <= LUA_VERSION_NUM && LUA_VERSION_NUM < 504)
#error "Awesome only supports Lua versions 5.1-5.3 and LuaJIT2, please refer to https://awesomewm.org/apidoc/documentation/10-building-and-testing.md.html#Building"
#endif
#define luaA_deprecate(L, repl) \
do { \
luaA_warn(L, "%s: This function is deprecated and will be removed, see %s", \

View File

@ -82,7 +82,7 @@ mousegrabber_handleevent(lua_State *L, int x, int y, uint16_t mask)
* The function is called with one argument:
* a table containing modifiers pointer coordinates.
*
* The list of valid cusrors are:
* The list of valid cursors is:
*
*@DOC_cursor_c_COMMON@
*

Some files were not shown because too many files have changed in this diff Show More