Compare commits

..

9 commits
v0.0.1 ... main

13 changed files with 158 additions and 123 deletions

2
.gitignore vendored
View file

@ -1,4 +1,4 @@
data/testing_config.yml data/testing_config.yml
easyconf-lemonbar/__pycache__ easyconf_lemonbar/__pycache__
pyrightconfig.json pyrightconfig.json
poetry.lock poetry.lock

View file

@ -24,6 +24,15 @@ cd easyconf-lemonbar
sudo sh install.sh sudo sh install.sh
``` ```
To uninstall from your system:
```sh
sudo sh uninstall.sh
```
This will leave your config file in place, should you ever reinstall eclb.
**To update: uninstall, then follow the installation instructions again.**
## Configuration and Usage ## Configuration and Usage
### Config File ### Config File
After installing, you will need to copy the example config file to your After installing, you will need to copy the example config file to your
@ -49,18 +58,22 @@ easyconf-lemonbar process. First, in the module configuration, specify which
signal to listen for, e.g. signal to listen for, e.g.
```yaml ```yaml
...
# Display a random number, and only update when we recieve the RTMIN+1 signal. # Display a random number, and only update when we recieve the RTMIN+1 signal.
- name: "random" random:
command: "python -c 'from random import randrange; print(randrange(1000))'" command: "python -c 'from random import randrange; print(randrange(1000))'"
refresh: 0 refresh: 0
signal: 1 signal: 1
format: format:
align: left align: left
...
``` ```
In the example above, the refresh is `0`. This means that the module *will never In the example above, the refresh is `0`. This means that the module *will never
refresh on its own*; it will wait for the RTMIN+1 signal. You may also configure refresh on its own*; it will wait for the RTMIN+1 signal. You may also configure
your module to both refresh on a time interval AND listen for a signal. your module to both refresh on a time interval AND listen for a signal. Also
note: multiple modules may listen for the same signal. They will all be
refreshed when the signal is recieved.
Unfortunately, to send the signal, you cant just `pkill easyconf-lemonbar`. You Unfortunately, to send the signal, you cant just `pkill easyconf-lemonbar`. You
have to read the PID from eclb's pidfile. I would recommend creating an alias in have to read the PID from eclb's pidfile. I would recommend creating an alias in

View file

@ -2,8 +2,8 @@
# where indicated. # where indicated.
bar: bar:
# Global configuration options. These are mostly flags passed to lemonbar at # Global configuration options. These are mostly flags passed to lemonbar at
# runtime. # runtime. These are all optional.
config: #config:
# Define a custom size of the bar. By default, this is different for every # Define a custom size of the bar. By default, this is different for every
# screen, so the format is shown below. # screen, so the format is shown below.
#geometry: "widthxheight+x+y" #geometry: "widthxheight+x+y"
@ -52,64 +52,67 @@ bar:
# Text to be placed on both ends of the bar, before and after all text. # Text to be placed on both ends of the bar, before and after all text.
#margin: "" #margin: ""
# Here is where you can define a list your individual modules. You can define # Modules are organized by monitor number, starting at 0 and increasing to 9.
# as many modules as you want. Some fields are required; these will be monitor:
# indicated. If a field doesn't say its required, you can safely omit it in 0:
# your own configuration. A "bare minimum" configuration is shown below for # Within each monitor is where you can define a your individual modules.
# your convience. # You can define as many modules as you want. Some fields are required;
modules: # these will be indicated. If a field doesn't say its required, you can
# This is an example module. You can name it whatever you want, it doesn't # safely omit it in your own configuration. A "bare minimum" configuration
# matter. Notice the `-` before the name. Each module must be a list item! # is shown below for your convience.
# THIS IS A REQUIRED FIELD. modules:
#- name: "time" # The top field is the name of the module. This can be whatever you
# # This command is run by `sh -c`, and whatever is sent to stdout is the # want, it doesn't matter. Because this is an example module, we will
# # text that will be displayed for this module. `date` here is used as an # call it 'example.'
# # example. THIS IS A REQUIRED FIELD. #example:
# command: "date" # # This command is run by `sh -c`, and whatever is sent to stdout is the
# # How often (in milliseconds) to re-run the command and display the # # text that will be displayed for this module. `date` here is used as an
# # updated text. If this value is 0, then the module will not be # # example. THIS IS A REQUIRED FIELD.
# # refreshed. THIS IS A REQUIRED FIELD. # command: "date"
# refresh: 1000 # # How often (in milliseconds) to re-run the command and display the
# # Prefix to be printed before the text of the command. # # updated text. If this value is 0, then the module will not be
# prefix: "DATE: " # # refreshed. THIS IS A REQUIRED FIELD.
# # Linux real-time signal that, when received, will refresh the module # refresh: 1000
# # instantly. See the README for more info. # # Prefix to be printed before the text of the command.
# signal: 0 # prefix: "DATE: "
# # Formatting options. These will only affect the text and padding of this # # Linux real-time signal that, when received, will refresh the module
# # module. # # instantly. See the README for more info.
# format: # signal: 0
# # Where the text should be placed on the bar. `left`, `right`, or # # Formatting options. These will only affect the text and padding of this
# # `center`. THIS IS A REQUIRED FIELD. # # module.
# align: left # format:
# # Background color of the bar. # # Where the text should be placed on the bar. `left`, `right`, or
# bg_color: "#000" # # `center`. THIS IS A REQUIRED FIELD.
# # Text color # align: left
# fg_color: "#FFF" # # Background color of the bar.
# # INDEX of the font for this module to be displayed in (see above). # bg_color: "#000"
# font: 1 # # Text color
# # Horizontal offset of the module text in pixels (can be negative). # fg_color: "#FFF"
# offset: 0 # # INDEX of the font for this module to be displayed in (see above).
# # Set an underline or overline # font: 1
# line: # # Horizontal offset of the module text in pixels (can be negative).
# # Self-explanatory. `underline` or `overline`. IF you set a line, then # offset: 0
# # this is a required field. # # Set an underline or overline
# type: underline # line:
# # Set a custom color for the line. # # Self-explanatory. `underline` or `overline`. IF you set a line, then
# color: "#000" # # this is a required field.
# # Make this module act like a button. Both of these fields are required # type: underline
# # if you are making a button. You may also define multiple buttons with # # Set a custom color for the line.
# # different activators for a single module. # color: "#000"
# button: # # Make this module act like a button. Both of these fields are required
# # What mouse click you have to make to activate the button. Can be # # if you are making a button. You may also define multiple buttons with
# # `left`, `center`, or `right`. You can also set it to activate when # # different activators for a single module.
# # you scroll up or down with `scrup` and `scrdown.` # button:
# activator: left # # What mouse click you have to make to activate the button. Can be
# # Command to be executed upon activation. Will be run with `sh -c`. # # `left`, `center`, or `right`. You can also set it to activate when
# command: "echo 'hello' > ~/file.txt" # # you scroll up or down with `scrup` and `scrdown.`
# activator: left
# # Command to be executed upon activation. Will be run with `sh -c`.
# command: "echo 'hello' > ~/file.txt"
# Here is a simpler example to more clearly show only the required fields. # Here is a simpler example to more clearly show only the required fields.
- name: "bare_minimum" bare_minimum:
command: "whoami" command: "whoami"
refresh: 1000 refresh: 1000
format: format:
align: right align: right

View file

@ -1,20 +1,20 @@
class Bar: class Bar:
def __init__(self, seperator, margin): def __init__(self, seperator, margin):
self.left = [] self.monitors = {}
self.center = []
self.right = []
self.seperator = seperator self.seperator = seperator
self.margin = margin self.margin = margin
def add_module(self, module): def add_monitor(self, monitor):
self.monitors[monitor] = { "left": [], "center": [], "right": [] }
def add_module(self, monitor, module):
match module.alignment: match module.alignment:
case "left": case "left":
self.left.append(module) self.monitors[monitor]["left"].append(module)
case "center": case "center":
self.center.append(module) self.monitors[monitor]["center"].append(module)
case "right": case "right":
self.right.append(module) self.monitors[monitor]["right"].append(module)
def __print_alignment_group(self, alignment_group): def __print_alignment_group(self, alignment_group):
for index, module in enumerate(alignment_group): for index, module in enumerate(alignment_group):
@ -26,13 +26,17 @@ class Bar:
break break
def print(self): def print(self):
print("%{l}" + self.margin, end='') for monitor in self.monitors:
self.__print_alignment_group(self.left) print("%{S" + str(monitor) + "}", end='')
print("%{l}" + self.margin, end='')
self.__print_alignment_group(self.monitors[monitor]["left"])
print("%{c}", end='') print("%{c}", end='')
self.__print_alignment_group(self.center) self.__print_alignment_group(self.monitors[monitor]["center"])
print("%{r}", end='') print("%{r}", end='')
self.__print_alignment_group(self.right) self.__print_alignment_group(self.monitors[monitor]["right"])
print(self.margin, flush=True) print(self.margin, end='')
print(flush=True)

View file

@ -6,8 +6,9 @@ class SignalRouter:
self.signal_thread_dictionary = {} self.signal_thread_dictionary = {}
def __route_signal(self, signal, frame): def __route_signal(self, signal, frame):
pthread_kill(self.signal_thread_dictionary[signal], signal) for thread_ident in self.signal_thread_dictionary[signal]:
pthread_kill(thread_ident, signal)
def register_signal_thread(self, signal, thread_ident): def register_signal_thread(self, signal, thread_ident):
self.signal_thread_dictionary[signal] = thread_ident self.signal_thread_dictionary.setdefault(signal, []).append(thread_ident)
sigsignal(signal, self.__route_signal) sigsignal(signal, self.__route_signal)

View file

@ -1,4 +1,7 @@
from parse_config_file import get_bar_config_and_module_config_list from os import getenv
from os.path import exists
from easyconf_lemonbar.parse_config_file import get_bar_config_and_monitor_list
def generate_flags(bar_config): def generate_flags(bar_config):
flags = "" flags = ""
@ -26,6 +29,11 @@ def generate_flags(bar_config):
flags += (" -U '" + bar_config[option] + "'") flags += (" -U '" + bar_config[option] + "'")
return flags.strip() return flags.strip()
bar_config, module_config_list = get_bar_config_and_module_config_list("/home/noah/src/easyconf-lemonbar/data/testing_config.yml") if exists("./data/testing_config.yml"):
config_file = "./data/testing_config.yml"
else:
config_file = str(getenv("HOME")) + "/.config/easyconf-lemonbar.yml"
bar_config, monitor_list = get_bar_config_and_monitor_list(config_file)
print(generate_flags(bar_config)) print(generate_flags(bar_config))

View file

@ -5,7 +5,7 @@ from os.path import exists
from SignalRouter import SignalRouter from SignalRouter import SignalRouter
from Bar import Bar from Bar import Bar
from Module import Module from Module import Module
from parse_config_file import get_bar_config_and_module_config_list from parse_config_file import get_bar_config_and_monitor_list
def sigterm_handler(signal, frame): def sigterm_handler(signal, frame):
delete_pidfile(pidfile_name()) delete_pidfile(pidfile_name())
@ -17,7 +17,7 @@ def main():
else: else:
config_file = str(getenv("HOME")) + "/.config/easyconf-lemonbar.yml" config_file = str(getenv("HOME")) + "/.config/easyconf-lemonbar.yml"
bar_config, module_config_list = get_bar_config_and_module_config_list(config_file) bar_config, monitor_list = get_bar_config_and_monitor_list(config_file)
padding = bar_config["padding"] padding = bar_config["padding"]
seperator = bar_config["seperator"] seperator = bar_config["seperator"]
margin = bar_config["margin"] margin = bar_config["margin"]
@ -26,12 +26,15 @@ def main():
bar = Bar(seperator, margin) bar = Bar(seperator, margin)
for module_config in module_config_list: for monitor in monitor_list:
module = Module(module_config, padding, bar) bar.add_monitor(monitor)
bar.add_module(module) module_config_list = monitor_list[monitor]["modules"]
module.start_thread() for module_config in module_config_list.values():
if module.signal != 0: module = Module(module_config, padding, bar)
signal_router.register_signal_thread(module.signal, module.ident) bar.add_module(monitor, module)
module.start_thread()
if module.signal != 0:
signal_router.register_signal_thread(module.signal, module.ident)
create_pidfile(pidfile_name()) create_pidfile(pidfile_name())

View file

@ -2,7 +2,7 @@ from yaml import safe_load
from schema import And, Optional, Regex, Schema, SchemaError, Or from schema import And, Optional, Regex, Schema, SchemaError, Or
from signal import SIGRTMIN, SIGRTMAX from signal import SIGRTMIN, SIGRTMAX
def get_bar_config_and_module_config_list(filename): def get_bar_config_and_monitor_list(filename):
config_schema = Schema({ config_schema = Schema({
"bar": { "bar": {
Optional("config"): { Optional("config"): {
@ -20,32 +20,35 @@ def get_bar_config_and_module_config_list(filename):
Optional("seperator", default="|"): str, Optional("seperator", default="|"): str,
Optional("margin", default=""): str Optional("margin", default=""): str
}, },
"modules": [ "monitor": {
{ And(int, lambda n: 0 <= n <= 9): {
"name": str, "modules": {
"command": str, str: {
"refresh": And(int, lambda n : n >= 0), "command": str,
Optional("prefix"): str, "refresh": And(int, lambda n : n >= 0),
Optional("signal"): And(int, lambda n : SIGRTMIN <= SIGRTMIN+n <= SIGRTMAX), Optional("prefix"): str,
"format": { Optional("signal"): And(int, lambda n : SIGRTMIN <= SIGRTMIN+n <= SIGRTMAX),
"align": Or("left", "center", "right"), "format": {
Optional("bg_color"): Regex(r'^#[0-9a-fA-F]{3}$|^#[0-9a-fA-F]{6}$|^#[0-9a-fA-F]{8}$'), "align": Or("left", "center", "right"),
Optional("fg_color"): Regex(r'^#[0-9a-fA-F]{3}$|^#[0-9a-fA-F]{6}$|^#[0-9a-fA-F]{8}$'), Optional("bg_color"): Regex(r'^#[0-9a-fA-F]{3}$|^#[0-9a-fA-F]{6}$|^#[0-9a-fA-F]{8}$'),
Optional("font"): And(int, lambda n: 1 <= n <= 5), Optional("fg_color"): Regex(r'^#[0-9a-fA-F]{3}$|^#[0-9a-fA-F]{6}$|^#[0-9a-fA-F]{8}$'),
Optional("offset"): int, Optional("font"): And(int, lambda n: 1 <= n <= 5),
Optional("line"): { Optional("offset"): int,
"type": Or("underline", "overline"), Optional("line"): {
Optional("color"): Regex(r'^#[0-9a-fA-F]{3}$|^#[0-9a-fA-F]{6}$|^#[0-9a-fA-F]{8}$') "type": Or("underline", "overline"),
Optional("color"): Regex(r'^#[0-9a-fA-F]{3}$|^#[0-9a-fA-F]{6}$|^#[0-9a-fA-F]{8}$')
}
# Not yet implemented.
#Optional("button"): {
# "activator": Or("left", "center", "right", "scrup", "scrdown"),
# "command": str
#}
}
} }
# Not yet implemented.
#Optional("button"): {
# "activator": Or("left", "center", "right", "scrup", "scrdown"),
# "command": str
#}
} }
} }
] }
} }
}) })
@ -54,6 +57,6 @@ def get_bar_config_and_module_config_list(filename):
try: try:
validated_config = config_schema.validate(config_file) validated_config = config_schema.validate(config_file)
return validated_config["bar"]["config"], validated_config["bar"]["modules"] return validated_config["bar"]["config"], validated_config["bar"]["monitor"]
except SchemaError as se: except SchemaError as se:
raise se raise se