panel: reorganize runtime files

parent dbee2b25
......@@ -11,7 +11,14 @@ user-facing configuration UI is expected to live in `ximperconf shell panel`.
- User modules: `~/.config/ximper-shell/panel/modules.json`
- User style: `~/.config/ximper-shell/panel/style.css`
- User dark style: `~/.config/ximper-shell/panel/style-dark.css`
- System modules: `/usr/share/ximperdistro/wm/base/waybar/modules.json`
- System defaults: `/etc/xdg/ximper-shell/panel/`
- System config: `/etc/xdg/ximper-shell/panel/config.json`
- System modules: `/etc/xdg/ximper-shell/panel/modules.json`
- System styles: `/etc/xdg/ximper-shell/panel/style.css`,
`/etc/xdg/ximper-shell/panel/style-dark.css`
System defaults are searched through `XDG_CONFIG_DIRS`, falling back to
`/etc/xdg`.
## Commands
......
{
"position": "top",
"type": "panel",
"modules_left": [
"image#menu",
"tray"
],
"modules_center": [
"workspaces"
],
"modules_right": [
"custom/media",
"language",
"group/volume",
"battery",
"network",
"bluetooth",
"custom/notification"
]
}
\ No newline at end of file
install_data(
'config.json',
'modules.json',
'style-base.css',
'style.css',
'style-dark.css',
install_dir: get_option('sysconfdir') / 'xdg' / 'ximper-shell' / 'panel',
)
* {
min-height: 0;
font-size: 14px;
font-family: Evolventa-Regular, FiraCode Nerd Font;
font-weight: 700;
}
window#waybar {
background: alpha(@bg, .99);
}
window#waybar.islands {
background: alpha(@bg, .0);
}
window#waybar.top {
border-radius: 0 0 20px 20px;
}
window#waybar.bottom {
border-radius: 20px 20px 0 0;
}
window#waybar.left {
border-radius: 0 20px 20px 0;
}
window#waybar.right {
border-radius: 20px 0 0 20px;
}
window#waybar.floating {
border-radius: 20px;
}
.module {
background-color: @selected_bg;
min-height: 0;
border-radius: 20px;
color: @text;
border: none;
}
window#waybar.top .module,
window#waybar.bottom .module {
margin: 5px 0;
padding: 0px 10px;
}
window#waybar.left .module,
window#waybar.right .module {
margin: 0 5px;
padding: 10px 0px;
}
window#waybar.top .modules-left,
window#waybar.bottom .modules-left {
margin: 0 5px;
}
window#waybar.top .modules-right,
window#waybar.bottom .modules-right {
margin: 0 5px;
}
window#waybar.left .modules-left,
window#waybar.right .modules-left {
margin: 5px 0;
}
window#waybar.left .modules-right,
window#waybar.right .modules-right {
margin: 5px 0;
}
.module:hover {
background-color: alpha(@selected_bg, .4);
}
window#waybar.islands .module:hover {
background-color: @bg;
}
.horizontal>widget>box.horizontal>widget:first-child>.module {
border-radius: 20px 0 0 20px;
margin: 3px 0;
padding: 0 5px 0 10px;
}
.horizontal>widget>box.horizontal>widget:not(:first-child):not(:last-child)>.module {
border-radius: 0;
margin: 3px 0;
padding: 0 5px;
}
.horizontal>widget>box.horizontal>widget:last-child>.module {
border-radius: 0 20px 20px 0;
margin: 3px 0;
padding: 0 10px 0 5px;
}
.vertical>widget>box.vertical>widget:first-child>.module {
border-radius: 20px 20px 0 0;
margin: 0 3px;
padding: 100px 10px 0 10px;
}
.vertical>widget>box.vertical>widget:not(:first-child):not(:last-child)>.module {
border-radius: 0;
margin: 3px 0;
padding: 0 5px;
}
.vertical>widget>box.vertical>widget:last-child>.module {
border-radius: 0 0 20px 20px;
margin: 3px 0;
padding: 0 10px 0 5px;
}
window#waybar.left .module#workspaces {
padding: 0px;
}
#workspaces button {
color: @text;
margin: 3px;
border-radius: 20px;
transition: padding, background 200ms ease-in-out 100ms;
}
window#waybar.top #workspaces button,
window#waybar.bottom #workspaces button,
window#waybar.left #workspaces button,
window#waybar.right #workspaces button {
padding: 0px;
}
window#waybar.top #workspaces button:hover,
window#waybar.top #workspaces button.active,
window#waybar.top #workspaces button.urgent,
window#waybar.bottom #workspaces button:hover,
window#waybar.bottom #workspaces button.active,
window#waybar.bottom #workspaces button.urgent {
padding: 0 10px;
}
window#waybar.left #workspaces button:hover,
window#waybar.left #workspaces button.active,
window#waybar.left #workspaces button.urgent,
window#waybar.right #workspaces button:hover,
window#waybar.right #workspaces button.active,
window#waybar.right #workspaces button.urgent {
padding: 10px 0;
}
#workspaces button.active {
background: alpha(@accent, .7);
}
#workspaces button.urgent {
background: alpha(@urgent, .7);
}
#workspaces button.special {
padding: 0px 5px;
}
#workspaces button.special.active {
background: alpha(@accent, .7);
padding: 0px 5px;
}
#workspaces button.special.urgent {
background: alpha(@urgent, .7);
padding: 0px 5px;
}
#tray menu {
background-color: @bg;
}
#idle_inhibitor {
padding: 0px 10px 0px 7px;
}
#pulseaudio.volume.muted {
color: @unfocused;
}
#pulseaudio.microphone.source-muted {
color: @unfocused;
}
window#waybar.empty #window {
background-color: transparent;
}
tooltip {
border-radius: 20px;
background: @bg;
color: @text
}
tooltip label {
color: @text
}
\ No newline at end of file
@define-color bg #222226;
@define-color selected_bg #343437;
@define-color text #ffffff;
@define-color workspace_text #a6a7ac;
@define-color unfocused #f85656;
@define-color urgent #e89b0b;
@define-color accent #3584e4;
@import url("style-base.css");
@define-color bg #fafafb;
@define-color selected_bg #deddda;
@define-color text rgb(0, 0, 6);
@define-color workspace_text #9a9996;
@define-color unfocused #f85656;
@define-color urgent #e89b0b;
@define-color accent #3584e4;
@import url("style-base.css");
......@@ -3,20 +3,5 @@ project('ximper-shell-panel',
meson_version: '>= 1.0.0',
)
custom_target(
'go-build',
build_by_default: true,
build_always_stale: true,
output: meson.project_name(),
console: true,
install: true,
install_dir: get_option('bindir'),
command: [
'go', 'build',
'-o', '@OUTPUT@',
meson.current_source_dir(),
],
env: [
'CGO_ENABLED=0',
],
)
subdir('src')
subdir('data')
......@@ -11,12 +11,10 @@ import (
)
const (
SystemConfigHome = "/usr/share/ximperdistro/wm/base"
SystemWaybarDir = SystemConfigHome + "/waybar"
ModulesFile = SystemWaybarDir + "/modules.json"
StyleFile = SystemWaybarDir + "/style.css"
DarkStyleFile = SystemWaybarDir + "/style-dark.css"
SafeModeEnv = "XIMPER_SHELL_SAFEMODE"
ConfigSubdir = "ximper-shell/panel"
DefaultSystemXDGPath = "/etc/xdg"
SafeModeEnv = "XIMPER_SHELL_SAFEMODE"
WaybarConfigDirEnv = "WAYBAR_CONFIG_DIR"
)
type Config struct {
......@@ -27,18 +25,12 @@ type Config struct {
ModulesRight []string `json:"modules_right"`
}
func DefaultConfig() Config {
return Config{
Position: "top",
Type: "panel",
ModulesLeft: []string{"image#menu"},
ModulesCenter: []string{"workspaces"},
ModulesRight: []string{"group/volume", "network", "clock"},
func LoadConfig(path string) (Config, error) {
cfg, err := LoadDefaultConfig()
if err != nil {
return Config{}, err
}
}
func LoadConfig(path string) (Config, error) {
cfg := DefaultConfig()
data, err := os.ReadFile(path)
if err != nil {
if errors.Is(err, os.ErrNotExist) {
......@@ -55,6 +47,22 @@ func LoadConfig(path string) (Config, error) {
return cfg, cfg.Validate()
}
func LoadDefaultConfig() (Config, error) {
var cfg Config
path := SystemConfigPath()
data, err := os.ReadFile(path)
if err != nil {
return cfg, err
}
if len(bytes.TrimSpace(data)) == 0 {
return cfg, fmt.Errorf("system panel config %s is empty", path)
}
if err := json.Unmarshal(data, &cfg); err != nil {
return cfg, fmt.Errorf("parse system panel config: %w", err)
}
return cfg, cfg.Validate()
}
func (c Config) Validate() error {
switch c.Position {
case "top", "bottom", "left", "right":
......@@ -69,7 +77,7 @@ func (c Config) Validate() error {
}
for _, name := range append(append([]string{}, c.ModulesLeft...), append(c.ModulesCenter, c.ModulesRight...)...) {
if name == "" {
if strings.TrimSpace(name) == "" {
return errors.New("module name cannot be empty")
}
}
......@@ -78,19 +86,72 @@ func (c Config) Validate() error {
}
func UserConfigPath() string {
return filepath.Join(os.Getenv("HOME"), ".config", "ximper-shell", "panel", "config.json")
return userConfigPath("config.json")
}
func SystemConfigPath() string {
return systemConfigPath("config.json")
}
func UserModulesPath() string {
return filepath.Join(os.Getenv("HOME"), ".config", "ximper-shell", "panel", "modules.json")
return userConfigPath("modules.json")
}
func UserStylePath() string {
return filepath.Join(os.Getenv("HOME"), ".config", "ximper-shell", "panel", "style.css")
return userConfigPath("style.css")
}
func UserDarkStylePath() string {
return filepath.Join(os.Getenv("HOME"), ".config", "ximper-shell", "panel", "style-dark.css")
return userConfigPath("style-dark.css")
}
func SystemModulesPath() string {
return systemConfigPath("modules.json")
}
func SystemStylePath() string {
return systemConfigPath("style.css")
}
func SystemDarkStylePath() string {
return systemConfigPath("style-dark.css")
}
func userConfigPath(name string) string {
dir, err := os.UserConfigDir()
if err != nil || dir == "" {
dir = filepath.Join(os.Getenv("HOME"), ".config")
}
return filepath.Join(dir, filepath.FromSlash(ConfigSubdir), name)
}
func systemConfigPath(name string) string {
dirs := systemConfigDirs()
for _, dir := range dirs {
path := filepath.Join(dir, filepath.FromSlash(ConfigSubdir), name)
if fileExists(path) {
return path
}
}
return filepath.Join(dirs[0], filepath.FromSlash(ConfigSubdir), name)
}
func systemConfigDirs() []string {
value := os.Getenv("XDG_CONFIG_DIRS")
if strings.TrimSpace(value) == "" {
return []string{DefaultSystemXDGPath}
}
var dirs []string
for _, dir := range filepath.SplitList(value) {
if strings.TrimSpace(dir) != "" {
dirs = append(dirs, dir)
}
}
if len(dirs) == 0 {
return []string{DefaultSystemXDGPath}
}
return dirs
}
func SafeMode() bool {
......@@ -107,20 +168,16 @@ func RuntimeDir() string {
return filepath.Join(os.TempDir(), "ximper-shell", "panel")
}
func RuntimeWaybarDir() string {
return filepath.Join(RuntimeDir(), "waybar")
}
func RuntimeConfigPath() string {
return filepath.Join(RuntimeWaybarDir(), "config")
return filepath.Join(RuntimeDir(), "config")
}
func RuntimeModulesPath() string {
return filepath.Join(RuntimeWaybarDir(), "modules.jsonc")
return filepath.Join(RuntimeDir(), "modules.jsonc")
}
func RuntimeStylePath(name string) string {
return filepath.Join(RuntimeWaybarDir(), name)
return filepath.Join(RuntimeDir(), name)
}
func RuntimePIDPath() string {
......
......@@ -106,11 +106,11 @@ func WriteRuntimeModules(registry Registry) error {
}
func WriteRuntimeStyles() error {
if err := os.MkdirAll(RuntimeWaybarDir(), 0755); err != nil {
if err := os.MkdirAll(RuntimeDir(), 0755); err != nil {
return err
}
userStyle := UserStylePath()
userDarkStyle := UserStylePath()
userDarkStyle := ""
if SafeMode() {
userStyle = ""
userDarkStyle = ""
......@@ -118,10 +118,10 @@ func WriteRuntimeStyles() error {
userDarkStyle = UserDarkStylePath()
}
if err := writeRuntimeStyle(RuntimeStylePath("style.css"), StyleFile, userStyle); err != nil {
if err := writeRuntimeStyle(RuntimeStylePath("style.css"), SystemStylePath(), userStyle); err != nil {
return err
}
return writeRuntimeStyle(RuntimeStylePath("style-dark.css"), DarkStyleFile, userDarkStyle)
return writeRuntimeStyle(RuntimeStylePath("style-dark.css"), SystemDarkStylePath(), userDarkStyle)
}
func writeRuntimeStyle(runtimePath, systemPath, userPath string) error {
......@@ -147,16 +147,16 @@ func loadInputs() (Config, Registry, error) {
func loadConfig() (Config, error) {
if SafeMode() {
return DefaultConfig(), nil
return LoadDefaultConfig()
}
return LoadConfig(UserConfigPath())
}
func loadRegistry() (Registry, error) {
if SafeMode() {
return LoadRegistry(ModulesFile)
return LoadRegistry(SystemModulesPath())
}
return LoadMergedRegistry(ModulesFile, UserModulesPath())
return LoadMergedRegistry(SystemModulesPath(), UserModulesPath())
}
func resolveModuleList(modules []string, position string, registry Registry) ([]string, error) {
......
panel_sources = files(
'config.go',
'generate.go',
'jsonc.go',
'main.go',
'registry.go',
'runner.go',
)
custom_target(
'go-build',
input: panel_sources,
build_by_default: true,
build_always_stale: true,
output: meson.project_name(),
console: true,
install: true,
install_dir: get_option('bindir'),
command: [
'go', 'build',
'-o', '@OUTPUT@',
meson.current_source_dir(),
],
env: [
'CGO_ENABLED=0',
],
)
......@@ -127,8 +127,8 @@ func (r Registry) SelectableNames() []string {
}
func (r Registry) Resolve(name, position string) (string, error) {
explicit := strings.Contains(name, "/") || strings.Contains(name, "#")
if explicit {
explicitVariant := strings.Contains(name, "#")
if explicitVariant {
if _, ok := r.Modules[name]; ok {
return name, nil
}
......@@ -137,6 +137,7 @@ func (r Registry) Resolve(name, position string) (string, error) {
wm := currentWM()
vertical := position == "left" || position == "right"
explicitModule := strings.Contains(name, "/")
var candidates []string
if vertical {
......@@ -145,6 +146,16 @@ func (r Registry) Resolve(name, position string) (string, error) {
}
candidates = append(candidates, name+"#vertical")
}
if explicitModule {
candidates = append(candidates, name)
for _, candidate := range candidates {
if _, ok := r.Modules[candidate]; ok {
return candidate, nil
}
}
return "", fmt.Errorf("module %q not found", name)
}
if wm != "" {
candidates = append(candidates, wm+"/"+name)
}
......
......@@ -35,7 +35,7 @@ func StartWaybar() error {
defer logFile.Close()
cmd := exec.Command("waybar")
cmd.Env = append(os.Environ(), "XDG_CONFIG_HOME="+RuntimeDir())
cmd.Env = append(os.Environ(), WaybarConfigDirEnv+"="+RuntimeDir())
cmd.Stdout = logFile
cmd.Stderr = logFile
cmd.Stdin = nil
......@@ -167,17 +167,17 @@ func isPanelProcess(pid int) bool {
return false
}
hasWaybar := bytes.Contains([]byte(filepath.Base(string(parts[0]))), []byte("waybar"))
return hasWaybar && hasPanelConfigHome(pid)
hasWaybar := filepath.Base(string(parts[0])) == "waybar"
return hasWaybar && hasPanelConfigDir(pid)
}
func hasPanelConfigHome(pid int) bool {
func hasPanelConfigDir(pid int) bool {
data, err := os.ReadFile(filepath.Join("/proc", strconv.Itoa(pid), "environ"))
if err != nil {
return false
}
for _, part := range bytes.Split(bytes.TrimRight(data, "\x00"), []byte{0}) {
if string(part) == "XDG_CONFIG_HOME="+RuntimeDir() {
if string(part) == WaybarConfigDirEnv+"="+RuntimeDir() {
return true
}
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment