Merge remote-tracking branch 'upstream/master' into tray-child-window

This commit is contained in:
patrick96 2022-07-25 23:36:54 +02:00
commit 4bbb28baaf
No known key found for this signature in database
GPG Key ID: 521E5E03AEBCA1A7
36 changed files with 365 additions and 228 deletions

View File

@ -12,6 +12,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Breaking
- `custom/script`: now doesn't hide failing script if it's output is not changing ([`#2636`](https://github.com/polybar/polybar/issues/2636)). Somewhat similar behaviour can be imitated with `format-fail`, if necessary.
### Build
- Respect `CMAKE_INSTALL_PREFIX` when installing default config ([`#2770`](https://github.com/polybar/polybar/pull/2770))
### Deprecated
- `custom/text`: The `content` setting and all its properties are deprecated in favor of `format` with the same functionality. ([`#2676`](https://github.com/polybar/polybar/pull/2676))
@ -20,6 +23,27 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- `custom/script`: Repeat interval for script failure (`interval-fail`) and `exec-if` (`interval-if`) ([`#943`](https://github.com/polybar/polybar/issues/943), [`#2606`](https://github.com/polybar/polybar/issues/2606), [`#2630`](https://github.com/polybar/polybar/pull/2630))
- `custom/text`: Loads the `format` setting, which supports the `<label>` tag, if the deprecated `content` is not defined ([`#1331`](https://github.com/polybar/polybar/issues/1331), [`#2673`](https://github.com/polybar/polybar/pull/2673), [`#2676`](https://github.com/polybar/polybar/pull/2676))
- Added experimental support for positioning the tray like a module
- `internal/backlight`: `scroll-interval` option ([`#2696`](https://github.com/polybar/polybar/issues/2696), [`#2700`](https://github.com/polybar/polybar/pull/2700))
### Changed
- `internal/fs`: Use `/` as a fallback if no mountpoints are specified ([`#2572`](https://github.com/polybar/polybar/issues/2572), [`#2705`](https://github.com/polybar/polybar/pull/2705))
- `internal/backlight`: Detect backlight if none specified ([`#2572`](https://github.com/polybar/polybar/issues/2572), [`#2728`](https://github.com/polybar/polybar/pull/2728))
### Fixed
- Waiting for double click interval on modules that don't have a double click action ([`#2663`](https://github.com/polybar/polybar/issues/2663), [`#2695`](https://github.com/polybar/polybar/pull/2695))
- config:
- Error reporting for deprecated config values ([`#2724`](https://github.com/polybar/polybar/issues/2724))
- Also monitor include-files for changes when --reload is set ([`#675`](https://github.com/polybar/polybar/issues/675), [`#2759`](https://github.com/polybar/polybar/pull/2759))
## [3.6.3] - 2022-05-04
### Fixed
- `custom/script`: Output clearing when `exec-if` fails ([`#2674`](https://github.com/polybar/polybar/issues/2674))
- `internal/battery`: `poll-interval` not working ([`#2649`](https://github.com/polybar/polybar/issues/2649), [`#2677`](https://github.com/polybar/polybar/pull/2677))
- ipc: Polybar failing to open IPC channel after another user already ran polybar, if `XDG_RUNTIME_DIR` is not set ([`#2683`](https://github.com/polybar/polybar/issues/2683), [`#2684`](https://github.com/polybar/polybar/pull/2684))
- No overlines/underlines being drawn when using offsets ([`#2685`](https://github.com/polybar/polybar/pull/2685))
- Update struts (`_NET_WM_STRUT_PARTIAL`) when hiding the bar ([`#2702`](https://github.com/polybar/polybar/pull/2702))
- `internal/pulseaudio`: Hanging during startup ([`#2707`](https://github.com/polybar/polybar/issues/2707), [`#2709`](https://github.com/polybar/polybar/pull/2709))
- `internal/xworkspaces`: Updates of `_NET_DESKTOP_VIEWPORT` being ignored ([`#2693`](https://github.com/polybar/polybar/issues/2693), [`#2698`](https://github.com/polybar/polybar/pull/2698))
## [3.6.2] - 2022-04-03
### Fixed
@ -184,7 +208,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Fixed
- Empty color values are no longer treated as invalid and no longer produce an error.
[Unreleased]: https://github.com/polybar/polybar/compare/3.6.2...HEAD
[Unreleased]: https://github.com/polybar/polybar/compare/3.6.3...HEAD
[3.6.3]: https://github.com/polybar/polybar/releases/tag/3.6.3
[3.6.2]: https://github.com/polybar/polybar/releases/tag/3.6.2
[3.6.1]: https://github.com/polybar/polybar/releases/tag/3.6.1
[3.6.0]: https://github.com/polybar/polybar/releases/tag/3.6.0

View File

@ -63,7 +63,7 @@ endif()
if(BUILD_CONFIG)
install(FILES ${CMAKE_SOURCE_DIR}/doc/config.ini
DESTINATION /etc/${PROJECT_NAME}
DESTINATION ${CMAKE_INSTALL_SYSCONFDIR}/${PROJECT_NAME}
COMPONENT config)
endif()

View File

@ -1,5 +1,6 @@
<p align="center">
<img src="doc/_static/banner.png" alt="Polybar">
<img src="doc/_static/banner.png#gh-light-mode-only" alt="Polybar">
<img src="doc/_static/banner-dark-mode.png#gh-dark-mode-only" alt="Polybar">
</p>
<p align="center">
@ -40,6 +41,7 @@ for their desktop environment, without the need of having a black belt in shell
* [Sponsors](#sponsors)
* [Backers](#backers)
* [License](#license)
* [Signatures](#signatures)
## Introduction
@ -92,15 +94,17 @@ list of available polybar packages.
If you are using **Debian** (bullseye/11/stable) or later, you can install [polybar](https://tracker.debian.org/pkg/polybar)
using `sudo apt install polybar`. Newer releases of polybar are sometimes provided in the [backports](https://wiki.debian.org/Backports)
repository for stable users, you need to enable [backports](https://wiki.debian.org/Backports) and then install using
`sudo apt -t buster-backports install polybar`.
`sudo apt -t bullseye-backports install polybar`.
If you are using **Ubuntu** 20.10 (Groovy Gorilla) or later, you can install polybar
using `sudo apt install polybar`.
If you are using **Arch Linux**, you can install the AUR package
[polybar](https://aur.archlinux.org/packages/polybar/) to get the latest
version, or [polybar-git](https://aur.archlinux.org/packages/polybar-git/) for
the most up-to-date (unstable) changes.
If you are using **Arch Linux**, you can install
[polybar](https://archlinux.org/packages/community/x86_64/polybar/) to get the
latest stable release using `sudo pacman -S polybar`. The latest unstable
changes are also available in the
[`polybar-git`](https://aur.archlinux.org/packages/polybar-git) package in the
AUR.
If you are using **Manjaro**, you can install [polybar](https://software.manjaro.org/package/polybar) to get the latest stable release using `sudo pacman -S polybar`.
@ -112,14 +116,11 @@ If you are using **Slackware**, polybar is available from the [SlackBuilds](http
If you are using **Source Mage GNU/Linux**, polybar spell is available in test grimoire and can be installed via `cast polybar`.
If you are using **openSUSE Tumbleweed**, polybar is available from the
If you are using **openSUSE Leap** or **openSUSE Tumbleweed**, polybar is available from the
[official
repositories](https://build.opensuse.org/package/show/openSUSE%3AFactory/polybar)
repositories](https://build.opensuse.org/package/show/X11:Utilities/polybar)
and can be installed via `zypper install polybar`.
If you are using **openSUSE Leap**, polybar is available from
[OBS](https://build.opensuse.org/package/show/X11:Utilities/polybar/).
The package is available for openSUSE Leap 15.1 and above.
The package is available for openSUSE Leap 15.3 and above.
If you are using **FreeBSD**, [polybar](https://svnweb.freebsd.org/ports/head/x11/polybar/) can be installed using `pkg install polybar`. Make sure you are using the `latest` package branch.
@ -233,3 +234,9 @@ Polybar accepts donations through [open collective](https://opencollective.com/p
## License
Polybar is licensed under the MIT license. [See LICENSE for more information](https://github.com/polybar/polybar/blob/master/LICENSE).
## Signatures
Release archives and tags are signed by a maintainer using GPG. Currently
everything is signed by [Patrick Ziegler](https://www.patrickziegler.ch/gpg)
with fingerprint `1D5791352D51A228D4DDDBA4521E5E03AEBCA1A7`

View File

@ -1,7 +1,7 @@
# Maintainer: Patrick Ziegler <p.ziegler96@gmail.com>
_pkgname=polybar
pkgname="${_pkgname}-git"
pkgver=3.6.2
pkgver=3.6.3
pkgrel=1
pkgdesc="A fast and easy-to-use status bar"
# aarch64 is not officially supported by polybar, it is only listed here for convenience
@ -14,6 +14,7 @@ depends=("libuv" "cairo" "xcb-util-image" "xcb-util-wm" "xcb-util-xrm"
optdepends=("i3-wm: i3 module support")
makedepends=("cmake" "git" "python" "pkg-config" "python-sphinx"
"python-packaging" "i3-wm")
backup=("etc/polybar/config.ini")
provides=("polybar")
conflicts=("polybar")
source=("${_pkgname}::git+${url}.git")

View File

@ -1,34 +0,0 @@
# Maintainer: Patrick Ziegler <p.ziegler96@gmail.com>
pkgname=polybar
pkgver=3.6.2
pkgrel=1
pkgdesc="A fast and easy-to-use status bar"
# aarch64 is not officially supported by polybar, it is only listed here for convenience
arch=("i686" "x86_64" "aarch64")
url="https://github.com/polybar/polybar"
license=("MIT")
depends=("libuv" "cairo" "xcb-util-image" "xcb-util-wm" "xcb-util-xrm"
"xcb-util-cursor" "alsa-lib" "libpulse" "libmpdclient" "libnl"
"jsoncpp" "curl")
optdepends=("i3-wm: i3 module support")
makedepends=("cmake" "python" "pkg-config" "python-sphinx" "python-packaging" "i3-wm")
conflicts=("polybar-git")
source=(${url}/releases/download/${pkgver}/${pkgname}-${pkgver}.tar.gz)
sha256sums=('73becc942e7d2418bc72bd194f2037a2a86792219fd561b663a8509fd5f547a0')
_dir="${pkgname}-${pkgver}"
prepare() {
mkdir -p "${_dir}/build"
}
build() {
cd "${_dir}/build" || exit 1
# Force cmake to use system python (to detect xcbgen)
cmake -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_BUILD_TYPE=Release -DPYTHON_EXECUTABLE=/usr/bin/python3 ..
cmake --build .
}
package() {
cmake --build "${_dir}/build" --target install -- DESTDIR="${pkgdir}"
install -Dm644 "${_dir}/LICENSE" "${pkgdir}/usr/share/licenses/${pkgname}/LICENSE"
}

BIN
doc/_static/banner-dark-mode.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

View File

@ -114,9 +114,16 @@ as follows:
* A draft PR is opened for the release branch. This PR MUST NOT be merged in
GitHub's interface, it is only here for review, merging happens at the
commandline.
* A `draft release`_ is created in GitHub's release publishing tools
* After approval, the GitHub release publishing tool is used to publish the
release and tag the tip of the release branch (the release commit).
* After approval, a signed git tag is created locally at the tip of the release
branch and pushed:
.. code-block:: shell
git tag -s X.Y.Z <release-branch>
git push --tags
* A `draft release`_ targetting the new tag is created in GitHub's release
publishing tools and published.
* After the tag is created, the release branch is manually merged into
``master``.
Here it is vitally important that the history of the release branch does not
@ -193,8 +200,8 @@ Draft Release
On `GitHub <https://github.com/polybar/polybar/releases/new>`_ a new release
should be drafted.
The release targets the tip of the release branch (the release commit), the
name of the release and the tag is simply the release number.
The release targets the git tag that was just pushed, the name of the release
and the tag is simply the release number.
The content of the release message should contain the changelog copied from
``CHANGELOG.md`` under the heading ``## Changelog``.
@ -205,6 +212,7 @@ The generated list of PRs can be removed.
After-Release Checklist
~~~~~~~~~~~~~~~~~~~~~~~
* Verify the release archive (see `Verify Release`_)
* Make sure all the new functionality is documented on the wiki
* Mark deprecated features appropriately (see `Deprecations`_)
* Remove all unreleased notes from the wiki (not for patch releases)
@ -212,13 +220,8 @@ After-Release Checklist
<https://github.com/polybar/polybar/issues/1971>`_. Mention any dependency
changes and any changes to the build workflow. Also mention any new files are
created by the installation.
* Confirm that the release archive was added to the release.
We have a GitHub action workflow called 'Release Workflow' that on every
release automatically creates a release archive, uploads it to the release,
and adds a 'Download' section to the release body.
If this fails for some reason, it should be triggered manually.
* Create a PR that updates the AUR ``PKGBUILD`` files for the ``polybar`` and
``polybar-git`` packages (push after the release archive is uploaded).
* Create a PR that updates the AUR ``PKGBUILD`` file for the ``polybar-git``
package (push after the release archive is uploaded).
* Close the `GitHub Milestone <https://github.com/polybar/polybar/milestones>`_
for the new release and move open issues (if any) to a later release.
* Activate the version on `Read the Docs
@ -226,6 +229,24 @@ After-Release Checklist
previous versions for the same minor release (e.g. for 3.5.4, deactivate all
other 3.5.X versions).
Verify Release
~~~~~~~~~~~~~~
Confirm that the release archive was added to the release.
We have a GitHub action workflow called 'Release Workflow' that on every
release automatically creates a release archive, uploads it to the release,
and adds a 'Download' section to the release body.
If this fails for some reason, it should be triggered manually.
Afterwards, download the archive, verify its hash, and sign it:
.. code-block:: shell
gpg --armor --detach-sign polybar-X.Y.Z.tar.gz
Finally, upload the generated ``polybar-X.Y.Z.tar.gz.asc`` to the GitHub
release.
Deprecations
~~~~~~~~~~~~

View File

@ -406,20 +406,22 @@ Below you can see an example of a menu module:
[module/apps]
type = custom/menu
label-open = Apps
menu-0-0 = Browsers
menu-0-0-exec = menu-open-1
menu-0-1 = Multimedia
menu-0-1-exec = menu-open-2
menu-1-0 = Firefox
menu-1-0-exec = firefox &
menu-1-0-exec = firefox
menu-1-1 = Chromium
menu-1-1-exec = chromium &
menu-1-1-exec = chromium
menu-2-0 = Gimp
menu-2-0-exec = gimp &
menu-2-0-exec = gimp
menu-2-1 = Scrot
menu-2-1-exec = scrot &
menu-2-1-exec = scrot
This module uses two actions: ``menu-open-1`` and ``menu-open-2``.
These are actions with data, the data specifies which level of the menu should
@ -437,17 +439,19 @@ likely not use ``apps``, but the name of your module.
[module/apps]
type = custom/menu
label-open = Apps
menu-0-0 = Browsers
menu-0-0-exec = #apps.open.1
menu-0-1 = Multimedia
menu-0-1-exec = #apps.open.2
menu-1-0 = Firefox
menu-1-0-exec = firefox &
menu-1-0-exec = firefox
menu-1-1 = Chromium
menu-1-1-exec = chromium &
menu-1-1-exec = chromium
menu-2-0 = Gimp
menu-2-0-exec = gimp &
menu-2-0-exec = gimp
menu-2-1 = Scrot
menu-2-1-exec = scrot &
menu-2-1-exec = scrot

View File

@ -2,6 +2,7 @@
#include <pulse/pulseaudio.h>
#include <atomic>
#include <queue>
#include "common.hpp"
@ -58,6 +59,11 @@ class pulseaudio {
const logger& m_log;
/**
* Has context_state_callback signalled the mainloop during connection.
*/
std::atomic_bool m_state_callback_signal{false};
// used for temporary callback results
int success{0};
pa_cvolume cv{};

View File

@ -1,6 +1,7 @@
#pragma once
#include <cstdlib>
#include <set>
#include "common.hpp"
#include "components/eventloop.hpp"
@ -42,7 +43,7 @@ class bar : public xpp::event::sink<evt::button_press, evt::expose, evt::propert
const bar_settings& settings() const;
void start();
void start(const string& tray_module_name);
void parse(string&& data, bool force = false);
@ -104,7 +105,7 @@ class bar : public xpp::event::sink<evt::button_press, evt::expose, evt::propert
string m_cursor{};
string m_lastinput{};
bool m_dblclicks{false};
std::set<mousebtn> m_dblclicks;
eventloop::TimerHandle& m_leftclick_timer{m_loop.handle<eventloop::TimerHandle>()};
eventloop::TimerHandle& m_middleclick_timer{m_loop.handle<eventloop::TimerHandle>()};

View File

@ -44,6 +44,8 @@ class config {
void set_included(file_list included);
file_list get_included_files() const;
void warn_deprecated(const string& section, const string& key, string replacement) const;
/**
@ -225,6 +227,10 @@ class config {
return value;
} catch (const key_error& err) {
return get<T>(section, newkey, fallback);
} catch (const std::exception& err) {
m_log.err("Invalid value for \"%s.%s\", using fallback key \"%s.%s\" (reason: %s)", section, old, section, newkey,
err.what());
return get<T>(section, newkey, fallback);
}
}

View File

@ -90,12 +90,12 @@ struct line_t {
class config_parser {
public:
config_parser(const logger& logger, string&& file, string&& bar);
config_parser(const logger& logger, string&& file);
/**
* This prevents passing a temporary logger to the constructor because that would be UB, as the temporary would be
* destroyed once the constructor returns.
*/
config_parser(logger&& logger, string&& file, string&& bar) = delete;
config_parser(logger&& logger, string&& file) = delete;
/**
* @brief Performs the parsing of the main config file m_file
@ -105,7 +105,7 @@ class config_parser {
* @throws syntax_error If there was any kind of syntax error
* @throws parser_error If aynthing else went wrong
*/
config::make_type parse();
config::make_type parse(string barname);
protected:
/**
@ -231,11 +231,6 @@ class config_parser {
*/
string m_config;
/**
* Is used to resolve ${root...} references
*/
string m_barname;
/**
* @brief List of all the lines in the config (with included files)
*

View File

@ -55,6 +55,7 @@ class controller : public signal_receiver<SIGN_PRIORITY_CONTROLLER, signals::eve
void signal_handler(int signum);
void conn_cb();
void create_config_watcher(const string& fname);
void confwatch_handler(const char* fname);
void notifier_handler();
void screenshot_handler();
@ -100,6 +101,7 @@ class controller : public signal_receiver<SIGN_PRIORITY_CONTROLLER, signals::eve
eventloop::loop& m_loop;
unique_ptr<bar> m_bar;
bool m_has_ipc;
string m_tray_module_name;
/**
* @brief Async handle to notify the eventloop

View File

@ -77,7 +77,7 @@ class renderer : public renderer_interface,
void fill_overline(rgba color, double x, double w);
void fill_underline(rgba color, double x, double w);
void fill_borders();
void draw_offset(rgba color, double x, double w);
void draw_offset(const tags::context& ctxt, rgba color, double x, double w);
double block_x(alignment a) const;
double block_y(alignment a) const;

View File

@ -107,6 +107,9 @@ namespace signals {
struct tray_pos_change : public detail::value_signal<tray_pos_change, int> {
using base_type::base_type;
};
struct tray_visibility : public detail::value_signal<tray_visibility, bool> {
using base_type::base_type;
};
} // namespace ui_tray
} // namespace signals

View File

@ -37,7 +37,8 @@ namespace signals {
namespace ui_tray {
struct tray_width_change;
struct tray_pos_change;
}
struct tray_visibility;
} // namespace ui_tray
} // namespace signals
POLYBAR_NS_END

View File

@ -1,7 +1,5 @@
#pragma once
#include <uv.h>
#include <set>
#include "common.hpp"
@ -94,6 +92,6 @@ namespace ipc {
void receive_data(string buf);
void receive_eof();
};
} // namespace ipc
} // namespace ipc
POLYBAR_NS_END

View File

@ -48,6 +48,7 @@ namespace modules {
string m_path_backlight;
float m_max_brightness;
bool m_scroll{false};
int m_scroll_interval{5};
bool m_use_actual_brightness{true};
brightness_handle m_val;

View File

@ -39,8 +39,10 @@ class connection;
class background_manager;
class bg_slice;
enum class tray_postition { NONE = 0, LEFT, CENTER, RIGHT, MODULE };
struct tray_settings {
alignment align{alignment::NONE};
tray_postition tray_position{tray_postition::NONE};
bool running{false};
/**
@ -83,16 +85,16 @@ struct tray_settings {
rgba foreground{};
bool transparent{false};
bool detached{false};
bool adaptive{false};
xcb_window_t bar_window;
};
class tray_manager : public xpp::event::sink<evt::expose, evt::visibility_notify, evt::client_message,
evt::configure_request, evt::resize_request, evt::selection_clear, evt::property_notify,
evt::reparent_notify, evt::destroy_notify, evt::map_notify, evt::unmap_notify>,
public signal_receiver<SIGN_PRIORITY_TRAY, signals::ui::visibility_change, signals::ui::dim_window,
signals::ui::update_background, signals::ui_tray::tray_pos_change> {
class tray_manager
: public xpp::event::sink<evt::expose, evt::visibility_notify, evt::client_message, evt::configure_request,
evt::resize_request, evt::selection_clear, evt::property_notify, evt::reparent_notify, evt::destroy_notify,
evt::map_notify, evt::unmap_notify>,
public signal_receiver<SIGN_PRIORITY_TRAY, signals::ui::visibility_change, signals::ui::dim_window,
signals::ui::update_background, signals::ui_tray::tray_pos_change, signals::ui_tray::tray_visibility> {
public:
using make_type = unique_ptr<tray_manager>;
static make_type make(const bar_settings& bar_opts);
@ -104,7 +106,7 @@ class tray_manager : public xpp::event::sink<evt::expose, evt::visibility_notify
const tray_settings settings() const;
void setup();
void setup(const string& tray_module_name);
void activate();
void activate_delayed(chrono::duration<double, std::milli> delay = 1s);
void deactivate(bool clear_selection = true);
@ -144,6 +146,7 @@ class tray_manager : public xpp::event::sink<evt::expose, evt::visibility_notify
void remove_client(xcb_window_t win, bool reconfigure = true);
int mapped_clients() const;
bool has_mapped_clients() const;
bool change_visibility(bool visible);
void handle(const evt::expose& evt) override;
void handle(const evt::visibility_notify& evt) override;
@ -161,6 +164,7 @@ class tray_manager : public xpp::event::sink<evt::expose, evt::visibility_notify
bool on(const signals::ui::dim_window& evt) override;
bool on(const signals::ui::update_background& evt) override;
bool on(const signals::ui_tray::tray_pos_change& evt) override;
bool on(const signals::ui_tray::tray_visibility& evt) override;
private:
connection& m_connection;

View File

@ -24,6 +24,8 @@ pulseaudio::pulseaudio(const logger& logger, string&& sink_name, bool max_volume
pa_context_set_state_callback(m_context, context_state_callback, this);
m_state_callback_signal = false;
if (pa_context_connect(m_context, nullptr, PA_CONTEXT_NOFLAGS, nullptr) < 0) {
pa_context_disconnect(m_context);
pa_context_unref(m_context);
@ -42,7 +44,17 @@ pulseaudio::pulseaudio(const logger& logger, string&& sink_name, bool max_volume
m_log.trace("pulseaudio: started mainloop");
pa_threaded_mainloop_wait(m_mainloop);
/*
* Only wait for signal from the context state callback, if it has not
* already signalled the mainloop since pa_context_connect was called,
* otherwise, we would wait forever.
*
* The while loop ensures spurious wakeups are handled.
*/
while (!m_state_callback_signal) {
pa_threaded_mainloop_wait(m_mainloop);
}
if (pa_context_get_state(m_context) != PA_CONTEXT_READY) {
pa_threaded_mainloop_unlock(m_mainloop);
pa_threaded_mainloop_stop(m_mainloop);
@ -310,6 +322,7 @@ void pulseaudio::context_state_callback(pa_context* context, void* userdata) {
case PA_CONTEXT_READY:
case PA_CONTEXT_TERMINATED:
case PA_CONTEXT_FAILED:
This->m_state_callback_signal = true;
pa_threaded_mainloop_signal(This->m_mainloop, 0);
break;

View File

@ -46,7 +46,10 @@ script_runner::interval script_runner::process() {
}
void script_runner::clear_output() {
set_output("");
auto changed = set_output("");
if (changed) {
m_on_update(m_data);
}
}
void script_runner::stop() {

View File

@ -131,12 +131,7 @@ bar::bar(connection& conn, signal_emitter& emitter, const config& config, const
m_log.info("Loaded monitor %s (%ix%i+%i+%i)", m_opts.monitor->name, m_opts.monitor->w, m_opts.monitor->h,
m_opts.monitor->x, m_opts.monitor->y);
try {
m_opts.override_redirect = m_conf.get<bool>(bs, "dock");
m_conf.warn_deprecated(bs, "dock", "override-redirect");
} catch (const key_error& err) {
m_opts.override_redirect = m_conf.get(bs, "override-redirect", m_opts.override_redirect);
}
m_opts.override_redirect = m_conf.deprecated(bs, "dock", "override-redirect", m_opts.override_redirect);
m_opts.dimvalue = m_conf.get(bs, "dim-value", 1.0);
m_opts.dimvalue = math_util::cap(m_opts.dimvalue, 0.0, 1.0);
@ -393,13 +388,14 @@ void bar::parse(string&& data, bool force) {
auto rect = m_opts.inner_area();
if (m_tray && !m_tray->settings().detached && m_tray->settings().num_mapped_clients > 0 && !m_tray->settings().adaptive) {
auto trayalign = m_tray->settings().align;
if (m_tray && !m_tray->settings().detached && m_tray->settings().num_mapped_clients > 0 &&
m_tray->settings().tray_position != tray_postition::MODULE) {
auto tray_pos = m_tray->settings().tray_position;
auto traywidth = m_tray->settings().win_size.w;
if (trayalign == alignment::LEFT) {
if (tray_pos == tray_postition::LEFT) {
rect.x += traywidth;
rect.width -= traywidth;
} else if (trayalign == alignment::RIGHT) {
} else if (tray_pos == tray_postition::RIGHT) {
rect.width -= traywidth;
}
}
@ -415,19 +411,12 @@ void bar::parse(string&& data, bool force) {
m_renderer->end();
const auto check_dblclicks = [&]() -> bool {
if (m_action_ctxt->has_double_click()) {
return true;
m_dblclicks.clear();
for (auto&& action : m_opts.actions) {
if (static_cast<int>(action.button) >= static_cast<int>(mousebtn::DOUBLE_LEFT)) {
m_dblclicks.insert(action.button);
}
for (auto&& action : m_opts.actions) {
if (static_cast<int>(action.button) >= static_cast<int>(mousebtn::DOUBLE_LEFT)) {
return true;
}
}
return false;
};
m_dblclicks = check_dblclicks();
}
}
/**
@ -440,10 +429,11 @@ void bar::hide() {
try {
m_log.info("Hiding bar window");
m_visible = false;
reconfigure_struts();
m_sig.emit(visibility_change{false});
m_connection.unmap_window_checked(m_opts.x_data.window);
m_connection.flush();
m_visible = false;
} catch (const exception& err) {
m_log.err("Failed to unmap bar window (err=%s", err.what());
}
@ -556,42 +546,46 @@ void bar::reconfigure_pos() {
* Reconfigure window strut values
*/
void bar::reconfigure_struts() {
auto geom = m_connection.get_geometry(m_connection.root());
int h = m_opts.size.h + m_opts.offset.y;
window win{m_connection, m_opts.x_data.window};
if (m_visible) {
auto geom = m_connection.get_geometry(m_connection.root());
int h = m_opts.size.h + m_opts.offset.y;
// Apply user-defined margins
if (m_opts.bottom) {
h += m_opts.strut.top;
} else {
h += m_opts.strut.bottom;
}
h = std::max(h, 0);
int correction = 0;
// Only apply correction if any space is requested
if (h > 0) {
/*
* Strut coordinates have to be relative to root window and not any monitor.
* If any monitor is not aligned at the top or bottom
*/
// Apply user-defined margins
if (m_opts.bottom) {
/*
* For bottom-algined bars, the correction is the number of pixels between
* the root window's bottom edge and the monitor's bottom edge
*/
correction = geom->height - (m_opts.monitor->y + m_opts.monitor->h);
h += m_opts.strut.top;
} else {
// For top-aligned bars, we simply add the monitor's y-position
correction = m_opts.monitor->y;
h += m_opts.strut.bottom;
}
correction = std::max(correction, 0);
}
h = std::max(h, 0);
window win{m_connection, m_opts.x_data.window};
win.reconfigure_struts(m_opts.size.w, h + correction, m_opts.pos.x, m_opts.bottom);
int correction = 0;
// Only apply correction if any space is requested
if (h > 0) {
/*
* Strut coordinates have to be relative to root window and not any monitor.
* If any monitor is not aligned at the top or bottom
*/
if (m_opts.bottom) {
/*
* For bottom-algined bars, the correction is the number of pixels between
* the root window's bottom edge and the monitor's bottom edge
*/
correction = geom->height - (m_opts.monitor->y + m_opts.monitor->h);
} else {
// For top-aligned bars, we simply add the monitor's y-position
correction = m_opts.monitor->y;
}
correction = std::max(correction, 0);
}
win.reconfigure_struts(m_opts.size.w, h + correction, m_opts.pos.x, m_opts.bottom);
} else {
// Set struts to 0 for invisible bars
win.reconfigure_struts(0, 0, 0, m_opts.bottom);
}
}
/**
@ -634,6 +628,8 @@ void bar::broadcast_visibility() {
}
void bar::map_window() {
m_visible = true;
/**
* First reconfigures the window so that WMs that discard some information
* when unmapping have the correct window properties (geometry etc).
@ -648,8 +644,6 @@ void bar::map_window() {
* mapping. Additionally updating the window position after mapping seems to fix that.
*/
reconfigure_pos();
m_visible = true;
}
void bar::trigger_click(mousebtn btn, int pos) {
@ -818,9 +812,12 @@ void bar::handle(const evt::button_press& evt) {
}
};
mousebtn double_btn = mousebtn_get_double(btn);
bool has_dblclick = m_dblclicks.count(double_btn) || m_action_ctxt->has_action(double_btn, pos) != tags::NO_ACTION;
// If there are no double click handlers defined we can
// just by-pass the click timer handling
if (!m_dblclicks) {
if (!has_dblclick) {
trigger_click(btn, pos);
} else if (btn == mousebtn::LEFT) {
check_double(m_leftclick_timer, btn, pos);
@ -876,7 +873,7 @@ void bar::handle(const evt::configure_notify&) {
m_sig.emit(signals::ui::update_geometry{});
}
void bar::start() {
void bar::start(const string& tray_module_name) {
m_log.trace("bar: Create renderer");
m_renderer = renderer::make(m_opts, *m_action_ctxt, *m_tray);
@ -907,7 +904,7 @@ void bar::start() {
m_renderer->end();
m_log.trace("bar: Setup tray manager");
m_tray->setup();
m_tray->setup(tray_module_name);
broadcast_visibility();
}

View File

@ -59,6 +59,10 @@ void config::set_included(file_list included) {
m_included = move(included);
}
file_list config::get_included_files() const {
return m_included;
}
void config::ignore_key(const string& section, const string& key) const {
if (has(section, key)) {
m_log.warn(

View File

@ -10,10 +10,10 @@
POLYBAR_NS
config_parser::config_parser(const logger& logger, string&& file, string&& bar)
: m_log(logger), m_config(file_util::expand(file)), m_barname(move(bar)) {}
config_parser::config_parser(const logger& logger, string&& file)
: m_log(logger), m_config(file_util::expand(file)) {}
config::make_type config_parser::parse() {
config::make_type config_parser::parse(string barname) {
m_log.notice("Parsing config file: %s", m_config);
parse_file(m_config, {});
@ -21,21 +21,21 @@ config::make_type config_parser::parse() {
sectionmap_t sections = create_sectionmap();
vector<string> bars = get_bars(sections);
if (m_barname.empty()) {
if (barname.empty()) {
if (bars.size() == 1) {
m_barname = bars[0];
barname = bars[0];
} else if (bars.empty()) {
throw application_error("The config file contains no bar.");
} else {
throw application_error("The config file contains multiple bars, but no bar name was given. Available bars: " +
string_util::join(bars, ", "));
}
} else if (sections.find("bar/" + m_barname) == sections.end()) {
} else if (sections.find("bar/" + barname) == sections.end()) {
if (bars.empty()) {
throw application_error("Undefined bar: " + m_barname + ". The config file contains no bar.");
throw application_error("Undefined bar: " + barname + ". The config file contains no bar.");
} else {
throw application_error(
"Undefined bar: " + m_barname + ". Available bars: " + string_util::join(get_bars(sections), ", "));
"Undefined bar: " + barname + ". Available bars: " + string_util::join(get_bars(sections), ", "));
}
}
@ -45,7 +45,7 @@ config::make_type config_parser::parse() {
* second element onwards for the included list
*/
file_list included(m_files.begin() + 1, m_files.end());
config::make_type result = config::make(m_config, m_barname);
config::make_type result = config::make(m_config, barname);
// Cast to non-const to set sections, included and xrm
config& m_conf = const_cast<config&>(result);

View File

@ -197,6 +197,16 @@ void controller::signal_handler(int signum) {
stop(signum == SIGUSR1);
}
void controller::create_config_watcher(const string& filename) {
auto& fs_event_handler = m_loop.handle<FSEventHandle>();
fs_event_handler.start(
filename, 0, [this](const auto& e) { confwatch_handler(e.path); },
[this, &fs_event_handler](const auto& e) {
m_log.err("libuv error while watching included file for changes: %s", uv_strerror(e.status));
fs_event_handler.close();
});
}
void controller::confwatch_handler(const char* filename) {
m_log.notice("Watched config file changed %s", filename);
stop(true);
@ -239,7 +249,7 @@ void controller::read_events(bool confwatch) {
m_log.info("Entering event loop (thread-id=%lu)", this_thread::get_id());
if (!m_writeback) {
m_bar->start();
m_bar->start(m_tray_module_name);
}
auto& poll_handle = m_loop.handle<PollHandle>(m_connection.get_file_descriptor());
@ -256,13 +266,11 @@ void controller::read_events(bool confwatch) {
}
if (confwatch) {
auto& fs_event_handle = m_loop.handle<FSEventHandle>();
fs_event_handle.start(
m_conf.filepath(), 0, [this](const auto& e) { confwatch_handler(e.path); },
[this, &fs_event_handle](const auto& e) {
m_log.err("libuv error while watching config file for changes: %s", uv_strerror(e.status));
fs_event_handle.close();
});
create_config_watcher(m_conf.filepath());
// also watch the include-files for changes
for (auto& module_path : m_conf.get_included_files()) {
create_config_watcher(module_path);
}
}
if (!m_snapshot_dst.empty()) {
@ -595,6 +603,13 @@ size_t controller::setup_modules(alignment align) {
try {
auto type = m_conf.get("module/" + module_name, "type");
if (type == tray_module::TYPE) {
if (!m_tray_module_name.empty()) {
throw module_error("Multiple trays defined. Using tray `" + m_tray_module_name + "`");
}
m_tray_module_name = module_name;
}
if (type == ipc_module::TYPE && !m_has_ipc) {
throw application_error("Inter-process messaging needs to be enabled");
}

View File

@ -748,8 +748,12 @@ void renderer::render_text(const tags::context& ctxt, const string&& contents) {
}
}
void renderer::draw_offset(rgba color, double x, double w) {
if (w > 0 && color != m_bar.background) {
void renderer::draw_offset(const tags::context& ctxt, rgba color, double x, double w) {
if (w <= 0) {
return;
}
if (color != m_bar.background) {
m_log.trace_x("renderer: offset(x=%f, w=%f)", x, w);
m_context->save();
*m_context << m_comp_bg;
@ -759,6 +763,14 @@ void renderer::draw_offset(rgba color, double x, double w) {
m_context->fill();
m_context->restore();
}
if (ctxt.has_underline()) {
fill_underline(ctxt.get_ul(), x, w);
}
if (ctxt.has_overline()) {
fill_overline(ctxt.get_ol(), x, w);
}
}
void renderer::render_offset(const tags::context& ctxt, const extent_val offset) {
@ -767,7 +779,7 @@ void renderer::render_offset(const tags::context& ctxt, const extent_val offset)
int offset_width = units_utils::extent_to_pixel(offset, m_bar.dpi_x);
rgba bg = ctxt.get_bg();
draw_offset(bg, m_blocks[m_align].x, offset_width);
draw_offset(ctxt, bg, m_blocks[m_align].x, offset_width);
increase_x(offset_width);
}
@ -835,6 +847,9 @@ void renderer::apply_tray_position(const tags::context& context) {
int absolute_x = static_cast<int>(
block_x(context.get_relative_tray_position().first) + context.get_relative_tray_position().second);
m_sig.emit(signals::ui_tray::tray_pos_change{absolute_x});
m_sig.emit(signals::ui_tray::tray_visibility{true});
} else {
m_sig.emit(signals::ui_tray::tray_visibility{false});
}
}

View File

@ -1,6 +1,7 @@
#include "ipc/util.hpp"
#include <sys/stat.h>
#include <unistd.h>
#include "errors.hpp"
#include "utils/env.hpp"
@ -12,8 +13,14 @@ POLYBAR_NS
namespace ipc {
static constexpr auto SUFFIX = ".sock";
static constexpr auto XDG_RUNTIME_DIR = "XDG_RUNTIME_DIR";
string get_runtime_path() {
if (env_util::has(XDG_RUNTIME_DIR)) {
return env_util::get("XDG_RUNTIME_DIR") + "/polybar";
} else {
return "/tmp/polybar-" + to_string(getuid());
}
return env_util::get("XDG_RUNTIME_DIR", "/tmp") + "/polybar";
}
@ -59,6 +66,6 @@ namespace ipc {
return -1;
}
}
} // namespace ipc
} // namespace ipc
POLYBAR_NS_END

View File

@ -127,8 +127,8 @@ int main(int argc, char** argv) {
barname = cli->get(0);
}
config_parser parser{logger, move(confpath), move(barname)};
config::make_type conf = parser.parse();
config_parser parser{logger, move(confpath)};
config::make_type conf = parser.parse(move(barname));
//==================================================
// Dump requested data

View File

@ -27,12 +27,33 @@ namespace modules {
: inotify_module<backlight_module>(bar, move(name_)) {
m_router->register_action(EVENT_DEC, [this]() { action_dec(); });
m_router->register_action(EVENT_INC, [this]() { action_inc(); });
auto card = m_conf.get(name(), "card", ""s);
if (card.empty()) {
vector<string> backlight_card_names = file_util::list_files(string_util::replace(PATH_BACKLIGHT, "%card%", ""));
backlight_card_names.erase(std::remove_if(backlight_card_names.begin(), backlight_card_names.end(),
[&](const string& card) -> bool {
auto dir = string_util::replace(PATH_BACKLIGHT, "%card%", card);
return !(file_util::is_file(dir + "/actual_brightness") &&
file_util::is_file(dir + "/brightness") &&
file_util::is_file(dir + "/max_brightness"));
}),
backlight_card_names.end());
auto card = m_conf.get(name(), "card");
if (backlight_card_names.empty()) {
throw module_error("no viable default backlight found");
}
card = backlight_card_names.at(0);
if (backlight_card_names.size() > 1) {
m_log.warn("%s: multiple backlights found, using %s", name(), card);
} else {
m_log.info("%s: no backlight specified, using `%s`", name(), card);
}
}
// Get flag to check if we should add scroll handlers for changing value
m_scroll = m_conf.get(name(), "enable-scroll", m_scroll);
m_scroll_interval = m_conf.get(name(), "scroll-interval", m_scroll_interval);
// Add formats and elements
m_formatter->add(DEFAULT_FORMAT, TAG_LABEL, {TAG_LABEL, TAG_BAR, TAG_RAMP});
@ -120,11 +141,11 @@ namespace modules {
}
void backlight_module::action_inc() {
change_value(5);
change_value(m_scroll_interval);
}
void backlight_module::action_dec() {
change_value(-5);
change_value(-m_scroll_interval);
}
void backlight_module::change_value(int value_mod) {

View File

@ -199,7 +199,7 @@ namespace modules {
if (chrono::duration_cast<decltype(m_interval)>(now - m_lastpoll) > m_interval) {
m_lastpoll = now;
m_log.info("%s: Polling values (inotify fallback)", name());
read(*m_capacity_reader);
on_event({});
}
}

View File

@ -27,7 +27,11 @@ namespace modules {
* setting up required components
*/
fs_module::fs_module(const bar_settings& bar, string name_) : timer_module<fs_module>(bar, move(name_)) {
m_mountpoints = m_conf.get_list(name(), "mount");
m_mountpoints = m_conf.get_list(name(), "mount", {});
if (m_mountpoints.empty()) {
m_log.info("%s: No mountpoints specified, using fallback \"/\"", name());
m_mountpoints.emplace_back("/");
}
m_remove_unmounted = m_conf.get(name(), "remove-unmounted", m_remove_unmounted);
m_perc_used_warn = m_conf.get(name(), "warn-percentage", 90);
m_fixed = m_conf.get(name(), "fixed-values", m_fixed);

View File

@ -108,7 +108,8 @@ namespace modules {
if (evt->atom == m_ewmh->_NET_CLIENT_LIST || evt->atom == m_ewmh->_NET_WM_DESKTOP) {
rebuild_clientlist();
rebuild_desktop_states();
} else if (evt->atom == m_ewmh->_NET_DESKTOP_NAMES || evt->atom == m_ewmh->_NET_NUMBER_OF_DESKTOPS) {
} else if (evt->atom == m_ewmh->_NET_DESKTOP_NAMES || evt->atom == m_ewmh->_NET_NUMBER_OF_DESKTOPS ||
evt->atom == m_ewmh->_NET_DESKTOP_VIEWPORT) {
m_desktop_names = get_desktop_names();
rebuild_desktops();
rebuild_clientlist();

View File

@ -67,25 +67,26 @@ tray_manager::~tray_manager() {
deactivate();
}
void tray_manager::setup() {
void tray_manager::setup(const string& tray_module_name) {
const config& conf = config::make();
auto bs = conf.section();
string position;
string position = conf.get(bs, "tray-position", "none"s);
try {
position = conf.get(bs, "tray-position");
} catch (const key_error& err) {
return m_log.info("Disabling tray manager (reason: missing `tray-position`)");
if (!position.empty() && position != "none" && !tray_module_name.empty()) {
m_log.warn(
"The tray position is manually defined (`tray-position`) and also set by the tray module (%s). `tray-position` "
"will be ignored",
tray_module_name);
}
if (position == "left") {
m_opts.align = alignment::LEFT;
if (!tray_module_name.empty()) {
m_opts.tray_position = tray_postition::MODULE;
} else if (position == "left") {
m_opts.tray_position = tray_postition::LEFT;
} else if (position == "right") {
m_opts.align = alignment::RIGHT;
m_opts.tray_position = tray_postition::RIGHT;
} else if (position == "center") {
m_opts.align = alignment::CENTER;
} else if (position == "adaptive") {
m_opts.adaptive = true;
m_opts.tray_position = tray_postition::CENTER;
} else if (position != "none") {
return m_log.err("Disabling tray manager (reason: Invalid position \"" + position + "\")");
} else {
@ -114,12 +115,12 @@ void tray_manager::setup() {
m_opts.win_size.h *= scale;
m_opts.pos.x = inner_area.x + [&]() -> int {
switch (m_opts.align) {
case alignment::LEFT:
switch (m_opts.tray_position) {
case tray_postition::LEFT:
return 0;
case alignment::CENTER:
case tray_postition::CENTER:
return inner_area.width / 2 - m_opts.client_size.w / 2;
case alignment::RIGHT:
case tray_postition::RIGHT:
return inner_area.width;
default:
return 0;
@ -755,9 +756,9 @@ void tray_manager::process_docking_request(xcb_window_t win) {
*/
int tray_manager::calculate_x(unsigned int width) const {
auto x = m_opts.pos.x;
if (m_opts.align == alignment::RIGHT) {
if (m_opts.tray_position == tray_postition::RIGHT) {
x -= ((m_opts.client_size.w + m_opts.spacing) * m_clients.size() + m_opts.spacing);
} else if (m_opts.align == alignment::CENTER) {
} else if (m_opts.tray_position == tray_postition::CENTER) {
x -= (width / 2) - (m_opts.client_size.w / 2);
}
return x;
@ -849,6 +850,29 @@ bool tray_manager::has_mapped_clients() const {
return std::find_if(m_clients.begin(), m_clients.end(), [](const auto& c) { return c.mapped(); }) != m_clients.end();
}
bool tray_manager::change_visibility(bool visible) {
bool has_clients = has_mapped_clients();
m_log.trace("tray: visibility_change (state=%i, activated=%i, mapped=%i, hidden=%i)", visible,
static_cast<bool>(m_activated), static_cast<bool>(m_mapped), static_cast<bool>(m_hidden));
m_hidden = !visible;
if (!m_activated) {
return false;
} else if (!m_hidden && !m_mapped && has_clients) {
m_connection.map_window(m_tray);
} else if ((!has_clients || m_hidden) && m_mapped) {
m_connection.unmap_window(m_tray);
} else if (m_mapped && !m_hidden && has_clients) {
redraw_window();
}
m_connection.flush();
return true;
}
/**
* Event callback : XCB_EXPOSE
*/
@ -1073,27 +1097,7 @@ void tray_manager::handle(const evt::unmap_notify& evt) {
* toggle the tray window whenever the visibility of the bar window changes.
*/
bool tray_manager::on(const signals::ui::visibility_change& evt) {
bool visible{evt.cast()};
bool has_clients = has_mapped_clients();
m_log.trace("tray: visibility_change (state=%i, activated=%i, mapped=%i, hidden=%i)", visible,
static_cast<bool>(m_activated), static_cast<bool>(m_mapped), static_cast<bool>(m_hidden));
m_hidden = !visible;
if (!m_activated) {
return false;
} else if (!m_hidden && !m_mapped && has_clients) {
m_connection.map_window(m_tray);
} else if ((!has_clients || m_hidden) && m_mapped) {
m_connection.unmap_window(m_tray);
} else if (m_mapped && !m_hidden && has_clients) {
redraw_window();
}
m_connection.flush();
return true;
return change_visibility(evt.cast());
}
bool tray_manager::on(const signals::ui::dim_window& evt) {
@ -1112,10 +1116,20 @@ bool tray_manager::on(const signals::ui::update_background&) {
}
bool tray_manager::on(const signals::ui_tray::tray_pos_change& evt) {
m_opts.pos.x = m_bar_opts.inner_area(true).x + evt.cast();
m_opts.pos.x =
m_bar_opts.inner_area(false).x + std::max(0, std::min(evt.cast(), (int)(m_bar_opts.size.w - calculate_w())));
reconfigure_window();
return true;
}
bool tray_manager::on(const signals::ui_tray::tray_visibility& evt) {
if (evt.cast() == m_hidden && m_opts.tray_position == tray_postition::MODULE) {
return change_visibility(evt.cast());
} else {
return true;
}
}
POLYBAR_NS_END

View File

@ -57,14 +57,16 @@ window window::reconfigure_pos(short int x, short int y) {
window window::reconfigure_struts(uint32_t w, uint32_t strut, uint32_t x, bool bottom) {
std::array<uint32_t, 12> values{};
uint32_t end_x = std::max<int>(0, x + w - 1);
if (bottom) {
values[to_integral(strut::BOTTOM)] = strut;
values[to_integral(strut::BOTTOM_START_X)] = x;
values[to_integral(strut::BOTTOM_END_X)] = x + w - 1;
values[to_integral(strut::BOTTOM_END_X)] = end_x;
} else {
values[to_integral(strut::TOP)] = strut;
values[to_integral(strut::TOP_START_X)] = x;
values[to_integral(strut::TOP_END_X)] = x + w - 1;
values[to_integral(strut::TOP_END_X)] = end_x;
}
connection().change_property_checked(

View File

@ -13,8 +13,8 @@ class TestableConfigParser : public config_parser {
using config_parser::config_parser;
public:
TestableConfigParser(const logger& logger, string&& file, string&& bar)
: config_parser(logger, move(file), move(bar)) {
TestableConfigParser(const logger& logger, string&& file)
: config_parser(logger, move(file)) {
m_files.push_back("test_config");
}
@ -43,7 +43,7 @@ class TestableConfigParser : public config_parser {
class ConfigParser : public ::testing::Test {
protected:
const logger l = logger(loglevel::NONE);
unique_ptr<TestableConfigParser> parser = make_unique<TestableConfigParser>(l, "/dev/zero", "TEST");
unique_ptr<TestableConfigParser> parser = make_unique<TestableConfigParser>(l, "/dev/zero");
};
// ParseLineTest {{{

View File

@ -1,4 +1,4 @@
# Polybar version information
# Update this on every release
# This is used to create the version string if a git repo is not available
3.6.2
3.6.3