glava-config widget generation (incomplete)

This commit is contained in:
Jarcode
2019-09-07 02:32:21 -07:00
parent 4bfbc859f8
commit 90d275f2cd
8 changed files with 901 additions and 84 deletions

307
glava-config/config.lua Normal file
View File

@@ -0,0 +1,307 @@
local lfs = require "lfs"
local mappings = require "glava-config.mappings"
local config = {
Profile = { mt = {} }
}
config.Profile.__index = config.Profile
setmetatable(config.Profile, config.Profile.mt)
-- Split path into entries, such that `table.concat` can be used to
-- reconstruct the path. Prepends the result with an empty string so
-- root (absolute) paths are preserved
local function path_split(str, sep)
local sep, fields = sep or ":", (str:sub(1, sep:len()) == sep and {""} or {})
local pattern = string.format("([^%s]+)", sep)
str:gsub(pattern, function(c) fields[#fields + 1] = c end)
return fields
end
-- Concatenates paths such that duplicate path separators are removed.
-- Can be used on non-split arguments
local function path_concat(...)
local ret = {}
for _, v in ipairs({...}) do
for _, e in ipairs(path_split(v, "/")) do
if e ~= "" or #ret == 0 then
ret[#ret + 1] = e
end
end
end
return table.concat(ret, "/")
end
-- To parse data from GLSL configs we use some complex pattern matching.
--
-- Because Lua's patterns operate on a per-character basis and do not offer
-- any read-ahead functionality, we use a pattern 'replacement' functionality
-- such that the match of an input pattern is passed to a function to produce
-- an output pattern.
--
-- This effectively means we have some fairly powerful parsing which allows us
-- to handle things like quoted strings with escaped characters.
local function none(...) return ... end
local MATCH_ENTRY_PATTERN = "^%s*%#(%a+)%s+(%a+)"
local MATCH_DATA_PREFIX = "^%s*%#%a+%s+%a+"
local MATCH_TYPES = {
["float"] = {
pattern = "(%d+.?%d*)",
cast = tonumber,
serialize = tostring
},
["int"] = {
pattern = "(%d+)",
cast = tonumber,
serialize = function(x) tostring(math.floor(x)) end
},
["string"] = {
pattern = "(.+)",
-- Strip away string quotation and escape syntax
cast = function(match)
local ret = {}
local escaped = false
for c in match:gmatch(".") do
if c == "\"" then
if escaped then ret[#ret + 1] = c end
elseif c ~= "\\" then ret[#ret + 1] = c end
if c == "\\" then
if escaped then ret[#ret + 1] = c end
escaped = not escaped
else escaped = false end
end
return table.concat(ret, "")
end,
-- Read-ahead function to generate a fixed-width pattern
-- to match the next (possibly quoted) string
transform = function(match)
local quoted = false
local start = true
local escaped = false
local count = 0
local skip = 0
for c in match:gmatch(".") do
count = count + 1
if c == "\"" then
if start then
start = false
quoted = true
elseif not escaped then
if quoted then
-- End-quote; end of string
break
else
-- Formatting error: non-escaped quote after string start: `foo"bar`
-- We attempt to resolve this by halting parsing and skipping the
-- out-of-context quotation
count = count - 1
skip = skip + 1
break
end
end
elseif c == " " then
if not start and not quoted then
-- Un-escaped space; end of string
-- skip the space itself
count = count - 1
break
end
else start = false end
if c == "\\" then
escaped = not escaped
else escaped = false end
end
-- Strings without an ending quote will simply take up the remainder of
-- the request, causing the following arguments to be overwritten. This
-- is intended to ensure we can save valid options after stripping out
-- the errornous quotes and using defaults for the subsequent arguments.
local ret = { "(" }
for t = 1, count do
ret[1 + t] = "."
end
ret[2 + count] = ")"
for t = 1, skip do
ret[2 + count + t] = "."
end
return table.concat(ret, "")
end,
serialize = function(x)
return string.format("\"%s\"", x)
end
}
}
config.path_concat = path_concat
config.path_split = path_split
local function create_p(parts, mode, silent)
local function errfmt(err)
return string.format("Failed to create '%s' in '%s': %s",
path_concat(parts, "/"), path_concat(arr, "/"), err)
end
for i, v in ipairs(arr) do
parts[#parts + 1] = v
local failret = false
if silent then failret = #parts == #arr end
local path = path_concat(parts, "/")
local m = (i == #arr and mode or "directory")
local attr, err = lfs.attributes(path, "mode")
if attr == nil then
({
file = function()
local ret, err = lfs.touch(path)
if ret ~= true then return false, errfmt(err) end
end,
directory = function()
local ret, err = lfs.mkdir(path)
if ret ~= true then return false, errfmt(err) end
end,
})[m]()
elseif attr ~= m then
if not (silent and #parts == #arr) then
return false, string.format("'%s' is not a %s", path, m)
else
return true
end
end
end
return true
end
local function create_p(path, ...) create_pf(path_split(path, "/"), ...) end
local function unwrap(ret, err)
if ret == nil or ret == false then
glava.fail(err)
else return ret end
end
function config.Profile:__call(args)
local self = { name = args.name }
self:rebuild()
return setmetatable(self, config.Profile)
end
function config.Profile:rename(new)
end
function config.Profile:rebuild()
self.store = {}
self.path = path_concat(glava.config_path, "profiles", self.name)
unwrap(create_p(self.path, "directory", true))
local unbuilt = {}
for k, _ in pairs(mappings) do
unbuilt[k] = true
end
for file in lfs.dir(self.path) do
if file ~= "." and file ~= ".." and mappings[file] ~= nil then
self:rebuild_file(file, path_concat(path, file))
unbuilt[file] = nil
end
end
for file, _ in pairs(unbuilt) do
self:rebuild_file(file, path_concat(path, file), true)
end
end
function config.Profile:rebuild_file(file, path, phony)
local fstore = {}
local fmap = mappings[file]
self.store[file] = fstore
for k, _ in pairs(fmap) do
if type(k) == "string" and k ~= "name" then
unbuilt[k] = true
end
end
function parse_line(line, idx, key, default)
local map = fmap[key]
if map == nil then return end
local tt = type(map.field_type) == "table" and map.field_type or { map.field_type }
local _,e = string.find(line, MATCH_DATA_PREFIX)
local at = string.sub(line, 1, e)
if default == nil or fstore[key] == nil then
fstore[key] = {}
end
if default == nil then fstore[key].line = idx end
for t, v in ipairs(tt) do
local r, i, match = string.find(at, "%s*" .. MATCH_TYPES[v].pattern)
if r ~= nil then
-- Handle read-ahead pattern transforms
if MATCH_TYPES[v].transform ~= nil then
_, i, match = string.find(at, "%s*" .. MATCH_TYPES[v].transform(match))
end
if default == nil or fstore[key][t] == nil then
fstore[key][t] = MATCH_TYPES[v].cast(match)
end
at = string.sub(at, 1, i)
else break end
end
end
local idx = 1
if phony ~= true then
for line in io.lines(path) do
local mtype, arg = string.match(line, MATCH_ENTRY_PATTERN)
if mtype ~= nil then
parse_line(line, idx, string.format("%s:%s", mtype, arg))
end
idx = idx + 1
end
end
idx = 1
for line in io.lines(path_concat(glava.system_shader_path, file)) do
local mtype, arg = string.match(line, MATCH_ENTRY_PATTERN)
if mtype ~= nil then
parse_line(line, idx, string.format("%s:%s", mtype, arg), true)
end
idx = idx + 1
end
end
-- Sync all
function config.Profile:sync()
for k, v in pairs(self.store) do self:sync_file(k) end
end
-- Sync filename relative to profile root
function config.Profile:sync_file(fname)
local fstore = self.store[fname]
local fmap = mappings[file]
local fpath = path_concat(self.path, fname)
local buf = {}
local extra = {}
local idx = 1
for k, v in fstore do
local parts = { string.format("#%s", string.gsub(k, ":", " ")) }
local field = fmap[k].field_type
for i, e in ipairs(type(field) == "table" and field or { field }) do
parts[#parts + 1] = MATCH_TYPES[e].serialize(v[i])
end
local serialized = table.concat(parts, " ")
if v.line then buf[line] = serialized
else extra[#extra + 1] = serialized end
end
if lfs.attributes(fpath, "mode") == "file" then
for line in io.lines(path) do
if not buf[idx] then
buf[idx] = line
end
idx = idx + 1
end
for _, v in ipairs(extra) do
buf[#buf + 1] = v
end
end
local handle, err = io.open(fpath, "w+")
if handle then
handle:write(table.concat(buf, "\n"))
handle:close()
else
glava.fail(string.format("Could not open file handle to \"%s\": %s", handle, err))
end
end
return config

View File

@@ -1,5 +1,3 @@
local window = require("glava-config.window")
local function dependency(name)
if package.loaded[name] then
return
@@ -17,14 +15,64 @@ local function dependency(name)
end
end
function glava.fail(message)
print(string.format("!!FATAL!!: %s", message))
os.exit(1)
end
local main = {}
-- Format string, but silently return nil if varargs contains any nil entries
local function format_silent(fmt, ...)
for _, v in ipairs({...}) do
if v == nil then return nil end
end
return string.format(fmt, ...)
end
function main.entry(prog, ...)
dependency("lgi")
dependency("lfs")
if glava.resource_path:sub(glava.resource_path:len()) ~= "/" then
glava.resource_path = glava.resource_path .. "/"
end
glava.config_path = format_silent("%s/glava", os.getenv("XDG_CONFIG_HOME"))
or format_silent("%s/.config/glava", os.getenv("HOME"))
or "/home/.config/glava"
local lfs = require "lfs"
local window = require "glava-config.window"
glava.module_list = {}
for m in lfs.dir(glava.system_shader_path) do
if m ~= "." and m ~= ".."
and lfs.attributes(glava.system_shader_path .. "/" .. m, "mode") == "directory"
and m ~= "util" then
glava.module_list[#glava.module_list + 1] = m
end
end
local mappings = require "glava-config.mappings"
-- Associate `map_name = tbl` from mapping list for future lookups
for k, v in pairs(mappings) do
local i = 1
local adv = false
while v[i] ~= nil do
if type(v[i]) == "table" then
v[v[i][1]] = v[i]
v[i].advanced = adv
i = i + 1
elseif type(v[i]) == "string" and v[i] == "advanced" then
adv = true
table.remove(v, i)
else
glava.fail(string.format("Unknown mappings entry type for file: \"%s\"", type(v)))
end
end
end
-- Enter into Gtk window
window()
end

40
glava-config/mappings.lua Normal file
View File

@@ -0,0 +1,40 @@
return {
["rc.glsl"] = {
name = "Global Options",
{
"request:mod",
field_type = "string",
field_attrs = {
entries = glava.module_list
},
description = "Visualizer module"
},
{
"request:fakeident",
field_type = "ident",
description = "Some identifier"
},
{
"request:fakefloat",
field_type = "float",
description = "Some Float"
},
{
"request:setbg",
field_type = "color",
field_attrs = { alpha = true },
description = "Window background color"
},
"advanced",
{
"request:setversion",
field_type = { "int", "int" },
field_attrs = {
frame_label = "Version",
{ label = "Major:", lower = 0, upper = 10, width = 2 },
{ label = "Minor:", lower = 0, upper = 10, width = 2 }
},
description = "OpenGL context version request"
}
}
}

68
glava-config/utils.lua Normal file
View File

@@ -0,0 +1,68 @@
local lgi = require "lgi"
local Gdk = lgi.Gdk
local utils = {}
function utils.infer_color_bits(x)
if x:sub(1, 1) ~= "#" then
x = "#" .. x
end
for i = 1, 9 - x:len() do
x = x .. (x:len() >= 7 and "F" or "0")
end
return x
end
function utils.sanitize_color(x)
return utils.infer_color_bits(x):sub(1, 9):gsub("[^#0-9a-fA-F]", "0")
end
function utils.parse_color_rgba(x)
local x = utils.infer_color_bits(x)
return Gdk.RGBA.parse(
string.format(
"rgba(%d,%d,%d,%f)",
tonumber(x:sub(2, 3), 16),
tonumber(x:sub(4, 5), 16),
tonumber(x:sub(6, 7), 16),
tonumber(x:sub(8, 9), 16) / 255
)
)
end
function utils.rgba_to_gdk_color(x)
return Gdk.Color(
math.floor(x.red * 255 + 0.5),
math.floor(x.green * 255 + 0.5),
math.floor(x.blue * 255 + 0.5)
)
end
function utils.rgba_to_integral(x)
return {
red = math.floor(x.red * 255 + 0.5),
green = math.floor(x.green * 255 + 0.5),
blue = math.floor(x.blue * 255 + 0.5)
}
end
function utils.format_color_rgba(x)
return string.format(
"#%02X%02X%02X%02X",
math.floor(x.red * 255 + 0.5),
math.floor(x.green * 255 + 0.5),
math.floor(x.blue * 255 + 0.5),
math.floor(x.alpha * 255 + 0.5)
)
end
function utils.format_color_rgb(x)
return string.format(
"#%02X%02X%02X",
math.floor(x.red * 255 + 0.5),
math.floor(x.green * 255 + 0.5),
math.floor(x.blue * 255 + 0.5)
)
end
return utils

View File

@@ -1,10 +1,52 @@
--[[
MAINTAINER NOTICE:
This application aims to be both Gtk+ 3 and 4 compatible for future-proofing. This means
avoiding *every* deprecated widget in Gtk+ 3, and watching out for some old functionality:
* Gdk.Color usage, use Gdk.RGBA instead
* Pango styles and style overrides
* Check convenience wrappers for deprecation, ie. GtkColorButton
* Avoid seldom used containers, as they may have been removed in 4.x (ie. GtkButtonBox)
In some cases we use deprecated widgets or 3.x restricted functionality, but only when we
query that the types are available from LGI (and otherwise use 4.x compatible code).
]]
return function()
local lgi = require 'lgi'
local GObject = lgi.GObject
local Gtk = lgi.Gtk
local Pango = lgi.Pango
local Gdk = lgi.Gdk
local lgi = require 'lgi'
local utils = require 'glava-config.utils'
local mappings = require 'glava-config.mappings'
local GObject = lgi.GObject
local Gtk = lgi.Gtk
local Pango = lgi.Pango
local Gdk = lgi.Gdk
local GdkPixbuf = lgi.GdkPixbuf
local cairo = lgi.cairo
-- Both `GtkColorChooserDialog` and `GtkColorSelectionDialog` are
-- supported by this tool, but the latter is deprecated and does
-- not exist in 4.x releases.
--
-- The old chooser, however, is objectively better so let's try
-- to use it if it exists.
local use_old_chooser = true
if Gtk.ColorSelectionDialog == nil then
use_old_chooser = false
end
local window
local repeat_pattern = cairo.SurfacePattern(
cairo.ImageSurface.create_from_png(glava.resource_path .. "transparent.png")
)
repeat_pattern:set_extend("REPEAT")
-- We need to define a CSS class to use an alternative font for
-- color and identity entries; used to indicate to the user that
-- the field has formatting requirements
local cssp = Gtk.CssProvider {}
cssp:load_from_data(".fixed-width-font-entry { font-family: \"Monospace\"; }")
local ItemColumn = {
PROFILE = 1,
@@ -31,58 +73,340 @@ return function()
[ItemColumn.WEIGHT] = 600
}
-- Apply `t[k] = v` to all table argument at array indexes,
-- and return the unpacked list of tables. Used for nesting
-- widget construction.
local function apply(tbl)
local ret = {}
for k, v in ipairs(tbl) do
ret[k] = v
tbl[k] = nil
end
for k, v in pairs(tbl) do
for _, r in ipairs(ret) do
r[k] = v
end
end
return unpack(ret)
end
local function ComboBoxFixed(tbl)
local inst = Gtk.ComboBoxText { id = tbl.id }
for _, v in pairs(tbl) do
if type(v) == "table" then
inst:append_text(v[1])
end
inst:append_text(v)
end
inst:set_active(tbl.default or 0)
return inst
end
local SpoilerView = function(tbl)
local stack = Gtk.Stack {
expand = true,
transition_type = Gtk.StackTransitionType.CROSSFADE
}
local btn = Gtk.CheckButton {
active = tbl.active or false
}
if tbl.active ~= true then
stack:add_named(Gtk.Box {}, "none")
end
stack:add_named(tbl[1], "view")
if tbl.active == true then
stack:add_named(Gtk.Box {}, "none")
end
function btn:on_toggled(path)
stack:set_visible_child_name(btn.active and "view" or "none")
end
return Gtk.Box {
expand = false,
orientation = "VERTICAL",
spacing = 4,
Gtk.Box {
orientation = "HORIZONTAL",
spacing = 6,
btn,
Gtk.Label { label = tbl.label or "Spoiler" }
},
Gtk.Separator(),
stack
}
end
local ConfigView = function(tbl)
local grid = {
row_spacing = 5,
column_spacing = 12,
column_homogeneous = false,
row_homogeneous = true
}
local list = {}
local idx = 0
for _, entry in pairs(tbl) do
local function cbuild(list, entry, idx)
list[#list + 1] = {
Gtk.Label { label = entry[1], xalign = 0 },
Gtk.Label { label = entry[1], halign = "START", valign = "START" },
left_attach = 0, top_attach = idx
}
list[#list + 1] = {
Gtk.Alignment { xscale = 0, yscale = 0, xalign = 1, entry[2] },
Gtk.Box { hexpand = true },
left_attach = 1, top_attach = idx
}
list[#list + 1] = {
apply { halign = "FILL", hexpand = false, entry[2] },
left_attach = 2, top_attach = idx
}
end
for _, entry in ipairs(tbl) do
cbuild(list, entry, idx)
idx = idx + 1
end
local adv = {}
if tbl.advanced then
idx = 0
for _, entry in ipairs(tbl.advanced) do
cbuild(adv, entry, idx)
idx = idx + 1
end
end
for k, v in pairs(grid) do
list[k] = v
adv[k] = v
end
return Gtk.ScrolledWindow {
shadow_type = "IN",
expand = true,
Gtk.Alignment {
top_padding = 12,
left_padding = 20,
right_padding = 20,
xscale = 1,
yscale = 1,
xalign = 0,
Gtk.Grid {
row_spacing = 5,
column_spacing = 12,
column_homogeneous = true,
unpack(list)
}
Gtk.Box {
margin_top = 12,
margin_left = 16,
margin_right = 16,
hexpand = true,
vexpand = true,
halign = "FILL",
orientation = "VERTICAL",
spacing = 6,
Gtk.Grid(list),
#adv > 0 and
SpoilerView {
label = "Show Advanced",
Gtk.Grid(adv)
} or Gtk.Box {}
}
}
end
local function wrap_label(widget, label)
if label then
widget = Gtk.Box {
orientation = "HORIZONTAL",
spacing = 6,
Gtk.Label {
label = label
}, widget
}
end
return widget
end
local widget_generators
widget_generators = {
["boolean"] = function(attrs)
local widget = Gtk.Switch { hexpand = false }
return {
widget = Gtk.Box { Gtk.Box { hexpand = true }, wrap_label(widget, attrs.label) },
set_data = function(x)
widget.active = x
end
}
end,
["string"] = function(attrs)
local widget = attrs.entries ~= nil
and apply { hexpand = true, ComboBoxFixed(attrs.entries) }
or Gtk.Entry { width_chars = 16 }
return {
widget = wrap_label(widget, attrs.label),
internal = widget,
set_data = function(x)
if not attrs.entries then
widget:set_text(x)
else
for k, v in ipairs(attrs.entries) do
if v == x then
widget:set_active(v - 1)
return
end
end
local fmt = "WARNING: Invalid string entry for Gtk.ComboBox mapping: \"%s\""
print(string.format(fmt, x))
end
end
}
end,
["ident"] = function(attrs)
local s = widget_generators.string(attrs)
s.internal:get_style_context():add_provider(cssp, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION)
s.internal:get_style_context():add_class("fixed-width-font-entry")
if not attrs.entries then
-- Handle idenifier formatting for entries without a preset list
function s.internal:on_changed()
local i = s.internal.text
if i:match("[^%w]") ~= nil or i:sub(1, 1):match("[^%a]") ~= nil then
s.internal.text = i:gsub("[^%w]", ""):gsub("^[^%a]+", "")
end
-- todo: handle changed (signal override?)
end
end
return s
end,
["float"] = function(attrs)
local widget = Gtk.SpinButton {
hexpand = true,
adjustment = Gtk.Adjustment {
lower = attrs.lower or 0,
upper = attrs.upper or 100,
page_increment = attrs.increment or 1,
page_size = attrs.increment or 1,
step_increment = attrs.increment or 1
},
width_chars = attrs.width or 6,
numeric = true,
digits = attrs.digits or 2,
climb_rate = attrs.increment or 1
}
return {
widget = wrap_label(widget, attrs.label),
set_data = function(x) widget:set_value(x) end
}
end,
["int"] = function(attrs)
local widget = Gtk.SpinButton {
hexpand = true,
adjustment = Gtk.Adjustment {
lower = attrs.lower or 0,
upper = attrs.upper or 100,
page_increment = attrs.increment or 1,
page_size = attrs.increment or 1,
step_increment = attrs.increment or 1
},
width_chars = attrs.width or 6,
numeric = true,
digits = 0,
climb_rate = attrs.increment or 1
}
return {
widget = wrap_label(apply { vexpand = false, widget }, attrs.label),
set_data = function(x) widget:set_value(x) end
}
end,
-- The color type is the hardest to implement; as Gtk deprecated
-- the old color chooser button, so we have to implement our own.
-- The benefits of doing this mean we get to use the "nice" Gtk3
-- chooser, and the button rendering itself is much better.
["color"] = function(attrs)
local c = Gdk.RGBA {
red = 1.0, green = 1.0, blue = 1.0, alpha = 1.0
}
local area = Gtk.DrawingArea()
area:set_size_request(16, 16)
local draw = function(widget, cr)
local context = widget:get_style_context()
local width = widget:get_allocated_width()
local height = widget:get_allocated_height()
local aargc = { width / 2, height / 2, math.min(width, height) / 2, 0, 2 * math.pi }
Gtk.render_background(context, cr, 0, 0, width, height)
cr:set_source(repeat_pattern)
cr:arc(unpack(aargc))
cr:fill()
cr:set_source_rgba(c.red, c.green, c.blue, c.alpha)
cr:arc(unpack(aargc))
cr:fill()
end
-- Gtk 3/4 compat
if area.set_draw_func then
area:set_draw_func(draw)
else
area.on_draw = draw
end
local btn = Gtk.Button {
apply {
margin_top = 1,
margin_bottom = 1,
area
}
}
local entry = Gtk.Entry {
hexpand = true,
width_chars = 9,
max_length = 9,
text = attrs.alpha and "#FFFFFFFF" or "#FFFFFF"
}
entry:get_style_context():add_provider(cssp, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION)
entry:get_style_context():add_class("fixed-width-font-entry")
local widget = Gtk.Box {
orientation = "HORIZONTAL",
spacing = 0,
entry, btn
}
widget:get_style_context():add_class("linked")
widget = wrap_label(widget, attrs.label)
function btn:on_clicked()
local dialog = (use_old_chooser and Gtk.ColorSelectionDialog or Gtk.ColorChooserDialog) {
title = "Select Color",
transient_for = window,
modal = true,
destroy_with_parent = true
}
if use_old_chooser then
dialog.cancel_button:set_visible(false)
dialog.ok_button.label = "Close"
dialog.color_selection.current_rgba = c
if attrs.alpha then
dialog.color_selection.has_opacity_control = true
end
function dialog.color_selection:on_color_changed()
c = dialog.color_selection.current_rgba
entry:set_text(attrs.alpha and utils.format_color_rgba(c) or utils.format_color_rgb(c))
area:queue_draw()
end
else
dialog.rgba = c
if attrs.alpha then
dialog.use_alpha = true
end
end
local ret = dialog:run()
dialog:set_visible(false)
if not use_old_chooser and ret == Gtk.ResponseType.OK then
c = dialog.rgba
entry:set_text(attrs.alpha and utils.format_color_rgba(c) or utils.format_color_rgb(c))
area:queue_draw()
end
end
function entry:on_changed()
local s = utils.sanitize_color(entry.text)
c = utils.parse_color_rgba(s)
area:queue_draw()
end
return {
widget = widget,
set_data = function(x)
local s = utils.sanitize_color(x)
c = utils.parse_color_rgba(s)
area:queue_draw()
entry:set_text(s)
end
}
end
}
local ServiceView = function(self)
local switch = Gtk.Switch { id = "autostart_enabled", sensitive = false }
local switch = Gtk.Switch {
id = "autostart_enabled",
sensitive = false,
hexpand = false
}
local method = ComboBoxFixed {
{ "None" },
{ "SystemD User Service" },
{ "InitD Entry" },
{ "Desktop Entry" }
"None",
"SystemD User Service",
"InitD Entry",
"Desktop Entry"
}
method.on_changed = function(box)
local opt = box:get_active_text()
@@ -107,36 +431,62 @@ return function()
end
-- TODO handle enable here
end
return ConfigView { { "Enabled", switch }, { "Autostart Method", method } }
return ConfigView {
{ "Enabled", Gtk.Box { Gtk.Box { hexpand = true }, switch } },
{ "Autostart Method", method }
}
end
local ProfileView = function(name)
local self = { name = name }
local notebook = Gtk.Notebook {
expand = true,
{ tab_label = "Global Options",
Gtk.ScrolledWindow {
shadow_type = "IN",
Gtk.Box {}
local args = {}
for k, v in pairs(mappings) do
local layout = {}
for _, e in ipairs(v) do
if type(e) == "table" then
local fields = {}
local ftypes = type(e.field_type) == "table" and e.field_type or { e.field_type }
local fattrs = type(e.field_type) == "table" and e.field_attrs or { e.field_attrs }
if not fattrs then fattrs = {} end
for i, f in ipairs(ftypes) do
local entry = widget_generators[f](fattrs[i] or {})
fields[#fields + 1] = entry.widget
end
fields.orientation = "VERTICAL"
fields.spacing = 2
local fwidget = {
e.description,
#fields > 1 and
Gtk.Frame {
label = fattrs.frame_label,
apply {
margin_left = 4,
margin_right = 4,
margin_top = 4,
margin_bottom = 4,
Gtk.Box(fields)
}
} or Gtk.Box(fields)
}
if not e.advanced then
layout[#layout + 1] = fwidget
else
if not layout.advanced then layout.advanced = {} end
layout.advanced[#layout.advanced + 1] = fwidget
end
end
end
args[#args + 1] = { tab_label = v.name, ConfigView(layout) }
end
args[#args + 1] = {
tab_label = "Autostart",
name ~= "Default" and ServiceView(self) or
Gtk.Label {
label = "Autostart options are not available for the default user profile."
}
},
{ tab_label = "Smoothing Options",
Gtk.ScrolledWindow {
shadow_type = "IN",
Gtk.Box {}
}
},
{ tab_label = "Module Options",
Gtk.ScrolledWindow {
shadow_type = "IN",
Gtk.Box {}
}
},
{ tab_label = "Autostart",
name ~= "Default" and ServiceView(self) or Gtk.Label {
label = "Autostart options are not available for the default user profile." }
}
}
args.expand = true
notebook = Gtk.Notebook(args)
notebook:show_all()
self.widget = notebook
function self:rename(new)
@@ -152,7 +502,7 @@ return function()
view_registry[default_entry[ItemColumn.PROFILE]] = ProfileView(default_entry[ItemColumn.PROFILE])
item_store:append(default_entry)
local window = Gtk.Window {
window = Gtk.Window {
title = "GLava Config",
default_width = 320,
default_height = 200,
@@ -206,27 +556,31 @@ return function()
Gtk.Box {
orientation = "HORIZONTAL",
spacing = 4,
Gtk.Alignment {
xscale = 0,
Gtk.Box {
homogeneous = true,
Gtk.Button {
id = "reload",
label = "Reload",
image = Gtk.Image { stock = Gtk.STOCK_REFRESH }
},
Gtk.Button {
id = "add",
label = "New",
image = Gtk.Image { stock = Gtk.STOCK_NEW },
},
Gtk.Button {
id = "remove",
label = "Delete",
sensitive = false,
image = Gtk.Image { stock = Gtk.STOCK_DELETE },
}
}
apply {
hexpand = false,
homogeneous = true,
(function()
local box = Gtk.Box {
Gtk.Button {
id = "reload",
label = "Reload",
image = Gtk.Image { stock = Gtk.STOCK_REFRESH }
},
Gtk.Button {
id = "add",
label = "New",
image = Gtk.Image { stock = Gtk.STOCK_NEW },
},
Gtk.Button {
id = "remove",
label = "Delete",
sensitive = false,
image = Gtk.Image { stock = Gtk.STOCK_DELETE },
}
}
box:get_style_context():add_class("linked")
return box
end)()
}
},
},
@@ -260,7 +614,7 @@ return function()
end
return profile_name
end
function window.child.view:on_row_activated(path, column)
local name = item_store[path][ItemColumn.PROFILE]
window.child.stack_view:set_visible_child_name(name)
@@ -293,7 +647,7 @@ return function()
item_store[path][ItemColumn.ENABLED] =
view_registry[item_store[path][ItemColumn.PROFILE]].widget.child.autostart_enabled.active
end
function window.child.add:on_clicked()
local profile_name = unique_profile("New Profile")
local entry = {
@@ -307,7 +661,7 @@ return function()
view_registry[profile_name] = view
window.child.stack_view:add_named(view.widget, profile_name);
end
function window.child.remove:on_clicked()
local dialog = Gtk.Dialog {
title = "Confirmation",

View File

@@ -74,7 +74,7 @@ void xwin_assign_icon_bmp(struct gl_wcb* wcb, void* impl, const char* path) {
/* Obtain image data pointer from offset */
const char* data = (const char*) (((const uint8_t*) header) + header->offset);
/* Assign icon using the older WMHints. Most window managers don't actually use this. */
XWMHints hints = {};
hints.flags = IconPixmapHint;

BIN
resources/transparent.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 690 B

View File

@@ -51,7 +51,7 @@
/* Window geometry (x, y, width, height) */
#request setgeometry 0 0 800 600
/* Window background color (RGB format).
/* Window background color (RGBA format).
Does not work with `setopacity "xroot"` */
#request setbg 00000000