2020-11-10 02:15:36 +00:00
|
|
|
const Bytes = imports.byteArray
|
|
|
|
const Gio = imports.gi.Gio
|
|
|
|
const GLib = imports.gi.GLib
|
|
|
|
const St = imports.gi.St
|
2021-05-10 22:03:41 +00:00
|
|
|
const Main = imports.ui.main
|
|
|
|
const Me = imports.misc.extensionUtils.getCurrentExtension()
|
|
|
|
const Convenience = Me.imports.convenience
|
2020-11-10 02:15:36 +00:00
|
|
|
|
|
|
|
const SETTINGS = Convenience.getSettings()
|
|
|
|
const WM_PREFS = Convenience.getPreferences()
|
|
|
|
|
2021-05-10 22:03:41 +00:00
|
|
|
const GTK_VERSIONS = [3, 4]
|
|
|
|
const USER_CONFIGS = GLib.get_user_config_dir()
|
|
|
|
|
|
|
|
function filePath(parts) {
|
|
|
|
const parse = part => part ? part.replace(/^@/, '') : ''
|
|
|
|
const paths = [Me.path].concat(parts).map(parse)
|
|
|
|
|
|
|
|
return GLib.build_filenamev(paths)
|
|
|
|
}
|
|
|
|
|
|
|
|
function userStylesPath(version) {
|
|
|
|
return GLib.build_filenamev([USER_CONFIGS, `gtk-${version}.0`, 'gtk.css'])
|
|
|
|
}
|
2020-11-10 02:15:36 +00:00
|
|
|
|
|
|
|
function fileExists(path) {
|
|
|
|
return GLib.file_test(path, GLib.FileTest.EXISTS)
|
|
|
|
}
|
|
|
|
|
|
|
|
function getGioFile(path) {
|
2021-05-10 22:03:41 +00:00
|
|
|
const absPath = filePath(path)
|
2020-11-10 02:15:36 +00:00
|
|
|
|
|
|
|
if (fileExists(absPath)) {
|
|
|
|
return Gio.file_new_for_path(absPath)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function getFileContents(path) {
|
|
|
|
if (fileExists(path)) {
|
|
|
|
const contents = GLib.file_get_contents(path)
|
|
|
|
return Bytes.toString(contents[1])
|
|
|
|
} else {
|
|
|
|
return ''
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function setFileContents(path, contents) {
|
2021-04-12 21:48:16 +00:00
|
|
|
if (!fileExists(path)) {
|
|
|
|
const dirname = GLib.path_get_dirname(path)
|
|
|
|
GLib.mkdir_with_parents(dirname, parseInt('0700', 8))
|
|
|
|
}
|
|
|
|
|
2020-11-10 02:15:36 +00:00
|
|
|
GLib.file_set_contents(path, contents)
|
|
|
|
}
|
|
|
|
|
2021-05-10 22:03:41 +00:00
|
|
|
function resetGtkStyles() {
|
|
|
|
GTK_VERSIONS.forEach(version => {
|
|
|
|
const filepath = userStylesPath(version)
|
|
|
|
let style = getFileContents(filepath)
|
2020-11-10 02:15:36 +00:00
|
|
|
|
2021-05-10 22:03:41 +00:00
|
|
|
style = style.replace(/\/\* UNITE ([\s\S]*?) UNITE \*\/\n/g, '')
|
|
|
|
style = style.replace(/@import.*unite@hardpixel\.eu.*css['"]\);\n/g, '')
|
2020-11-10 02:15:36 +00:00
|
|
|
|
2021-05-10 22:03:41 +00:00
|
|
|
setFileContents(filepath, style)
|
|
|
|
})
|
2020-11-10 02:15:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
var Signals = class Signals {
|
|
|
|
constructor() {
|
|
|
|
this.signals = new Map()
|
|
|
|
}
|
|
|
|
|
|
|
|
registerHandler(object, name, callback) {
|
|
|
|
const uid = GLib.uuid_string_random()
|
|
|
|
const key = `[signal ${name} uuid@${uid}]`
|
|
|
|
|
|
|
|
this.signals.set(key, {
|
|
|
|
object: object,
|
|
|
|
signalId: object.connect(name, callback)
|
|
|
|
})
|
|
|
|
|
|
|
|
return key
|
|
|
|
}
|
|
|
|
|
|
|
|
hasSignal(key) {
|
|
|
|
return this.signals.has(key)
|
|
|
|
}
|
|
|
|
|
|
|
|
connect(object, name, callback) {
|
|
|
|
return this.registerHandler(object, name, callback)
|
|
|
|
}
|
|
|
|
|
|
|
|
disconnect(key) {
|
|
|
|
if (this.hasSignal(key)) {
|
|
|
|
const data = this.signals.get(key)
|
|
|
|
data.object.disconnect(data.signalId)
|
|
|
|
|
|
|
|
this.signals.delete(key)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
disconnectMany(keys) {
|
|
|
|
keys.forEach(this.disconnect.bind(this))
|
|
|
|
}
|
|
|
|
|
|
|
|
disconnectAll() {
|
|
|
|
for (const key of this.signals.keys()) {
|
|
|
|
this.disconnect(key)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
var Settings = class Settings extends Signals {
|
|
|
|
getSettingObject(key) {
|
|
|
|
if (SETTINGS.exists(key)) {
|
|
|
|
return SETTINGS
|
|
|
|
} else {
|
|
|
|
return WM_PREFS
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
connect(name, callback) {
|
|
|
|
const object = this.getSettingObject(name)
|
|
|
|
return this.registerHandler(object, `changed::${name}`, callback)
|
|
|
|
}
|
|
|
|
|
|
|
|
get(key) {
|
|
|
|
const object = this.getSettingObject(key)
|
|
|
|
return object.getSetting(key)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-05-10 22:03:41 +00:00
|
|
|
var Feature = class Feature {
|
|
|
|
constructor(setting, callback) {
|
|
|
|
this._settingsKey = setting
|
|
|
|
this._checkActive = callback
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
var Features = class Features {
|
|
|
|
constructor() {
|
|
|
|
this.features = []
|
|
|
|
this.settings = new Settings()
|
|
|
|
}
|
|
|
|
|
|
|
|
add(klass) {
|
|
|
|
const feature = new klass()
|
|
|
|
this.features.push(feature)
|
|
|
|
|
|
|
|
const setting = feature._settingsKey
|
|
|
|
const checkCb = feature._checkActive
|
|
|
|
|
|
|
|
feature.activated = false
|
|
|
|
|
|
|
|
const isActive = () => {
|
|
|
|
return checkCb.call(null, this.settings.get(setting))
|
|
|
|
}
|
|
|
|
|
|
|
|
const onChange = () => {
|
|
|
|
const active = isActive()
|
|
|
|
|
|
|
|
if (active && !feature.activated) {
|
|
|
|
feature.activated = true
|
|
|
|
return feature.activate()
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!active && feature.activated) {
|
|
|
|
feature.activated = false
|
|
|
|
return feature.destroy()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
feature._doActivate = () => {
|
|
|
|
this.settings.connect(setting, onChange.bind(feature))
|
|
|
|
onChange()
|
|
|
|
}
|
|
|
|
|
|
|
|
feature._doDestroy = () => {
|
|
|
|
if (feature.activated) {
|
|
|
|
feature.destroy()
|
|
|
|
feature.activated = false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
activate() {
|
|
|
|
this.features.forEach(feature => feature._doActivate())
|
|
|
|
}
|
|
|
|
|
|
|
|
destroy() {
|
|
|
|
this.features.forEach(feature => feature._doDestroy())
|
|
|
|
this.settings.disconnectAll()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
class ShellStyle {
|
2020-11-10 02:15:36 +00:00
|
|
|
constructor(path) {
|
|
|
|
this.file = getGioFile(path)
|
|
|
|
}
|
|
|
|
|
|
|
|
get context() {
|
|
|
|
return St.ThemeContext.get_for_stage(global.stage)
|
|
|
|
}
|
|
|
|
|
|
|
|
get theme() {
|
|
|
|
return this.context.get_theme()
|
|
|
|
}
|
|
|
|
|
|
|
|
load() {
|
|
|
|
this.theme.load_stylesheet(this.file)
|
|
|
|
}
|
|
|
|
|
|
|
|
unload() {
|
|
|
|
this.theme.unload_stylesheet(this.file)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-05-10 22:03:41 +00:00
|
|
|
class WidgetStyle {
|
2020-11-10 02:15:36 +00:00
|
|
|
constructor(widget, style) {
|
|
|
|
this.widget = widget
|
|
|
|
this.style = style
|
|
|
|
}
|
|
|
|
|
|
|
|
get existing() {
|
|
|
|
return this.widget.get_style() || ''
|
|
|
|
}
|
|
|
|
|
|
|
|
load() {
|
|
|
|
const style = this.existing + this.style
|
|
|
|
this.widget.set_style(style)
|
|
|
|
}
|
|
|
|
|
|
|
|
unload() {
|
|
|
|
const style = this.existing.replace(this.style, '')
|
|
|
|
this.widget.set_style(style)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-05-10 22:03:41 +00:00
|
|
|
class GtkStyle {
|
|
|
|
constructor(version, name, data) {
|
|
|
|
const content = this.parse(data, version)
|
|
|
|
|
|
|
|
this.filepath = userStylesPath(version)
|
|
|
|
this.contents = `/* UNITE ${name} */\n${content}\n/* ${name} UNITE */\n`
|
2020-11-10 02:15:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
get existing() {
|
2021-04-12 21:48:16 +00:00
|
|
|
return getFileContents(this.filepath)
|
2020-11-10 02:15:36 +00:00
|
|
|
}
|
|
|
|
|
2021-05-10 22:03:41 +00:00
|
|
|
parse(data, ver) {
|
|
|
|
if (data.startsWith('@/')) {
|
|
|
|
const path = filePath(['styles', `gtk${ver}`, data])
|
|
|
|
return `@import url('${path}');`
|
|
|
|
} else {
|
|
|
|
return data
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-11-10 02:15:36 +00:00
|
|
|
load() {
|
|
|
|
const style = this.contents + this.existing
|
2021-04-12 21:48:16 +00:00
|
|
|
setFileContents(this.filepath, style)
|
2020-11-10 02:15:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
unload() {
|
|
|
|
const style = this.existing.replace(this.contents, '')
|
2021-04-12 21:48:16 +00:00
|
|
|
setFileContents(this.filepath, style)
|
2020-11-10 02:15:36 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-05-10 22:03:41 +00:00
|
|
|
class GtkStyles {
|
|
|
|
constructor(name, data, versions) {
|
|
|
|
const items = [].concat(versions).filter(ver => GTK_VERSIONS.includes(ver))
|
|
|
|
this.styles = items.map(ver => new GtkStyle(ver, name, data))
|
|
|
|
}
|
|
|
|
|
|
|
|
load() {
|
|
|
|
this.styles.forEach(style => style.load())
|
|
|
|
}
|
|
|
|
|
|
|
|
unload() {
|
|
|
|
this.styles.forEach(style => style.unload())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-11-10 02:15:36 +00:00
|
|
|
var Styles = class Styles {
|
|
|
|
constructor() {
|
|
|
|
this.styles = new Map()
|
|
|
|
}
|
|
|
|
|
|
|
|
hasStyle(name) {
|
|
|
|
return name && this.styles.has(name)
|
|
|
|
}
|
|
|
|
|
|
|
|
getStyle(name) {
|
|
|
|
return name && this.styles.get(name)
|
|
|
|
}
|
|
|
|
|
|
|
|
setStyle(name, object, ...args) {
|
|
|
|
if (!this.hasStyle(name)) {
|
|
|
|
const style = new object(...args)
|
|
|
|
style.load()
|
|
|
|
|
|
|
|
this.styles.set(name, style)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
deleteStyle(name) {
|
|
|
|
if (this.hasStyle(name)) {
|
|
|
|
const style = this.getStyle(name)
|
|
|
|
style.unload()
|
|
|
|
|
|
|
|
this.styles.delete(name)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-05-10 22:03:41 +00:00
|
|
|
addShellStyle(name, data) {
|
|
|
|
if (data.startsWith('@/')) {
|
|
|
|
this.deleteStyle(name)
|
|
|
|
this.setStyle(name, ShellStyle, data)
|
|
|
|
} else {
|
|
|
|
this.addWidgetStyle(name, Main.uiGroup, data)
|
|
|
|
}
|
2020-11-10 02:15:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
addWidgetStyle(name, widget, styles) {
|
|
|
|
this.deleteStyle(name)
|
|
|
|
this.setStyle(name, WidgetStyle, widget, styles)
|
|
|
|
}
|
|
|
|
|
2021-05-10 22:03:41 +00:00
|
|
|
addGtkStyle(name, contents, versions = GTK_VERSIONS) {
|
2020-11-10 02:15:36 +00:00
|
|
|
this.deleteStyle(name)
|
2021-05-10 22:03:41 +00:00
|
|
|
this.setStyle(name, GtkStyles, name, contents, versions)
|
2020-11-10 02:15:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
removeAll() {
|
|
|
|
for (const key of this.styles.keys()) {
|
|
|
|
this.deleteStyle(key)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-05-10 22:03:41 +00:00
|
|
|
resetGtkStyles()
|