pax_global_header 0000666 0000000 0000000 00000000064 14743561345 0014526 g ustar 00root root 0000000 0000000 52 comment=986439c5367eea41d24952e55684efc8899d8809
nwg-look-1.0.2/ 0000775 0000000 0000000 00000000000 14743561345 0013263 5 ustar 00root root 0000000 0000000 nwg-look-1.0.2/.github/ 0000775 0000000 0000000 00000000000 14743561345 0014623 5 ustar 00root root 0000000 0000000 nwg-look-1.0.2/.github/FUNDING.yml 0000664 0000000 0000000 00000000041 14743561345 0016433 0 ustar 00root root 0000000 0000000 github: nwg-piotr
liberapay: nwg
nwg-look-1.0.2/.gitignore 0000664 0000000 0000000 00000000477 14743561345 0015263 0 ustar 00root root 0000000 0000000 # Binaries for programs and plugins
*.exe
*.exe~
*.dll
*.so
*.dylib
# Test binary, built with `go test -c`
*.test
# Output of the go coverage tool, specifically when used with LiteIDE
*.out
# Dependency directories (remove the comment below to include it)
# vendor/
.idea/
bin/
nwg-look
*.glade~
stuff/#main.glade#
nwg-look-1.0.2/LICENSE 0000664 0000000 0000000 00000002101 14743561345 0014262 0 ustar 00root root 0000000 0000000 MIT License
Copyright (c) 2022-2025 Piotr Miller & Contributors
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
nwg-look-1.0.2/Makefile 0000664 0000000 0000000 00000002363 14743561345 0014727 0 ustar 00root root 0000000 0000000 # For alternate install dir (e.g. "/usr/local")
# specify PREFIX in make command:
# sudo make PREFIX=/usr/local install
# Defaults to "/usr" if not specified.
PREFIX ?= /usr
get:
go get github.com/gotk3/gotk3
go get github.com/gotk3/gotk3/gdk
go get "github.com/sirupsen/logrus"
build:
go build -v -o bin/nwg-look .
install:
mkdir -p $(DESTDIR)$(PREFIX)/share/nwg-look
mkdir -p $(DESTDIR)$(PREFIX)/share/nwg-look/langs
mkdir -p $(DESTDIR)$(PREFIX)/bin
mkdir -p $(DESTDIR)$(PREFIX)/share/applications
mkdir -p $(DESTDIR)$(PREFIX)/share/pixmaps
mkdir -p $(DESTDIR)$(PREFIX)/share/doc/nwg-look
mkdir -p $(DESTDIR)$(PREFIX)/share/licenses/nwg-look
cp stuff/main.glade $(DESTDIR)$(PREFIX)/share/nwg-look/
cp langs/* $(DESTDIR)$(PREFIX)/share/nwg-look/langs/
cp stuff/nwg-look.desktop $(DESTDIR)$(PREFIX)/share/applications/
cp stuff/nwg-look.svg $(DESTDIR)$(PREFIX)/share/pixmaps/
cp bin/nwg-look $(DESTDIR)$(PREFIX)/bin
cp README.md $(DESTDIR)$(PREFIX)/share/doc/nwg-look
cp LICENSE $(DESTDIR)$(PREFIX)/share/licenses/nwg-look
uninstall:
rm -r $(DESTDIR)$(PREFIX)/share/nwg-look
rm $(DESTDIR)$(PREFIX)/share/applications/nwg-look.desktop
rm $(DESTDIR)$(PREFIX)/share/pixmaps/nwg-look.svg
rm $(DESTDIR)$(PREFIX)/bin/nwg-look
run:
go run .
nwg-look-1.0.2/README.md 0000664 0000000 0000000 00000007406 14743561345 0014551 0 ustar 00root root 0000000 0000000
nwg-look
This application is a part of the [nwg-shell](https://nwg-piotr.github.io/nwg-shell) project.
Nwg-look is a GTK settings editor, designed to work properly in wlroots-based Wayland environment.
The look and feel is strongly influenced by [LXAppearance](https://wiki.lxde.org/en/LXAppearance),
but nwg-look is intended to free the user from a few inconveniences:
- It works natively on Wayland. You no longer need Xwayland, nor strange env variables for it to run.
- It applies gsettings directly, with no need to use
[workarounds](https://github.com/swaywm/sway/wiki/GTK-3-settings-on-Wayland). You don't need to set
gsettings in the sway config file. You don't need the `import-gsettings` script.

## Dependencies
- go (build dependency)
- gtk3
- [xcur2png](https://github.com/eworm-de/xcur2png)
- gsettings
Depending on your distro, you may also need to install
[gotk3 dependencies](https://github.com/gotk3/gotk3#installation).
## Installation
[](https://repology.org/project/nwg-look/versions)
If nwg-look has not yet been packaged for your Linux distribution:
1. Clone the repository, cd into it.
2. `make build`
3. `sudo make install`
## Usage
```text
$ nwg-look -h
Usage of nwg-look:
-a Apply stored gsetting and quit
-d turn on Debug messages
-r Restore default values and quit
-v display Version information
-x eXport config files and quit
```
The `-a` flag has been added just in case. When you press the "Apply" button, in addition to applying the changes, a backup file is also created. You may apply gsetting again w/o running the GUI, by just `nwg-look -a`. No idea if it's going to be useful in real life. ;)
### Usage in sway
The default way to apply GTK setting on [sway](https://github.com/swaywm/sway) Wayland compositor has been
described in the [GTK 3 settings on Wayland](https://github.com/swaywm/sway/wiki/GTK-3-settings-on-Wayland)
Wiki section. **You no longer need it**. Nwg-look loads and saves gsettings values directly, and does not
care about the `~/.config/gtk-3.0/settings.ini` file. It only exports your settings to it, unless you use
the `-n` flag.
Therefore, if your sway config file contains either
```text
set $gnome-schema org.gnome.desktop.interface
exec_always {
gsettings set $gnome-schema gtk-theme 'Your theme'
gsettings set $gnome-schema icon-theme 'Your icon theme'
gsettings set $gnome-schema cursor-theme 'Your cursor Theme'
gsettings set $gnome-schema font-name 'Your font name'
}
```
or if you use the `import-gsettings` script:
```text
exec_always import-gsettings
```
to parse and apply the settings.ini file, **remove these lines**.
## Backward compatibility
Some gsetting keys have no direct counterparts in the Gtk.Settings type. While exporting
the settings.ini file, nwg-look uses the most similar values:
| gsettings | Gtk.Settings |
| --------- | ------------ |
| `font-hinting` | `gtk-xft-hintstyle` |
| `font-antialiasing` | `gtk-xft-antialias` |
| `font-rgba-order` | `gtk-xft-rgba` |
Some **Other** settings have been left just for LXAppearance compatibility, and possible
use of your settings.ini file elsewhere:
- Toolbar style
- Toolbar icon size
have been deprecated since GTK 3.10, and the values are ignored.
- Show button images
- Show menu images
have been deprecated since GTK 3.10, and have no corresponding gsettings values.
- Enable event sounds
- Enable input feedback sounds
don't seem to change anything in non-GNOME environment.
nwg-look-1.0.2/go.mod 0000664 0000000 0000000 00000000311 14743561345 0014364 0 ustar 00root root 0000000 0000000 module github.com/nwg-piotr/nwg-look
go 1.23
require (
github.com/gotk3/gotk3 v0.6.5-0.20240618185848-ff349ae13f56
github.com/sirupsen/logrus v1.9.3
)
require golang.org/x/sys v0.29.0 // indirect
nwg-look-1.0.2/go.sum 0000664 0000000 0000000 00000003202 14743561345 0014413 0 ustar 00root root 0000000 0000000 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/gotk3/gotk3 v0.6.5-0.20240618185848-ff349ae13f56 h1:eR+xxC8qqKuPMTucZqaklBxLIT7/4L7dzhlwKMrDbj8=
github.com/gotk3/gotk3 v0.6.5-0.20240618185848-ff349ae13f56/go.mod h1:/hqFpkNa9T3JgNAE2fLvCdov7c5bw//FHNZrZ3Uv9/Q=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
nwg-look-1.0.2/langs/ 0000775 0000000 0000000 00000000000 14743561345 0014367 5 ustar 00root root 0000000 0000000 nwg-look-1.0.2/langs/en_US.json 0000664 0000000 0000000 00000003276 14743561345 0016303 0 ustar 00root root 0000000 0000000 {
"apply": "Apply",
"button": "Button",
"check-button": "Check button",
"clear": "Clear",
"close": "Close",
"color-scheme": "Color scheme",
"cursor-size": "Cursor size",
"cursor-theme-preview": "Cursor theme preview",
"default": "Default",
"default-font": "Default font",
"deprecated": "deprecated",
"deprecated-since-gtk-310": "Deprecated since GTK 3.10",
"enable-event-sounds": "Enable event sounds",
"enable-input-feedback-sounds": "Enable input feedback sounds",
"entry": "Entry",
"files-to-export": "Files to export",
"font": "Font",
"font-antialiasing": "Font antialiasing",
"font-hinting": "Font hinting",
"font-rgba-order": "Font RGBA order",
"font-settings": "Font settings",
"full": "Full",
"grayscale": "Greyscale",
"icon-theme": "Icon theme",
"icon-theme-preview": "Icon theme preview",
"icons": "Icons",
"ignored": "ignored",
"large": "Large",
"medium": "Medium",
"mouse-cursor": "Mouse cursor",
"none": "None",
"other": "Other",
"other-settings": "Other settings",
"prefer-dark": "Prefer dark",
"prefer-light": "Prefer light",
"preferences": "Preferences",
"program-settings": "Program settings",
"radio-button": "Radio button",
"show-button-images": "Show button images",
"show-menu-images": "Show menu images",
"slight": "Slight",
"small": "Small",
"sound-effects": "Sound effects",
"text": "Text",
"text-below-icons": "Text below icons",
"text-next-to-icons": "Text next to icons",
"text-scaling-factor": "Text scaling factor",
"toolbar-icon-size": "Toolbar icon size",
"toolbar-style": "Toolbar style",
"ui-settings": "UI settings",
"widgets": "Widgets",
"widget-style-preview": "Widget style preview"
}
nwg-look-1.0.2/langs/ja_JP.json 0000664 0000000 0000000 00000004207 14743561345 0016250 0 ustar 00root root 0000000 0000000 {
"apply": "適用",
"button": "ボタン",
"check-button": "チェックボタン",
"clear": "設定をクリア",
"close": "閉じる",
"color-scheme": "カラースキーマ",
"cursor-size": "カーソルサイズ",
"cursor-theme-preview": "カーソルテーマ プレビュー",
"default": "既定",
"default-font": "既定のフォント",
"deprecated": "非推奨",
"deprecated-since-gtk-310": "GTK 3.10 以降で非推奨",
"enable-event-sounds": "イベントサウンドを有効",
"enable-input-feedback-sounds": "入力フィードバック音を有効",
"entry": "エントリ",
"files-to-export": "エクスポートするファイル",
"font": "フォント",
"font-antialiasing": "フォントアンチエイリアス",
"font-hinting": "フォントヒンティング",
"font-rgba-order": "フォント RGBA の順序",
"font-settings": "フォント設定",
"full": "全体",
"grayscale": "グレースケール",
"icon-theme": "アイコンテーマ",
"icon-theme-preview": "アイコンテーマ プレビュー",
"icons": "アイコン",
"ignored": "無視される",
"large": "大",
"medium": "中",
"mouse-cursor": "マウスカーソル",
"none": "なし",
"other": "その他",
"other-settings": "その他の設定",
"prefer-dark": "ダークモード優先",
"prefer-light": "ライトモード優先",
"preferences": "設定",
"program-settings": "プログラム設定",
"radio-button": "ラジオボタン",
"show-button-images": "ボタン画像を表示",
"show-menu-images": "メニュー画像を表示",
"slight": "わずか",
"small": "小",
"sound-effects": "サウンドエフェクト",
"text": "テキスト",
"text-below-icons": "アイコンの下にテキスト",
"text-next-to-icons": "アイコンの隣にテキスト",
"text-scaling-factor": "テキストスケーリング係数",
"toolbar-icon-size": "ツールバーのアイコンサイズ",
"toolbar-style": "ツールバーのスタイル",
"ui-settings": "UI 設定",
"widgets": "ウィジェット",
"widget-style-preview": "ウィジェットスタイル プレビュー"
}
nwg-look-1.0.2/langs/pl_PL.json 0000664 0000000 0000000 00000003567 14743561345 0016303 0 ustar 00root root 0000000 0000000 {
"apply": "Zastosuj",
"button": "Przycisk",
"check-button": "Przycisk wyboru",
"clear": "Wyczyść",
"close": "Zamknij",
"color-scheme": "Schemat kolorów",
"cursor-size": "Rozmiar kursora",
"cursor-theme-preview": "Podgląd motywu kursora",
"default": "Domyślny",
"default-font": "Domyślna czcionka",
"deprecated": "przestarzałe",
"deprecated-since-gtk-310": "Przestarzałe od GTK 3.10",
"enable-event-sounds": "Włącz dźwięki zdarzeń",
"enable-input-feedback-sounds": "Włącz dźwięki zwrotne wejścia",
"entry": "Pozycja",
"files-to-export": "Pliki do wyeksportowania",
"font": "Czcionka",
"font-antialiasing": "Wygładzanie czcionki",
"font-hinting": "Hinting czcionki",
"font-rgba-order": "Kolejność RGBA czcionki",
"font-settings": "Ustawienia czcionki",
"full": "Pełny",
"grayscale": "Skala szarości",
"icon-theme": "Motyw ikon",
"icon-theme-preview": "Podgląd motywu ikon",
"icons": "Ikony",
"ignored": "ignorowane",
"large": "Duży",
"medium": "Średni",
"mouse-cursor": "Kursor myszy",
"none": "Brak",
"other": "Inne",
"other-settings": "Inne ustawienia",
"prefer-dark": "Preferuj ciemny",
"prefer-light": "Preferuj jasny",
"preferences": "Ustawienia",
"program-settings": "Ustawienia programu",
"radio-button": "Przycisk opcji",
"show-button-images": "Pokazuj ikony przycisku",
"show-menu-images": "Pokazuj ikony menu",
"slight": "Lekki",
"small": "Mały",
"sound-effects": "Efekty dźwiękowe",
"text": "Tekst",
"text-below-icons": "Tekst poniżej ikon",
"text-next-to-icons": "Tekst obok ikon",
"text-scaling-factor": "Współczynnik skalowania tekstu",
"toolbar-icon-size": "Rozmiar ikony paska narzędzi",
"toolbar-style": "Styl paska narzędzi",
"ui-settings": "Ustawienia interfejsu użytkownika",
"widgets": "Widżety",
"widget-style-preview": "Podgląd stylu widżetów"
}
nwg-look-1.0.2/langs/ru_RU.json 0000664 0000000 0000000 00000005164 14743561345 0016324 0 ustar 00root root 0000000 0000000 {
"apply": "Применить",
"button": "Кнопка",
"check-button": "Чекбокс",
"clear": "Очистить",
"close": "Закрыть",
"color-scheme": "Цветовая схема",
"cursor-size": "Размер курсора",
"cursor-theme-preview": "Предпросмотр темы курсора",
"default": "По умолчанию",
"default-font": "Шрифт по умолчанию",
"deprecated": "не поддерживается",
"deprecated-since-gtk-310": "Не поддерживается с GTK 3.10",
"enable-event-sounds": "Включить звуки событий",
"enable-input-feedback-sounds": "Включить звуки обратной связи ввода",
"entry": "Позиция",
"files-to-export": "Файлы для экспорта",
"font": "Шрифт",
"font-antialiasing": "Сглаживание шрифта",
"font-hinting": "Хинтинг шрифта",
"font-rgba-order": "Порядок шрифта RGBA",
"font-settings": "Настройки шрифта",
"full": "Полный",
"grayscale": "Градации серого",
"icon-theme": "Тема значков",
"icon-theme-preview": "Предпросмотр темы значков",
"icons": "Значки",
"ignored": "игнорируется",
"large": "Большой",
"medium": "Средний",
"mouse-cursor": "Курсор мыши",
"none": "Нет",
"other": "Прочее",
"other-settings": "Прочие настройки",
"prefer-dark": "Предпочитать темную",
"prefer-light": "Предпочитать светлую",
"preferences": "Настройки",
"program-settings": "Настройки программы",
"radio-button": "Переключатель",
"show-button-images": "Показывать изображения на кнопках",
"show-menu-images": "Показывать изображения меню",
"slight": "Легкий",
"small": "Маленький",
"sound-effects": "Звуковые эффекты",
"text": "Текст",
"text-below-icons": "Текст под значками",
"text-next-to-icons": "Текст рядом со значками",
"text-scaling-factor": "Коэффициент масштабирования текста",
"toolbar-icon-size": "Размер значков панели инструментов",
"toolbar-style": "Стиль панели инструментов",
"ui-settings": "Настройки интерфейса",
"widgets": "Виджеты",
"widget-style-preview": "Предпросмотр стиля виджетов"
}
nwg-look-1.0.2/langs/zh_CN.json 0000664 0000000 0000000 00000003474 14743561345 0016273 0 ustar 00root root 0000000 0000000 {
"apply": "应用",
"button": "按钮",
"check-button": "复选按钮",
"clear": "清除设置",
"close": "关闭",
"color-scheme": "颜色方案",
"cursor-size": "光标大小",
"cursor-theme-preview": "光标样式预览",
"default": "默认",
"default-font": "默认字体",
"deprecated": "已弃用",
"deprecated-since-gtk-310": "自 GTK 3.10 起已弃用",
"enable-event-sounds": "启用事件声音",
"enable-input-feedback-sounds": "启用输入反馈声音",
"entry": "输入框",
"files-to-export": "要导出的文件",
"font": "字体",
"font-antialiasing": "字体抗锯齿",
"font-hinting": "字体微调",
"font-rgba-order": "字体 RGBA 顺序",
"font-settings": "字体设置",
"full": "完全",
"grayscale": "灰度",
"icon-theme": "图标主题",
"icon-theme-preview": "图标主题预览",
"icons": "图标",
"ignored": "已忽略",
"large": "大",
"medium": "中等",
"mouse-cursor": "鼠标光标",
"none": "无",
"other": "其他",
"other-settings": "其他设置",
"prefer-dark": "倾向暗色",
"prefer-light": "倾向亮色",
"preferences": "偏好设置",
"program-settings": "程序设置",
"radio-button": "单选按钮",
"show-button-images": "显示按钮图片",
"show-menu-images": "显示菜单图片",
"slight": "轻微",
"small": "小",
"sound-effects": "声音效果",
"text": "文本",
"text-below-icons": "文本在图标下方",
"text-next-to-icons": "文本在图标侧面",
"text-scaling-factor": "文本缩放因子",
"toolbar-icon-size": "工具栏图标大小",
"toolbar-style": "工具栏样式",
"ui-settings": "界面设置",
"widgets": "组件",
"widget-style-preview": "组件风格预览"
}
nwg-look-1.0.2/main.go 0000664 0000000 0000000 00000025365 14743561345 0014551 0 ustar 00root root 0000000 0000000 /*
GTK settings editor adapted to work in the sway / wlroots environment
Project: https://github.com/nwg-piotr/nwg-look
Author's email: nwg.piotr@gmail.com
Copyright (c) 2022-2025 Piotr Miller & Contributors
License: MIT
*/
package main
import (
"flag"
"fmt"
"os"
"path/filepath"
"strings"
log "github.com/sirupsen/logrus"
"github.com/gotk3/gotk3/gdk"
"github.com/gotk3/gotk3/gtk"
)
const version = "1.0.2"
var (
preferences programSettings
originalGtkConfig []string // we will append not parsed settings.ini lines from here
gtkConfig gtkConfigProperties
gtkSettings *gtk.Settings
gsettings gsettingsValues
dataDirs []string
cursorThemes map[string]string // theme name to path
cursorThemeNames map[string]string // theme name to theme folder name
viewport *gtk.Viewport
scrolledWindow *gtk.ScrolledWindow
listBox *gtk.ListBox
menuBar *gtk.MenuBar
themeSettingsSelector *gtk.Grid
grid *gtk.Grid
preview *gtk.Frame
cursorSizeSelector *gtk.Box
rowToFocus *gtk.ListBoxRow
voc map[string]string
gtkThemePaths map[string]string // theme name to path
)
type programSettings struct {
ExportSettingsIni bool `json:"export-settings-ini"`
ExportGtkRc20 bool `json:"export-gtkrc-20"`
ExportIndexTheme bool `json:"export-index-theme"`
ExportXsettingsd bool `json:"export-xsettingsd"`
ExportGtk4Symlinks bool `json:"export-gtk4-symlinks"`
}
func programSettingsNewWithDefaults() programSettings {
p := programSettings{}
p.ExportSettingsIni = true
p.ExportGtkRc20 = true
p.ExportIndexTheme = true
p.ExportXsettingsd = true
p.ExportGtk4Symlinks = true
return p
}
type gtkConfigProperties struct {
themeName string
iconThemeName string
fontName string
cursorThemeName string
cursorThemeSize int
toolbarStyle string
toolbarIconSize string
buttonImages bool
menuImages bool
enableEventSounds bool
enableInputFeedbackSounds bool
xftAntialias int
fontAntialiasing string
xftDpi int
xftHinting int
xftHintstyle string
xftRgba string
applicationPreferDarkTheme bool
}
func gtkConfigPropertiesNewWithDefaults() gtkConfigProperties {
s := gtkConfigProperties{}
// 'ignored' and 'deprecated' values left for lxappearance compatibility
s.themeName = "Adwaita"
s.iconThemeName = "Adwaita"
s.fontName = "Sans 10"
s.cursorThemeName = ""
s.cursorThemeSize = 0
s.toolbarStyle = "GTK_TOOLBAR_ICONS" // ignored
s.toolbarIconSize = "GTK_ICON_SIZE_LARGE_TOOLBAR" // ignored
s.buttonImages = false // deprecated
s.menuImages = false // deprecated
s.enableEventSounds = true
s.enableInputFeedbackSounds = true
s.xftAntialias = -1
s.applicationPreferDarkTheme = false
val, err := getGsettingsValue("org.gnome.desktop.interface", "font-antialiasing")
if err == nil {
s.fontAntialiasing = val
} else {
log.Warn(err)
}
s.xftHinting = -1
s.xftHintstyle = "hintmedium"
s.xftRgba = "none"
return s
}
type gsettingsValues struct {
// org.gnome.desktop.interface
gtkTheme string
iconTheme string
fontName string
cursorTheme string
cursorSize int
toolbarStyle string
toolbarIconsSize string
fontHinting string
fontAntialiasing string
fontRgbaOrder string
textScalingFactor float64
colorScheme string
// org.gnome.desktop.sound
eventSounds bool
inputFeedbackSounds bool
}
func gsettingsNewWithDefaults() gsettingsValues {
g := gsettingsValues{}
g.gtkTheme = "Adwaita"
g.iconTheme = "Adwaita"
g.fontName = "Sans 10"
g.cursorTheme = "Adwaita"
g.cursorSize = 24
g.toolbarStyle = "both-horiz"
g.toolbarIconsSize = "large"
g.fontHinting = "medium"
g.fontAntialiasing = "grayscale"
g.fontRgbaOrder = "rgb"
g.textScalingFactor = 1.0
g.eventSounds = true
g.inputFeedbackSounds = false
g.colorScheme = "default"
return g
}
func displayThemes() {
destroyContent()
rowToFocus = nil
listBox = setUpThemeListBox(gsettings.gtkTheme)
viewport.Add(listBox)
menuBar.Deactivate()
if rowToFocus != nil {
rowToFocus.GrabFocus()
}
preview = setUpWidgetsPreview()
grid.Attach(preview, 1, 1, 1, 1)
themeSettingsSelector = setUpThemeSettingsForm(gsettings.fontName)
themeSettingsSelector.SetProperty("vexpand", true)
themeSettingsSelector.SetProperty("valign", gtk.ALIGN_START)
grid.Attach(themeSettingsSelector, 1, 2, 1, 1)
viewport.ShowAll()
grid.ShowAll()
}
func displayIconThemes() {
destroyContent()
rowToFocus = nil
listBox = setUpIconThemeListBox(gsettings.iconTheme)
viewport.Add(listBox)
menuBar.Deactivate()
if rowToFocus != nil {
rowToFocus.GrabFocus()
}
preview = setUpIconsPreview()
grid.Attach(preview, 1, 1, 1, 1)
viewport.ShowAll()
grid.ShowAll()
}
func displayCursorThemes() {
destroyContent()
rowToFocus = nil
listBox = setUpCursorThemeListBox(gsettings.cursorTheme)
viewport.Add(listBox)
menuBar.Deactivate()
if rowToFocus != nil {
rowToFocus.GrabFocus()
}
preview = setUpCursorsPreview(cursorThemes[gsettings.cursorTheme])
grid.Attach(preview, 1, 1, 1, 1)
cursorSizeSelector = setUpCursorSizeSelector()
grid.Attach(cursorSizeSelector, 1, 2, 1, 1)
viewport.ShowAll()
grid.ShowAll()
}
func displayFontSettingsForm() {
destroyContent()
preview = setUpFontSettingsForm()
grid.Attach(preview, 0, 1, 1, 1)
menuBar.Deactivate()
grid.ShowAll()
scrolledWindow.Hide()
}
func displayOtherSettingsForm() {
destroyContent()
preview = setUpOtherSettingsForm()
grid.Attach(preview, 0, 1, 1, 1)
menuBar.Deactivate()
grid.ShowAll()
scrolledWindow.Hide()
}
func displayProgramSettingsForm() {
destroyContent()
preview = setUpProgramSettingsForm()
grid.Attach(preview, 0, 1, 1, 1)
menuBar.Deactivate()
grid.ShowAll()
scrolledWindow.Hide()
}
func destroyContent() {
if listBox != nil {
listBox.Destroy()
}
if preview != nil {
preview.Destroy()
}
if themeSettingsSelector != nil {
themeSettingsSelector.Destroy()
}
if cursorSizeSelector != nil {
cursorSizeSelector.Destroy()
}
}
func main() {
var debug = flag.Bool("d", false, "turn on Debug messages")
var displayVersion = flag.Bool("v", false, "display Version information")
var applyGs = flag.Bool("a", false, "Apply stored gsetting and quit")
var restoreDefaults = flag.Bool("r", false, "Restore default values and quit")
var exportConfigs = flag.Bool("x", false, "eXport config files and quit")
flag.Parse()
if *displayVersion {
fmt.Printf("nwg-look version %s\n", version)
os.Exit(0)
}
if *debug {
log.SetLevel(log.DebugLevel)
}
loadPreferences()
lang := detectLang()
log.Infof("lang: %s", lang)
voc = loadVocabulary(lang)
// initialize gsettings type with default gtk values
gsettings = gsettingsNewWithDefaults()
// initialize gtkConfigProperties type with default gtk.Settings values
gtkConfig = gtkConfigPropertiesNewWithDefaults()
if *restoreDefaults {
fmt.Print("Restore default gtk settings? y/N ")
var input string
fmt.Scanln(&input)
fmt.Println(input)
if strings.ToUpper(input) == "Y" {
applyGsettings()
saveGsettingsBackup()
if preferences.ExportSettingsIni {
saveGtkIni()
}
if preferences.ExportGtkRc20 {
saveGtkRc20()
}
if preferences.ExportIndexTheme {
saveIndexTheme()
}
if preferences.ExportXsettingsd {
saveXsettingsd()
}
if preferences.ExportGtk4Symlinks {
linkGtk4Stuff()
} else {
clearGtk4Symlinks()
}
}
os.Exit(0)
}
readGsettings()
if *applyGs || *exportConfigs {
if *applyGs {
applyGsettingsFromFile()
}
if *exportConfigs {
if preferences.ExportSettingsIni {
saveGtkIni()
}
if preferences.ExportGtkRc20 {
saveGtkRc20()
}
if preferences.ExportIndexTheme {
saveIndexTheme()
}
if preferences.ExportXsettingsd {
saveXsettingsd()
}
if preferences.ExportGtk4Symlinks {
_, gtkThemePaths = getThemeNames()
linkGtk4Stuff()
}
}
os.Exit(0)
}
dataDirs = getDataDirs()
cursorThemes, cursorThemeNames = getCursorThemes()
gtk.Init(nil)
// update gtkConfig from gtk-3.0/settings.ini
if preferences.ExportSettingsIni {
loadGtkConfig()
}
gtkSettings, _ = gtk.SettingsGetDefault()
gladeFile := ""
for _, d := range dataDirs {
gladeFile = filepath.Join(d, "/nwg-look/main.glade")
if pathExists(gladeFile) {
break
}
}
builder, _ := gtk.BuilderNewFromFile(gladeFile)
win, _ := getWindow(builder, "window")
win.Connect("destroy", func() {
gtk.MainQuit()
})
win.Connect("key-release-event", func(window *gtk.Window, event *gdk.Event) bool {
key := &gdk.EventKey{Event: event}
if key.KeyVal() == gdk.KEY_Escape {
gtk.MainQuit()
return true
}
return false
})
viewport, _ = getViewPort(builder, "viewport-list")
scrolledWindow, _ = getScrolledWindow(builder, "scrolled-window")
grid, _ = getGrid(builder, "grid")
menuBar, _ = getMenuBar(builder, "menubar")
item1, _ := getMenuItem(builder, "item-widgets")
item1.SetLabel(voc["widgets"])
item1.Connect("button-release-event", displayThemes)
item2, _ := getMenuItem(builder, "item-icons")
item2.SetLabel(voc["icon-theme"])
item2.Connect("button-release-event", displayIconThemes)
item3, _ := getMenuItem(builder, "item-cursors")
item3.SetLabel(voc["mouse-cursor"])
item3.Connect("button-release-event", displayCursorThemes)
item4, _ := getMenuItem(builder, "item-font")
item4.SetLabel(voc["font"])
item4.Connect("button-release-event", displayFontSettingsForm)
item5, _ := getMenuItem(builder, "item-other")
item5.SetLabel(voc["other"])
item5.Connect("button-release-event", displayOtherSettingsForm)
item6, _ := getMenuItem(builder, "item-preferences")
item6.SetLabel(voc["preferences"])
item6.Connect("button-release-event", displayProgramSettingsForm)
btnClose, _ := getButton(builder, "btn-close")
btnClose.SetLabel(voc["close"])
btnClose.Connect("clicked", func() {
gtk.MainQuit()
})
btnApply, _ := getButton(builder, "btn-apply")
btnApply.SetLabel(voc["apply"])
btnApply.Connect("clicked", func() {
applyGsettings()
saveGsettingsBackup()
if preferences.ExportSettingsIni {
saveGtkIni()
}
if preferences.ExportGtkRc20 {
saveGtkRc20()
}
if preferences.ExportIndexTheme {
saveIndexTheme()
}
if preferences.ExportXsettingsd {
saveXsettingsd()
}
if preferences.ExportGtk4Symlinks {
linkGtk4Stuff()
}
savePreferences()
})
verLabel, _ := getLabel(builder, "version-label")
verLabel.SetMarkup(fmt.Sprintf("nwg-look v%s GitHub", version))
displayThemes()
win.ShowAll()
gtk.Main()
}
nwg-look-1.0.2/stuff/ 0000775 0000000 0000000 00000000000 14743561345 0014412 5 ustar 00root root 0000000 0000000 nwg-look-1.0.2/stuff/main.glade 0000664 0000000 0000000 00000022152 14743561345 0016336 0 ustar 00root root 0000000 0000000
nwg-look-1.0.2/stuff/nwg-look.desktop 0000664 0000000 0000000 00000000776 14743561345 0017554 0 ustar 00root root 0000000 0000000 [Desktop Entry]
Type=Application
Name=GTK Settings
Name[pl]=Ustawienia GTK
GenericName=Adjust Look and Feel
GenericName[pl]=Dopasuj ustawienia wyglądu
Comment=Customizes GTK3 look and feel settings
Comment[pl]=Dostosowuje ustawienia środowiska graficznego GTK3
Keywords=windows;preferences;settings;theme;style;appearance;look;
Keywords[pl]=okna;preferencje;ustawienia;motyw;styl;wygląd;
Icon=nwg-look
Exec=nwg-look
NotShowIn=GNOME;KDE;XFCE;MATE;
StartupNotify=true
Categories=GTK;Settings;DesktopSettings;
nwg-look-1.0.2/stuff/nwg-look.svg 0000664 0000000 0000000 00000024746 14743561345 0016705 0 ustar 00root root 0000000 0000000
nwg-look-1.0.2/tools.go 0000664 0000000 0000000 00000112304 14743561345 0014753 0 ustar 00root root 0000000 0000000 // tools
package main
import (
"bufio"
"encoding/json"
"fmt"
"io"
"os"
"os/exec"
"path/filepath"
"sort"
"strconv"
"strings"
"github.com/gotk3/gotk3/gtk"
log "github.com/sirupsen/logrus"
)
func configHome() string {
cHome := os.Getenv("XDG_CONFIG_HOME")
if cHome != "" {
return cHome
}
return filepath.Join(os.Getenv("HOME"), ".config/")
}
func loadPreferences() {
cH := configHome()
preferencesFile := filepath.Join(cH, "/nwg-look/config")
if !pathExists(preferencesFile) {
log.Infof("%s file not found, creating", preferencesFile)
makeDir(filepath.Join(cH, "/nwg-look/"))
preferences = programSettingsNewWithDefaults()
savePreferences()
} else {
file, err := os.Open(preferencesFile)
defer file.Close()
if err != nil {
fmt.Println(err.Error())
}
log.Info(">>> Loading preferences")
jsonParser := json.NewDecoder(file)
jsonParser.Decode(&preferences)
jsonData, err := json.Marshal(preferences)
if err == nil {
log.Debugf("Loaded preferences: %s", string(jsonData))
}
}
}
func savePreferences() {
preferencesFile := filepath.Join(configHome(), "/nwg-look/config")
jsonData, err := json.MarshalIndent(preferences, "", " ")
if err != nil {
log.Warn(err)
return
}
err = os.WriteFile(preferencesFile, jsonData, 0644)
if err == nil {
log.Debugf("Saved config: %s", string(jsonData))
}
}
func loadGtkConfig() {
// parse gtk settings file
originalGtkConfig = []string{}
configFile := filepath.Join(configHome(), "gtk-3.0/settings.ini")
if pathExists(configFile) {
lines, err := loadTextFile(configFile)
if err == nil {
log.Infof(">>> Parsing original %s", configFile)
} else {
log.Warnf("Couldn't load %s", configFile)
}
for _, line := range lines {
// In case users settings.ini had some lines we didn't expect,
// we'll append them back from here.
if !strings.HasPrefix(line, "[") {
originalGtkConfig = append(originalGtkConfig, line)
}
if !strings.HasPrefix(line, "[") && !strings.HasPrefix(line, "#") &&
strings.Contains(line, "=") {
parts := strings.Split(line, "=")
key := strings.TrimSpace(parts[0])
value := strings.TrimSpace(parts[1])
switch key {
case "gtk-theme-name":
gtkConfig.themeName = value
case "gtk-icon-theme-name":
gtkConfig.iconThemeName = value
case "gtk-font-name":
gtkConfig.fontName = value
case "gtk-cursor-theme-name":
gtkConfig.cursorThemeName = value
case "gtk-cursor-theme-size":
i := intValue(value)
if i != -1 {
gtkConfig.cursorThemeSize = i
} else {
gtkConfig.cursorThemeSize = 0
}
case "gtk-toolbar-style":
gtkConfig.toolbarStyle = value
case "gtk-toolbar-icon-size":
gtkConfig.toolbarIconSize = value
case "gtk-button-images":
gtkConfig.buttonImages = value == "1"
case "gtk-menu-images":
gtkConfig.menuImages = value == "1"
case "gtk-enable-event-sounds":
gtkConfig.enableEventSounds = value == "1"
case "gtk-enable-input-feedback-sounds":
gtkConfig.enableInputFeedbackSounds = value == "1"
case "gtk-xft-antialias":
gtkConfig.xftAntialias = intValue(value)
case "gtk-xft-hinting":
gtkConfig.xftHinting = intValue(value)
case "gtk-xft-hintstyle":
gtkConfig.xftHintstyle = value
case "gtk-xft-rgba":
gtkConfig.xftRgba = value
case "gtk-application-prefer-dark-theme":
gtkConfig.applicationPreferDarkTheme = value == "1"
default:
log.Warnf("Unsupported config key: %s", key)
}
}
}
} else {
log.Warnf("Could'n find %s", configFile)
}
log.Debugf("gtk-theme-name: %s", gtkConfig.themeName)
log.Debugf("gtk-icon-theme-name: %s", gtkConfig.iconThemeName)
log.Debugf("gtk-font-name: %s", gtkConfig.fontName)
log.Debugf("gtk-cursor-theme-name: %s", gtkConfig.cursorThemeName)
log.Debugf("gtk-cursor-theme-size: %v", gtkConfig.cursorThemeSize)
log.Debugf("gtk-toolbar-style: %s", gtkConfig.toolbarStyle)
log.Debugf("gtk-toolbar-icon-size: %s", gtkConfig.toolbarIconSize)
log.Debugf("gtk-button-images: %v", gtkConfig.buttonImages)
log.Debugf("gtk-menu-images: %v", gtkConfig.menuImages)
log.Debugf("gtk-enable-event-sounds: %v", gtkConfig.enableEventSounds)
log.Debugf("gtk-enable-input-feedback-sounds: %v", gtkConfig.enableInputFeedbackSounds)
log.Debugf("gtk-xft-antialias: %v", gtkConfig.xftAntialias)
log.Debugf("gtk-xft-hinting: %v", gtkConfig.xftHinting)
log.Debugf("gtk-xft-hintstyle: %v", gtkConfig.xftHintstyle)
log.Debugf("gtk-xft-rgba: %v", gtkConfig.xftRgba)
log.Debugf("gtk-application-prefer-dark-theme: %v", gtkConfig.applicationPreferDarkTheme)
}
func intValue(s string) int {
i, err := strconv.Atoi(s)
if err == nil {
return i
}
// -1 is default
return -1
}
func readGsettings() {
log.Info(">>> Reading gsettings")
val, err := getGsettingsValue("org.gnome.desktop.interface", "gtk-theme")
if err == nil {
gsettings.gtkTheme = val
log.Infof("gtk-theme: %s", gsettings.gtkTheme)
} else {
log.Warnf("Couldn't read gtk-theme, leaving default %s",
gsettings.gtkTheme)
}
val, err = getGsettingsValue("org.gnome.desktop.interface", "icon-theme")
if err == nil {
gsettings.iconTheme = val
log.Infof("icon-theme: %s", gsettings.iconTheme)
} else {
log.Warnf("Couldn't read icon-theme, leaving default %s",
gsettings.iconTheme)
}
val, err = getGsettingsValue("org.gnome.desktop.interface", "font-name")
if err == nil {
gsettings.fontName = val
log.Infof("font-name: %s", gsettings.fontName)
} else {
log.Warnf("Couldn't read font-name, leaving default %s",
gsettings.fontName)
}
val, err = getGsettingsValue("org.gnome.desktop.interface", "cursor-theme")
if err == nil {
gsettings.cursorTheme = val
log.Infof("cursor-theme: %s", gsettings.cursorTheme)
} else {
gsettings.cursorTheme = ""
log.Warnf("Couldn't read cursor-theme, leaving default %s",
gsettings.cursorTheme)
}
val, err = getGsettingsValue("org.gnome.desktop.interface", "cursor-size")
if err == nil {
v, e := strconv.Atoi(val)
if e == nil {
gsettings.cursorSize = v
log.Infof("cursor-size: %v", gsettings.cursorSize)
}
} else {
log.Warnf("Couldn't read cursorSize, leaving default %s",
gsettings.cursorSize)
}
val, err = getGsettingsValue("org.gnome.desktop.interface", "toolbar-style")
if err == nil {
gsettings.toolbarStyle = val
log.Infof("toolbar-style: %s", gsettings.toolbarStyle)
} else {
log.Warnf("Couldn't read toolbar-style, leaving default %s",
gsettings.toolbarStyle)
}
val, err = getGsettingsValue("org.gnome.desktop.interface", "toolbar-icons-size")
if err == nil {
gsettings.toolbarIconsSize = val
log.Infof("toolbar-icons-size: %s", gsettings.toolbarIconsSize)
} else {
log.Warnf("Couldn't read toolbar-icons-size, leaving default %s",
gsettings.toolbarIconsSize)
}
val, err = getGsettingsValue("org.gnome.desktop.interface", "font-hinting")
if err == nil {
gsettings.fontHinting = val
log.Infof("font-hinting: %s", gsettings.fontHinting)
} else {
log.Warnf("Couldn't read font-hinting, leaving default %s",
gsettings.fontHinting)
}
val, err = getGsettingsValue("org.gnome.desktop.interface", "font-antialiasing")
if err == nil {
gsettings.fontAntialiasing = val
log.Infof("font-antialiasing: %s", gsettings.fontAntialiasing)
} else {
log.Warnf("Couldn't read font-antialiasing, leaving default %s",
gsettings.fontAntialiasing)
}
val, err = getGsettingsValue("org.gnome.desktop.interface", "font-rgba-order")
if err == nil {
gsettings.fontRgbaOrder = val
log.Infof("font-rgba-order: %s", gsettings.fontRgbaOrder)
} else {
log.Warnf("Couldn't read font-rgba-order, leaving default %s",
gsettings.fontRgbaOrder)
}
val, err = getGsettingsValue("org.gnome.desktop.interface", "text-scaling-factor")
if err == nil {
v, e := strconv.ParseFloat(val, 32)
if e == nil {
gsettings.textScalingFactor = v
log.Infof("text-scaling-factor: %v", gsettings.textScalingFactor)
}
} else {
log.Warnf("Couldn't read textScalingFactor, leaving default %s",
gsettings.textScalingFactor)
}
val, err = getGsettingsValue("org.gnome.desktop.interface", "color-scheme")
if err == nil {
gsettings.colorScheme = val
log.Infof("color-scheme: %s", gsettings.colorScheme)
} else {
log.Warnf("Couldn't read color-scheme, leaving default %s",
gsettings.colorScheme)
}
val, err = getGsettingsValue("org.gnome.desktop.sound", "event-sounds")
if err == nil {
if val == "true" {
gsettings.eventSounds = true
} else {
gsettings.eventSounds = false
}
log.Infof("event-sounds: %v", gsettings.eventSounds)
} else {
log.Warnf("Couldn't read event-sounds, leaving default %v",
gsettings.eventSounds)
}
val, err = getGsettingsValue("org.gnome.desktop.sound", "input-feedback-sounds")
if err == nil {
if val == "true" {
gsettings.inputFeedbackSounds = true
} else {
gsettings.inputFeedbackSounds = false
}
log.Infof("input-feedback-sounds: %v", gsettings.inputFeedbackSounds)
} else {
log.Warnf("Couldn't read input-feedback-sounds, leaving default %v",
gsettings.inputFeedbackSounds)
}
}
func saveGsettingsBackup() {
gsettingsFile := filepath.Join(dataHome(), "nwg-look/")
makeDir(gsettingsFile)
log.Infof(">>> Backing up gsettings to %s", gsettingsFile)
lines := []string{"# Generated by nwg-look, do not edit this file."}
for _, key := range []string{
"gtk-theme",
"icon-theme",
"font-name",
"cursor-theme",
"cursor-size",
"toolbar-style",
"toolbar-icons-size",
"font-hinting",
"font-antialiasing",
"font-rgba-order",
"text-scaling-factor",
"color-scheme"} {
val, err := getGsettingsValue("org.gnome.desktop.interface", key)
if err == nil {
line := fmt.Sprintf("%s=%s", key, val)
lines = append(lines, line)
} else {
log.Warnf("Couldn't get gsettings key: $s", key)
}
}
for _, key := range []string{"event-sounds", "input-feedback-sounds"} {
val, err := getGsettingsValue("org.gnome.desktop.sound", key)
if err == nil {
line := fmt.Sprintf("%s=%s", key, val)
lines = append(lines, line)
} else {
log.Warnf("Couldn't get gsettings key: $s", key)
}
}
saveTextFile(lines, filepath.Join(dataHome(), "nwg-look/gsettings"))
}
func getGsettingsValue(schema, key string) (string, error) {
cmd := exec.Command("gsettings", "get", schema, key)
out, err := cmd.CombinedOutput()
if err == nil {
s := fmt.Sprintf("%s", strings.TrimSpace(string(out)))
if strings.HasPrefix(s, "'") {
return s[1 : len(s)-1], nil
}
return s, nil
}
return "", err
}
func applyGsettings() {
gnomeSchema := "org.gnome.desktop.interface"
log.Info(">>> Applying gsettings")
log.Infof(">> %s", gnomeSchema)
cmd := exec.Command("gsettings", "set", gnomeSchema, "gtk-theme", gsettings.gtkTheme)
err := cmd.Run()
if err != nil {
log.Warnf("gtk-theme: %s", err)
} else {
log.Infof("gtk-theme: %s OK", gsettings.gtkTheme)
}
cmd = exec.Command("gsettings", "set", gnomeSchema, "icon-theme", gsettings.iconTheme)
err = cmd.Run()
if err != nil {
log.Warnf("icon-theme: %s", err)
} else {
log.Infof("icon-theme: %s OK", gsettings.iconTheme)
}
cmd = exec.Command("gsettings", "set", gnomeSchema, "cursor-theme", gsettings.cursorTheme)
err = cmd.Run()
if err != nil {
log.Warnf("cursor-theme: %s", err)
} else {
log.Infof("cursor-theme: %s OK", gsettings.cursorTheme)
}
var val string
val = strconv.Itoa(gsettings.cursorSize)
cmd = exec.Command("gsettings", "set", gnomeSchema, "cursor-size", val)
err = cmd.Run()
if err != nil {
log.Warnf("cursor-size: %s", err)
} else {
log.Infof("cursor-size: %s OK", val)
}
cmd = exec.Command("gsettings", "set", gnomeSchema, "font-name", gsettings.fontName)
err = cmd.Run()
if err != nil {
log.Warnf("font-name: %s %s", gsettings.fontName, err)
} else {
log.Infof("font-name: %s OK", gsettings.fontName)
}
cmd = exec.Command("gsettings", "set", gnomeSchema, "font-hinting", gsettings.fontHinting)
err = cmd.Run()
if err != nil {
log.Warnf("font-hinting: %s %s", gsettings.fontHinting, err)
} else {
log.Infof("font-hinting: %s OK", gsettings.fontHinting)
}
cmd = exec.Command("gsettings", "set", gnomeSchema, "font-antialiasing", gsettings.fontAntialiasing)
err = cmd.Run()
if err != nil {
log.Warnf("font-antialiasing: %s %s", gsettings.fontAntialiasing, err)
} else {
log.Infof("font-antialiasing: %s OK", gsettings.fontAntialiasing)
}
cmd = exec.Command("gsettings", "set", gnomeSchema, "font-rgba-order", gsettings.fontRgbaOrder)
err = cmd.Run()
if err != nil {
log.Warnf("font-rgba-order: %s %s", gsettings.fontRgbaOrder, err)
} else {
log.Infof("font-rgba-order: %s OK", gsettings.fontRgbaOrder)
}
cmd = exec.Command("gsettings", "set", gnomeSchema, "text-scaling-factor", fmt.Sprintf("%f", gsettings.textScalingFactor))
err = cmd.Run()
if err != nil {
log.Warnf("text-scaling-factor: %s %s", gsettings.textScalingFactor, err)
} else {
log.Infof("text-scaling-factor: %v OK", gsettings.textScalingFactor)
}
cmd = exec.Command("gsettings", "set", gnomeSchema, "toolbar-style", gsettings.toolbarStyle)
err = cmd.Run()
if err != nil {
log.Warnf("toolbar-style: %s %s", gsettings.toolbarStyle, err)
} else {
log.Infof("toolbar-style: %s OK", gsettings.toolbarStyle)
}
cmd = exec.Command("gsettings", "set", gnomeSchema, "toolbar-icons-size", gsettings.toolbarIconsSize)
err = cmd.Run()
if err != nil {
log.Warnf("toolbar-icons-size: %s %s", gsettings.toolbarIconsSize, err)
} else {
log.Infof("toolbar-icons-size: %s OK", gsettings.toolbarIconsSize)
}
cmd = exec.Command("gsettings", "set", gnomeSchema, "color-scheme", gsettings.colorScheme)
err = cmd.Run()
if err != nil {
log.Warnf("color-scheme: %s %s", gsettings.colorScheme, err)
} else {
log.Infof("color-scheme: %s OK", gsettings.colorScheme)
}
gnomeSchema = "org.gnome.desktop.sound"
log.Infof(">> %s", gnomeSchema)
if gsettings.eventSounds {
val = "true"
} else {
val = "false"
}
cmd = exec.Command("gsettings", "set", gnomeSchema, "event-sounds", val)
err = cmd.Run()
if err != nil {
log.Warnf("event-sounds: %s %s", val, err)
} else {
log.Infof("event-sounds: %s OK", val)
}
if gsettings.inputFeedbackSounds {
val = "true"
} else {
val = "false"
}
cmd = exec.Command("gsettings", "set", gnomeSchema, "input-feedback-sounds", val)
err = cmd.Run()
if err != nil {
log.Warnf("input-feedback-sounds: %s %s", val, err)
} else {
log.Infof("input-feedback-sounds: %s OK", val)
}
}
func applyGsettingsFromFile() {
gsettingsFile := filepath.Join(dataHome(), "nwg-look/gsettings")
if pathExists(gsettingsFile) {
log.Infof("Loading gsettings from %s", gsettingsFile)
lines, err := loadTextFile(gsettingsFile)
if err != nil {
log.Fatalf("Failed loading file: %s", err)
}
var key, value string
for _, line := range lines {
if !strings.HasPrefix(line, "#") {
parts := strings.Split(line, "=")
if len(parts) == 2 {
key = parts[0]
value = parts[1]
switch key {
case "gtk-theme":
gsettings.gtkTheme = value
case "icon-theme":
gsettings.iconTheme = value
case "font-name":
gsettings.fontName = value
case "cursor-theme":
gsettings.cursorTheme = value
case "cursor-size":
v, err := strconv.Atoi(value)
if err == nil {
gsettings.cursorSize = v
}
case "toolbar-style":
gsettings.toolbarStyle = value
case "toolbar-icons-size":
gsettings.toolbarIconsSize = value
case "font-hinting":
gsettings.fontHinting = value
case "font-antialiasing":
gsettings.fontAntialiasing = value
case "font-rgba-order":
gsettings.fontRgbaOrder = value
case "text-scaling-factor":
v, err := strconv.ParseFloat(value, 64)
if err == nil {
gsettings.textScalingFactor = v
}
case "event-sounds":
gsettings.eventSounds = value == "true"
case "input-feedback-sounds":
gsettings.inputFeedbackSounds = value == "true"
case "color-scheme":
gsettings.colorScheme = value
}
}
}
}
applyGsettings()
} else {
log.Warnf("Couldn't find file: %s", gsettingsFile)
os.Exit(1)
}
}
func saveGtkIni() {
configFile := filepath.Join(configHome(), "gtk-3.0/settings.ini")
if !pathExists(configFile) {
makeDir(filepath.Join(configHome(), "gtk-3.0/"))
}
log.Infof(">>> Exporting %s", configFile)
lines := []string{"[Settings]"}
lines = append(lines, fmt.Sprintf("gtk-theme-name=%s", gsettings.gtkTheme))
lines = append(lines, fmt.Sprintf("gtk-icon-theme-name=%s", gsettings.iconTheme))
lines = append(lines, fmt.Sprintf("gtk-font-name=%s", gsettings.fontName))
lines = append(lines, fmt.Sprintf("gtk-cursor-theme-name=%s", gsettings.cursorTheme))
lines = append(lines, fmt.Sprintf("gtk-cursor-theme-size=%v", gsettings.cursorSize))
// Ignored
lines = append(lines, fmt.Sprintf("gtk-toolbar-style=%s", gtkConfig.toolbarStyle))
lines = append(lines, fmt.Sprintf("gtk-toolbar-icon-size=%s", gtkConfig.toolbarIconSize))
// Deprecated
v := 0
if gtkConfig.buttonImages {
v = 1
}
lines = append(lines, fmt.Sprintf("gtk-button-images=%v", v))
if gtkConfig.menuImages {
v = 1
} else {
v = 0
}
lines = append(lines, fmt.Sprintf("gtk-menu-images=%v", v))
if gsettings.eventSounds {
v = 1
} else {
v = 0
}
lines = append(lines, fmt.Sprintf("gtk-enable-event-sounds=%v", v))
if gsettings.inputFeedbackSounds {
v = 1
} else {
v = 0
}
lines = append(lines, fmt.Sprintf("gtk-enable-input-feedback-sounds=%v", v))
if gsettings.fontAntialiasing != "none" {
v = 1
} else {
v = 0
}
lines = append(lines, fmt.Sprintf("gtk-xft-antialias=%v", v))
if gsettings.fontHinting != "none" {
v = 1
} else {
v = 0
}
lines = append(lines, fmt.Sprintf("gtk-xft-hinting=%v", v))
var fh string
switch gsettings.fontHinting {
case "slight":
fh = "hintslight"
case "medium":
fh = "hintmedium"
case "full":
fh = "hintfull"
default:
fh = "hintnone"
}
lines = append(lines, fmt.Sprintf("gtk-xft-hintstyle=%s", fh))
lines = append(lines, fmt.Sprintf("gtk-xft-rgba=%s", gsettings.fontRgbaOrder))
if gsettings.colorScheme == "prefer-dark" {
v = 1
} else {
v = 0
}
lines = append(lines, fmt.Sprintf("gtk-application-prefer-dark-theme=%v", v))
// append unsupported lines / comments from the original settings.ini file
for _, l := range originalGtkConfig {
if l != "" && !isSupported(l) {
lines = append(lines, l)
}
}
for _, l := range lines {
log.Debug(l)
}
saveTextFile(lines, configFile)
}
func isSupported(line string) bool {
supported := []string{
"gtk-theme-name",
"gtk-icon-theme-name",
"gtk-font-name",
"gtk-cursor-theme-name",
"gtk-cursor-theme-size",
"gtk-toolbar-style",
"gtk-toolbar-icon-size",
"gtk-button-images",
"gtk-menu-images",
"gtk-enable-event-sounds",
"gtk-enable-input-feedback-sounds",
"gtk-xft-antialias",
"gtk-xft-hinting",
"gtk-xft-hintstyle",
"gtk-xft-rgba",
"gtk-application-prefer-dark-theme",
}
for _, d := range supported {
if strings.HasPrefix(line, d) {
return true
}
}
return false
}
func saveGtkRc20() {
home := os.Getenv("HOME")
var configFile string
if os.Getenv("GTK2_RC_FILES") != "" {
configFile = os.Getenv("GTK2_RC_FILES")
} else {
configFile = filepath.Join(home, ".gtkrc-2.0")
}
log.Infof(">>> Exporting %s", configFile)
lines := []string{
"# DO NOT EDIT! This file will be overwritten by nwg-look.",
"# Any customization should be done in ~/.gtkrc-2.0.mine instead.",
"",
}
lines = append(lines, fmt.Sprintf("include \"%s/.gtkrc-2.0.mine\"", home))
lines = append(lines, fmt.Sprintf("gtk-theme-name=\"%s\"", gsettings.gtkTheme))
lines = append(lines, fmt.Sprintf("gtk-icon-theme-name=\"%s\"", gsettings.iconTheme))
lines = append(lines, fmt.Sprintf("gtk-font-name=\"%s\"", gsettings.fontName))
lines = append(lines, fmt.Sprintf("gtk-cursor-theme-name=\"%s\"", gsettings.cursorTheme))
lines = append(lines, fmt.Sprintf("gtk-cursor-theme-size=%v", gsettings.cursorSize))
lines = append(lines, fmt.Sprintf("gtk-toolbar-style=%s", gtkConfig.toolbarStyle))
lines = append(lines, fmt.Sprintf("gtk-toolbar-icon-size=%s", gtkConfig.toolbarIconSize))
v := 0
if gtkConfig.buttonImages {
v = 1
}
lines = append(lines, fmt.Sprintf("gtk-button-images=%v", v))
if gtkConfig.menuImages {
v = 1
} else {
v = 0
}
lines = append(lines, fmt.Sprintf("gtk-menu-images=%v", v))
if gsettings.eventSounds {
v = 1
} else {
v = 0
}
lines = append(lines, fmt.Sprintf("gtk-enable-event-sounds=%v", v))
if gsettings.inputFeedbackSounds {
v = 1
} else {
v = 0
}
lines = append(lines, fmt.Sprintf("gtk-enable-input-feedback-sounds=%v", v))
if gsettings.fontAntialiasing != "none" {
v = 1
} else {
v = 0
}
lines = append(lines, fmt.Sprintf("gtk-xft-antialias=%v", v))
if gsettings.fontHinting != "none" {
v = 1
} else {
v = 0
}
lines = append(lines, fmt.Sprintf("gtk-xft-hinting=%v", v))
var fh string
switch gsettings.fontHinting {
case "slight":
fh = "hintslight"
case "medium":
fh = "hintmedium"
case "full":
fh = "hintfull"
default:
fh = "hintnone"
}
lines = append(lines, fmt.Sprintf("gtk-xft-hintstyle=\"%s\"", fh))
lines = append(lines, fmt.Sprintf("gtk-xft-rgba=\"%s\"", gsettings.fontRgbaOrder))
if gtkConfig.applicationPreferDarkTheme {
v = 1
} else {
v = 0
}
for _, l := range lines {
log.Debug(l)
}
saveTextFile(lines, configFile)
}
func saveXsettingsd() {
configFile := filepath.Join(configHome(), "xsettingsd/xsettingsd.conf")
if !pathExists(configFile) {
makeDir(filepath.Join(configHome(), "xsettingsd/"))
}
log.Infof(">>> Exporting %s", configFile)
lines := []string{}
lines = append(lines, fmt.Sprintf("Net/ThemeName \"%s\"", gsettings.gtkTheme))
lines = append(lines, fmt.Sprintf("Net/IconThemeName \"%s\"", gsettings.iconTheme))
lines = append(lines, fmt.Sprintf("Gtk/CursorThemeName \"%s\"", gsettings.cursorTheme))
var v int
if gsettings.eventSounds {
v = 1
} else {
v = 0
}
lines = append(lines, fmt.Sprintf("Net/EnableEventSounds %v", v))
if gsettings.inputFeedbackSounds {
v = 1
} else {
v = 0
}
lines = append(lines, fmt.Sprintf("EnableInputFeedbackSounds %v", v))
if gsettings.fontAntialiasing != "none" {
v = 1
} else {
v = 0
}
lines = append(lines, fmt.Sprintf("Xft/Antialias %v", v))
if gsettings.fontHinting != "none" {
v = 1
} else {
v = 0
}
lines = append(lines, fmt.Sprintf("Xft/Hinting %v", v))
var fh string
switch gsettings.fontHinting {
case "slight":
fh = "hintslight"
case "medium":
fh = "hintmedium"
case "full":
fh = "hintfull"
default:
fh = "hintnone"
}
lines = append(lines, fmt.Sprintf("Xft/HintStyle \"%s\"", fh))
lines = append(lines, fmt.Sprintf("Xft/RGBA \"%s\"", gsettings.fontRgbaOrder))
for _, l := range lines {
log.Debug(l)
}
saveTextFile(lines, configFile)
}
func linkGtk4Stuff() {
home := os.Getenv("HOME")
configPath := filepath.Join(home, ".config")
themeName := gsettings.gtkTheme
if gsettings.gtkTheme != "" {
log.Infof(">>> Symlinking files in %s", filepath.Join(configPath, "/gtk-4.0"))
log.Debugf("GTK Theme: '%s' at '%s'", themeName, gtkThemePaths[themeName])
log.Debugf("Config path: '%s'", configPath)
themePath := gtkThemePaths[themeName]
log.Debugf("Theme path: '%s'", gtkThemePaths[gsettings.gtkTheme])
if themePath == "" {
log.Warnf("Unknown theme path: '%s'", themePath)
return
}
if !pathExists(filepath.Join(themePath, "gtk-4.0")) {
log.Warnf("%s theme has no gtk-4.0 directory", themePath)
return
}
clearGtk4Symlinks()
// Create symlinks
if pathExists(filepath.Join(themePath, "gtk-4.0/gtk.css")) {
cmd := exec.Command("ln", "-s", filepath.Join(themePath, "gtk-4.0/gtk.css"), filepath.Join(configPath, "gtk-4.0/gtk.css"))
err := cmd.Run()
if err != nil {
log.Warnf("Couldn't symlink '%s': %s", filepath.Join(themePath, "gtk-4.0/gtk.css"), err)
} else {
log.Debugf("Created symlink to '%s'", filepath.Join(themePath, "gtk-4.0/gtk.css"))
}
}
if pathExists(filepath.Join(themePath, "gtk-4.0/gtk-dark.css")) {
cmd := exec.Command("ln", "-s", filepath.Join(themePath, "gtk-4.0/gtk-dark.css"), filepath.Join(configPath, "gtk-4.0/gtk-dark.css"))
err := cmd.Run()
if err != nil {
log.Warnf("Couldn't symlink '%s': %s", filepath.Join(themePath, "gtk-4.0/gtk-dark.css"), err)
} else {
log.Debugf("Created symlink to '%s'", filepath.Join(themePath, "gtk-4.0/gtk-dark.css"))
}
}
if pathExists(filepath.Join(themePath, "gtk-4.0/assets")) {
cmd := exec.Command("ln", "-s", filepath.Join(themePath, "gtk-4.0/assets"), filepath.Join(configPath, "gtk-4.0/assets"))
err := cmd.Run()
if err != nil {
log.Warnf("Couldn't symlink '%s': %s", filepath.Join(themePath, "gtk-4.0/assets"), err)
} else {
log.Debugf("Created symlink to '%s'", filepath.Join(themePath, "gtk-4.0/assets"))
}
}
if pathExists(filepath.Join(themePath, "assets")) {
cmd := exec.Command("ln", "-s", filepath.Join(themePath, "assets"), filepath.Join(configPath, "assets"))
err := cmd.Run()
if err != nil {
log.Warnf("Couldn't symlink '%s': %s", filepath.Join(themePath, "assets"), err)
} else {
log.Debugf("Created symlink to '%s'", filepath.Join(themePath, "assets"))
}
}
} else {
log.Warnf("GTK theme name unknown")
}
}
func clearGtk4Symlinks() {
home := os.Getenv("HOME")
configPath := filepath.Join(home, ".config")
items := []string{"gtk-4.0/gtk.css", "gtk-4.0/gtk-dark.css", "gtk-4.0/assets", "assets"}
for _, item := range items {
p := filepath.Join(configPath, item)
if pathExists(p) {
log.Debugf("Removing '%s'", p)
info, _ := os.Stat(p)
if info.IsDir() {
cmd := exec.Command("rm", "-r", p)
err := cmd.Run()
if err != nil {
log.Warnf("Couldn't remove '%s': %s", p, err)
}
} else {
cmd := exec.Command("rm", p)
err := cmd.Run()
if err != nil {
log.Warnf("Couldn't remove '%s': %s", p, err)
}
}
}
}
}
func saveIndexTheme() {
home := os.Getenv("HOME")
iconsFolder := ""
if pathExists(filepath.Join(home, ".icons")) {
iconsFolder = filepath.Join(home, ".icons")
} else {
if os.Getenv("XDG_DATA_HOME") != "" {
if pathExists(filepath.Join(os.Getenv("XDG_DATA_HOME"), "icons")) {
iconsFolder = filepath.Join(os.Getenv("XDG_DATA_HOME"), "icons")
}
} else {
if pathExists(filepath.Join(home, ".local/share/icons")) {
iconsFolder = filepath.Join(home, ".local/share/icons")
}
}
}
if iconsFolder != "" {
indexThemeFile := filepath.Join(iconsFolder, "/default/index.theme")
if !pathExists(filepath.Join(iconsFolder, "default")) {
makeDir(filepath.Join(iconsFolder, "default"))
}
log.Infof(">>> Exporting %s", indexThemeFile)
lines := []string{
"# This file is written by nwg-look. Do not edit.",
"[Icon Theme]",
"Name=Default",
"Comment=Default Cursor Theme",
}
lines = append(lines, fmt.Sprintf("Inherits=%s", gsettings.cursorTheme))
saveTextFile(lines, indexThemeFile)
} else {
log.Warn("Couldn't find icons folder")
}
}
func getThemeNames() ([]string, map[string]string) {
var dirs []string
themePaths := make(map[string]string) // theme name 2 theme path
// get theme dirs
for _, dir := range dataDirs {
if pathExists(filepath.Join(dir, "themes")) {
dirs = append(dirs, filepath.Join(dir, "themes"))
}
}
home := os.Getenv("HOME")
if home != "" {
if pathExists(filepath.Join(home, ".themes")) {
dirs = append(dirs, filepath.Join(home, ".themes"))
}
}
exclusions := []string{"Default", "Emacs"}
var names []string
for _, d := range dirs {
files, err := listFiles(d)
if err == nil {
for _, f := range files {
if f.IsDir() {
subdirs, err := listFiles(filepath.Join(d, f.Name()))
if err == nil {
for _, sd := range subdirs {
if sd.IsDir() && strings.HasPrefix(sd.Name(), "gtk-") {
if !isIn(names, f.Name()) {
if !isIn(exclusions, f.Name()) {
names = append(names, f.Name())
themePaths[f.Name()] = filepath.Join(d, f.Name())
log.Debugf("Theme found: '%s' at '%s'", f.Name(), filepath.Join(d, f.Name()))
} else {
log.Debugf("Excluded theme: %s", f.Name())
}
break
}
}
}
}
}
}
}
}
sort.Slice(names, func(i, j int) bool {
return names[i] < names[j]
})
return names, themePaths
}
// returns map[displayName]folderName
func getIconThemeNames() map[string]string {
var dirs []string
name2folderName := make(map[string]string)
// get icon theme dirs
for _, dir := range dataDirs {
if pathExists(filepath.Join(dir, "icons")) {
dirs = append(dirs, filepath.Join(dir, "icons"))
}
}
home := os.Getenv("HOME")
if home != "" {
if pathExists(filepath.Join(home, ".icons")) {
dirs = append(dirs, filepath.Join(home, ".icons"))
}
}
exclusions := []string{"default", "hicolor", "locolor"}
var names []string
for _, d := range dirs {
files, err := listFiles(d)
if err == nil {
for _, f := range files {
if f.IsDir() {
if !isIn(exclusions, f.Name()) {
name, hasDirs, err := iconThemeName(filepath.Join(d, f.Name()))
if err == nil && hasDirs {
names = append(names, name)
name2folderName[name] = f.Name()
log.Debugf("Icon theme found: %s", name)
}
} else {
log.Debugf("Excluded icon theme: %s", f.Name())
}
}
}
}
}
sort.Slice(names, func(i, j int) bool {
return strings.ToUpper(names[i]) < strings.ToUpper(names[j])
})
return name2folderName
}
func getCursorThemes() (map[string]string, map[string]string) {
var dirs []string
name2path := make(map[string]string)
name2FolderName := make(map[string]string)
// get icon theme dirs
for _, dir := range dataDirs {
if pathExists(filepath.Join(dir, "icons")) {
dirs = append(dirs, filepath.Join(dir, "icons"))
}
}
home := os.Getenv("HOME")
if home != "" {
if pathExists(filepath.Join(home, ".icons")) {
dirs = append(dirs, filepath.Join(home, ".icons"))
}
}
exclusions := []string{"default", "hicolor", "locolor"}
for _, d := range dirs {
files, err := listFiles(d)
if err == nil {
for _, f := range files {
if f.IsDir() {
if !isIn(exclusions, f.Name()) {
content, _ := listFiles(filepath.Join(d, f.Name()))
if err == nil {
for _, item := range content {
if item.Name() == "cursors" {
name, _, err := iconThemeName(filepath.Join(d, f.Name()))
if err == nil {
name2FolderName[name] = f.Name()
}
log.Debugf("Cursor theme found: %s", f.Name())
name2path[f.Name()] = filepath.Join(d, f.Name(), "cursors")
}
}
}
}
}
}
}
}
return name2path, name2FolderName
}
func dataHome() string {
xdgDataHome := os.Getenv("XDG_DATA_HOME")
if xdgDataHome != "" {
return xdgDataHome
}
return filepath.Join(os.Getenv("HOME"), ".local/share")
}
func getDataDirs() []string {
var dirs []string
xdgDataDirs := ""
dirs = append(dirs, dataHome())
if os.Getenv("XDG_DATA_DIRS") != "" {
xdgDataDirs = os.Getenv("XDG_DATA_DIRS")
} else {
xdgDataDirs = "/usr/local/share/:/usr/share/"
}
for _, d := range strings.Split(xdgDataDirs, ":") {
dirs = append(dirs, d)
}
var confirmedDirs []string
for _, d := range dirs {
if pathExists(d) {
confirmedDirs = append(confirmedDirs, d)
}
}
return confirmedDirs
}
func iconThemeName(path string) (string, bool, error) {
name := ""
hasDirs := false
lines, err := loadTextFile(filepath.Join(path, "index.theme"))
if err != nil {
return name, hasDirs, err
}
for _, line := range lines {
if strings.HasPrefix(line, "Name=") || strings.HasPrefix(line, "Name =") {
name = strings.Split(line, "=")[1]
name = strings.TrimSpace(name)
break
}
}
for _, line := range lines {
if strings.HasPrefix(line, "Directories=") || strings.HasPrefix(line, "Directories =") {
hasDirs = true
break
}
}
return name, hasDirs, err
}
func loadTextFile(path string) ([]string, error) {
bytes, err := os.ReadFile(path)
if err != nil {
return nil, err
}
lines := strings.Split(string(bytes), "\n")
var output []string
for _, line := range lines {
line = strings.TrimSpace(line)
output = append(output, line)
}
return output, nil
}
func saveTextFile(text []string, path string) {
file, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0644)
if err != nil {
log.Warnf("Failed creating file: %s", err)
}
datawriter := bufio.NewWriter(file)
for _, data := range text {
_, _ = datawriter.WriteString(data + "\n")
}
datawriter.Flush()
file.Close()
}
func listFiles(dir string) ([]os.DirEntry, error) {
files, err := os.ReadDir(dir)
if err == nil {
return files, nil
}
return nil, err
}
func isIn(slice []string, val string) bool {
for _, item := range slice {
if item == val {
return true
}
}
return false
}
func pathExists(name string) bool {
if _, err := os.Stat(name); err != nil {
if os.IsNotExist(err) {
return false
}
}
return true
}
func tempDir() string {
if os.Getenv("TMPDIR") != "" {
return os.Getenv("TMPDIR")
} else if os.Getenv("TEMP") != "" {
return os.Getenv("TEMP")
} else if os.Getenv("TMP") != "" {
return os.Getenv("TMP")
}
return "/tmp"
}
func makeDir(dir string) {
if _, err := os.Stat(dir); os.IsNotExist(err) {
err := os.MkdirAll(dir, os.ModePerm)
if err == nil {
log.Debugf("Creating dir: %s", dir)
}
}
}
// Assert types to gtk.Builder objects
func getWindow(b *gtk.Builder, id string) (*gtk.Window, error) {
obj, err := b.GetObject(id)
if err != nil {
return nil, err
}
window, ok := obj.(*gtk.Window)
if !ok {
return nil, err
}
return window, nil
}
func getScrolledWindow(b *gtk.Builder, id string) (*gtk.ScrolledWindow, error) {
obj, err := b.GetObject(id)
if err != nil {
return nil, err
}
window, ok := obj.(*gtk.ScrolledWindow)
if !ok {
return nil, err
}
return window, nil
}
func getViewPort(b *gtk.Builder, id string) (*gtk.Viewport, error) {
obj, err := b.GetObject(id)
if err != nil {
return nil, err
}
viewport, ok := obj.(*gtk.Viewport)
if !ok {
return nil, err
}
return viewport, nil
}
func getButton(b *gtk.Builder, id string) (*gtk.Button, error) {
obj, err := b.GetObject(id)
if err != nil {
return nil, err
}
btn, ok := obj.(*gtk.Button)
if !ok {
return nil, err
}
return btn, nil
}
func getGrid(b *gtk.Builder, id string) (*gtk.Grid, error) {
obj, err := b.GetObject(id)
if err != nil {
return nil, err
}
grid, ok := obj.(*gtk.Grid)
if !ok {
return nil, err
}
return grid, nil
}
func getLabel(b *gtk.Builder, id string) (*gtk.Label, error) {
obj, err := b.GetObject(id)
if err != nil {
return nil, err
}
label, ok := obj.(*gtk.Label)
if !ok {
return nil, err
}
return label, nil
}
func getMenuBar(b *gtk.Builder, id string) (*gtk.MenuBar, error) {
obj, err := b.GetObject(id)
if err != nil {
return nil, err
}
menuBar, ok := obj.(*gtk.MenuBar)
if !ok {
return nil, err
}
return menuBar, nil
}
func getMenuItem(b *gtk.Builder, id string) (*gtk.MenuItem, error) {
obj, err := b.GetObject(id)
if err != nil {
return nil, err
}
item, ok := obj.(*gtk.MenuItem)
if !ok {
return nil, err
}
return item, nil
}
func detectLang() string {
lang := ""
shellDataFile := filepath.Join(dataHome(), "/nwg-shell/data")
if pathExists(shellDataFile) {
jsonFile, err := os.Open(shellDataFile)
if err == nil {
byteValue, _ := io.ReadAll(jsonFile)
var result map[string]interface{}
err = json.Unmarshal([]byte(byteValue), &result)
if err == nil {
if result["interface-locale"] != "" {
lang = fmt.Sprintf("%s", result["interface-locale"])
log.Infof("lang '%s' set from nwg-shell settings", lang)
}
}
}
defer jsonFile.Close()
}
if lang == "" {
if os.Getenv("LANG") != "" {
lang = strings.Split(os.Getenv("LANG"), ".")[0]
log.Debugf("lang '%s' set from the $LANG variable", lang)
} else {
lang = "en_US"
log.Warn("Couldn't determine your lang")
}
}
return lang
}
func loadVocabulary(lang string) map[string]string {
var dataDirs []string
dataDirs = getDataDirs()
for _, d := range dataDirs {
langsDir := filepath.Join(d, "/nwg-look/langs/")
enUSFile := filepath.Join(langsDir, "en_US.json")
if pathExists(enUSFile) {
log.Infof(">>> Loading basic lang from '%s'", enUSFile)
jsonFile, err := os.Open(enUSFile)
if err != nil {
log.Errorf("Error loading basic lang: %s", err)
os.Exit(1)
} else {
byteValue, _ := io.ReadAll(jsonFile)
var result map[string]string
err = json.Unmarshal([]byte(byteValue), &result)
if err != nil {
log.Errorf("Error unmarshalling '%s': %s", enUSFile, err)
// We can't continue w/o the basic dictionary!
os.Exit(1)
} else {
translationFile := filepath.Join(langsDir, fmt.Sprintf("%s.json", lang))
if lang == "en_US" || !pathExists(translationFile) {
// Users lang is en_US, or we have no translation into users lang
return result
} else {
log.Infof(">>> Loading translation from '%s'", translationFile)
jsonFile, err = os.Open(translationFile)
if err != nil {
log.Errorf("Error loading translation: %s", err)
} else {
byteValue, _ = io.ReadAll(jsonFile)
var result1 map[string]string
err = json.Unmarshal([]byte(byteValue), &result1)
if err != nil {
log.Errorf("Error unmarshalling '%s': %s", translationFile, err)
// We can continue, we just have no translation
return result
} else {
// Translate
for key, _ := range result1 {
if _, ok := result[key]; ok {
result[key] = result1[key]
}
}
return result
}
}
}
}
}
}
}
log.Errorf("Couldn't load the basic lang file")
os.Exit(1)
return nil
}
nwg-look-1.0.2/uicomponents.go 0000664 0000000 0000000 00000047172 14743561345 0016350 0 ustar 00root root 0000000 0000000 package main
import (
"fmt"
"os"
"os/exec"
"path/filepath"
"sort"
"strings"
"github.com/gotk3/gotk3/gdk"
"github.com/gotk3/gotk3/gtk"
log "github.com/sirupsen/logrus"
)
func setUpThemeListBox(currentTheme string) *gtk.ListBox {
listBox, _ := gtk.ListBoxNew()
var rowToSelect *gtk.ListBoxRow
themeNames, themePaths := getThemeNames()
gtkThemePaths = themePaths
for _, name := range themeNames {
row, _ := gtk.ListBoxRowNew()
eventBox, _ := gtk.EventBoxNew()
box, _ := gtk.BoxNew(gtk.ORIENTATION_HORIZONTAL, 6)
eventBox.Add(box)
lbl, _ := gtk.LabelNew(name)
lbl.SetProperty("margin-start", 6)
lbl.SetProperty("margin-end", 6)
n := name
eventBox.Connect("button-press-event", func() {
gtkSettings.SetProperty("gtk-theme-name", n)
gsettings.gtkTheme = n
})
row.Connect("focus-in-event", func() {
gtkSettings.SetProperty("gtk-theme-name", n)
gsettings.gtkTheme = n
})
if n == currentTheme {
rowToSelect = row
}
box.PackStart(lbl, false, false, 0)
row.Add(eventBox)
listBox.Add(row)
}
if rowToSelect != nil {
listBox.SelectRow(rowToSelect)
rowToFocus = rowToSelect
}
return listBox
}
func setUpIconThemeListBox(currentIconTheme string) *gtk.ListBox {
listBox, _ := gtk.ListBoxNew()
var rowToSelect *gtk.ListBoxRow
// map[displayName]folderName
namesMap := getIconThemeNames()
var displayNames []string
for name := range namesMap {
displayNames = append(displayNames, name)
}
sort.Slice(displayNames, func(i, j int) bool {
return strings.ToUpper(displayNames[i]) < strings.ToUpper(displayNames[j])
})
for _, name := range displayNames {
row, _ := gtk.ListBoxRowNew()
eventBox, _ := gtk.EventBoxNew()
box, _ := gtk.BoxNew(gtk.ORIENTATION_HORIZONTAL, 6)
eventBox.Add(box)
lbl, _ := gtk.LabelNew(name)
lbl.SetProperty("margin-start", 6)
lbl.SetProperty("margin-end", 6)
n := name
eventBox.Connect("button-press-event", func() {
gtkSettings.SetProperty("gtk-icon-theme-name", namesMap[n])
gsettings.iconTheme = namesMap[n]
})
row.Connect("focus-in-event", func() {
gtkSettings.SetProperty("gtk-icon-theme-name", namesMap[n])
gsettings.iconTheme = namesMap[n]
})
if namesMap[n] == currentIconTheme || n == currentIconTheme {
rowToSelect = row
}
box.PackStart(lbl, false, false, 0)
row.Add(eventBox)
listBox.Add(row)
}
if rowToSelect != nil {
listBox.SelectRow(rowToSelect)
rowToFocus = rowToSelect
}
return listBox
}
func setUpCursorThemeListBox(currentCursorTheme string) *gtk.ListBox {
listBox, _ := gtk.ListBoxNew()
var rowToSelect *gtk.ListBoxRow
var names []string
for name := range cursorThemeNames {
names = append(names, name)
}
sort.Slice(names, func(i, j int) bool {
return strings.ToUpper(names[i]) < strings.ToUpper(names[j])
})
for _, name := range names {
row, _ := gtk.ListBoxRowNew()
eventBox, _ := gtk.EventBoxNew()
box, _ := gtk.BoxNew(gtk.ORIENTATION_HORIZONTAL, 6)
eventBox.Add(box)
lbl, _ := gtk.LabelNew(name)
lbl.SetProperty("margin-start", 6)
lbl.SetProperty("margin-end", 6)
n := name
eventBox.Connect("button-press-event", func() {
gtkSettings.SetProperty("gtk-cursor-theme-name", cursorThemeNames[n])
gsettings.cursorTheme = cursorThemeNames[n]
displayCursorThemes()
})
row.Connect("focus-in-event", func() {
gtkSettings.SetProperty("gtk-cursor-theme-name", cursorThemeNames[n])
gsettings.cursorTheme = cursorThemeNames[n]
})
if cursorThemeNames[n] == currentCursorTheme {
rowToSelect = row
}
box.PackStart(lbl, false, false, 0)
row.Add(eventBox)
listBox.Add(row)
}
if rowToSelect != nil {
listBox.SelectRow(rowToSelect)
rowToFocus = rowToSelect
}
return listBox
}
func setUpWidgetsPreview() *gtk.Frame {
frame, _ := gtk.FrameNew(fmt.Sprintf(" %s ", voc["widget-style-preview"]))
frame.SetLabelAlign(0.5, 0.5)
frame.SetProperty("margin", 6)
frame.SetProperty("valign", gtk.ALIGN_START)
grid, _ := gtk.GridNew()
grid.SetRowSpacing(6)
grid.SetColumnSpacing(12)
grid.SetProperty("margin", 6)
frame.Add(grid)
box, _ := gtk.BoxNew(gtk.ORIENTATION_HORIZONTAL, 6)
box.SetProperty("hexpand", true)
grid.Attach(box, 0, 0, 3, 1)
btn, _ := gtk.ButtonNewFromIconName("go-previous", gtk.ICON_SIZE_BUTTON)
btn.SetProperty("can-focus", false)
box.PackStart(btn, false, false, 0)
btn, _ = gtk.ButtonNewFromIconName("go-next", gtk.ICON_SIZE_BUTTON)
btn.SetProperty("can-focus", false)
box.PackStart(btn, false, false, 0)
btn, _ = gtk.ButtonNewFromIconName("process-stop", gtk.ICON_SIZE_BUTTON)
btn.SetProperty("can-focus", false)
box.PackStart(btn, false, false, 0)
entry, _ := gtk.EntryNew()
entry.SetProperty("can-focus", false)
box.PackStart(entry, true, true, 0)
checkButton, _ := gtk.CheckButtonNew()
checkButton.SetProperty("can-focus", false)
checkButton.SetLabel(voc["check-button"])
grid.Attach(checkButton, 0, 1, 1, 1)
radioButton, _ := gtk.RadioButtonNew(nil)
radioButton.SetProperty("can-focus", false)
radioButton.SetLabel(voc["radio-button"])
grid.Attach(radioButton, 0, 2, 1, 1)
spinButton, _ := gtk.SpinButtonNewWithRange(0, 1000, 10)
spinButton.SetProperty("can-focus", false)
grid.Attach(spinButton, 0, 3, 1, 1)
button, _ := gtk.ButtonNewFromIconName("search", gtk.ICON_SIZE_BUTTON)
button.SetProperty("can-focus", false)
button.SetLabel(voc["button"])
grid.Attach(button, 1, 3, 1, 1)
scale, _ := gtk.ScaleNewWithRange(gtk.ORIENTATION_HORIZONTAL, 0, 100, 1)
scale.SetProperty("can-focus", false)
scale.SetDrawValue(true)
scale.SetValue(50)
grid.Attach(scale, 1, 1, 2, 1)
separator, _ := gtk.SeparatorNew(gtk.ORIENTATION_HORIZONTAL)
separator.SetProperty("can-focus", false)
separator.SetProperty("valign", gtk.ALIGN_CENTER)
grid.Attach(separator, 1, 2, 2, 1)
combo, _ := gtk.ComboBoxTextNew()
combo.Append("entry #1", fmt.Sprintf("%s 1", voc["entry"]))
combo.Append("entry #2", fmt.Sprintf("%s 2", voc["entry"]))
combo.SetProperty("can-focus", false)
grid.Attach(combo, 2, 3, 1, 1)
progressBar, _ := gtk.ProgressBarNew()
progressBar.SetFraction(0.3)
progressBar.SetText("30%")
progressBar.SetShowText(true)
progressBar.SetProperty("margin-bottom", 6)
grid.Attach(progressBar, 0, 4, 3, 1)
return frame
}
func setUpThemeSettingsForm(defaultFontName string) *gtk.Grid {
grid, _ := gtk.GridNew()
grid.SetColumnSpacing(12)
grid.SetRowSpacing(6)
grid.SetProperty("margin", 12)
label, _ := gtk.LabelNew(fmt.Sprintf("%s:", voc["default-font"]))
label.SetProperty("halign", gtk.ALIGN_END)
grid.Attach(label, 0, 0, 1, 1)
fontButton, _ := gtk.FontButtonNew()
fontButton.SetProperty("valign", gtk.ALIGN_CENTER)
fontButton.SetFont(defaultFontName)
fontButton.Connect("font-set", func() {
fontName := fontButton.GetFont()
gtkSettings.SetProperty("gtk-font-name", fontName)
gsettings.fontName = fontName
})
grid.Attach(fontButton, 1, 0, 1, 1)
label, _ = gtk.LabelNew(fmt.Sprintf("%s:", voc["color-scheme"]))
label.SetProperty("halign", gtk.ALIGN_END)
grid.Attach(label, 0, 1, 1, 1)
combo, _ := gtk.ComboBoxTextNew()
combo.Append("default", voc["default"])
combo.Append("prefer-dark", voc["prefer-dark"])
combo.Append("prefer-light", voc["prefer-light"])
combo.SetActiveID(gsettings.colorScheme)
combo.SetProperty("can-focus", false)
combo.Connect("changed", func() {
id := combo.GetActiveID()
gsettings.colorScheme = id
if id == "prefer-dark" {
gtkConfig.applicationPreferDarkTheme = true
gtkSettings.SetProperty("gtk-application-prefer-dark-theme", true)
} else {
gtkConfig.applicationPreferDarkTheme = false
gtkSettings.SetProperty("gtk-application-prefer-dark-theme", false)
}
})
grid.Attach(combo, 1, 1, 1, 1)
return grid
}
func setUpIconsPreview() *gtk.Frame {
frame, _ := gtk.FrameNew(fmt.Sprintf(" %s ", voc["icon-theme-preview"]))
frame.SetLabelAlign(0.5, 0.5)
frame.SetProperty("margin", 6)
frame.SetProperty("valign", gtk.ALIGN_START)
box, _ := gtk.BoxNew(gtk.ORIENTATION_VERTICAL, 12)
box.SetProperty("hexpand", true)
frame.Add(box)
flowBox, _ := gtk.FlowBoxNew()
flowBox.SetMaxChildrenPerLine(7)
flowBox.SetMinChildrenPerLine(7)
box.PackStart(flowBox, false, false, 0)
icons := []string{
"user-home",
"user-desktop",
"folder",
"folder-remote",
"user-trash",
"x-office-document",
"application-x-executable",
"image-x-generic",
"package-x-generic",
"emblem-mail",
"utilities-terminal",
"chromium",
"firefox",
"gimp"}
for _, name := range icons {
img, err := gtk.ImageNewFromIconName(name, gtk.ICON_SIZE_DIALOG)
if err == nil {
flowBox.Add(img)
log.Debugf("Added icon: '%s'", name)
} else {
log.Warnf("Couldn't create image: '%s'", name)
}
}
flowBox, _ = gtk.FlowBoxNew()
box.PackStart(flowBox, false, false, 12)
icons = []string{
"network-wired-symbolic",
"network-wireless-symbolic",
"bluetooth-active-symbolic",
"computer-symbolic",
"audio-volume-high-symbolic",
"battery-low-charging-symbolic",
"display-brightness-medium-symbolic",
}
for _, name := range icons {
img, err := gtk.ImageNewFromIconName(name, gtk.ICON_SIZE_MENU)
if err == nil {
flowBox.Add(img)
log.Debugf("Added icon: '%s'", name)
} else {
log.Warnf("Couldn't create image: '%s'", name)
}
}
return frame
}
func setUpCursorsPreview(path string) *gtk.Frame {
frame, _ := gtk.FrameNew(fmt.Sprintf(" %s ", voc["cursor-theme-preview"]))
frame.SetLabelAlign(0.5, 0.5)
frame.SetProperty("margin", 6)
box, _ := gtk.BoxNew(gtk.ORIENTATION_VERTICAL, 12)
box.SetProperty("margin", 12)
box.SetProperty("hexpand", true)
frame.Add(box)
flowBox, _ := gtk.FlowBoxNew()
flowBox.SetMaxChildrenPerLine(8)
box.Add(flowBox)
images := []string{
"left_ptr",
"hand2",
"sb_v_double_arrow",
"fleur",
"xterm",
"left_side",
"top_left_corner",
"h_double_arrow",
}
if path != "" {
// As I have no better idea, we'll use the external `xcur2png` tool
// to extract images from xcursor files, and save them to tmp dir.
cursorsDir := filepath.Join(tempDir(), "nwg-look-cursors")
dir, err := os.ReadDir(cursorsDir)
if err == nil {
for _, d := range dir {
os.RemoveAll(filepath.Join([]string{cursorsDir, d.Name()}...))
}
}
// just in case it didn't yet exist
makeDir(cursorsDir)
for _, name := range images {
imgPath := filepath.Join(path, name)
args := []string{imgPath, "-d", cursorsDir, "-c", cursorsDir, "-q"}
cmd := exec.Command("xcur2png", args...)
cmd.Run()
fName := fmt.Sprintf("%s_000.png", name)
pngPath := filepath.Join(cursorsDir, fName)
pixbuf, err := gdk.PixbufNewFromFileAtSize(pngPath, 24, 24)
if err == nil {
img, err := gtk.ImageNewFromPixbuf(pixbuf)
if err == nil {
flowBox.Add(img)
p, _ := img.GetParent()
parent, _ := p.(*gtk.FlowBoxChild)
parent.SetProperty("can-focus", false)
log.Debugf("Added icon: '%s'", pngPath)
} else {
log.Warnf("Couldn't create pixbuf from '%s'", pngPath)
}
} else {
log.Warnf("Couldn't create image from '%s'", pngPath)
}
}
}
return frame
}
func setUpCursorSizeSelector() *gtk.Box {
box, _ := gtk.BoxNew(gtk.ORIENTATION_HORIZONTAL, 6)
box.SetProperty("margin", 12)
box.SetProperty("hexpand", true)
box.SetProperty("vexpand", true)
box.SetProperty("valign", gtk.ALIGN_START)
lbl, _ := gtk.LabelNew(fmt.Sprintf("%s:", voc["cursor-size"]))
box.PackStart(lbl, false, false, 0)
sb, _ := gtk.SpinButtonNewWithRange(6, 1024, 1)
sb.SetValue(float64(gsettings.cursorSize))
sb.Connect("value-changed", func() {
v := int(sb.GetValue())
gtkSettings.SetProperty("gtk-cursor-theme-size", v)
gsettings.cursorSize = v
})
box.PackStart(sb, false, false, 6)
lbl, _ = gtk.LabelNew(fmt.Sprintf("(%s: 24)", voc["default"]))
box.PackStart(lbl, false, false, 0)
return box
}
func setUpFontSettingsForm() *gtk.Frame {
// We wont be applying these properties to gtk.Settings for preview,
// as they remain unchanged in once open window.
frame, _ := gtk.FrameNew(fmt.Sprintf(" %s ", voc["font-settings"]))
frame.SetLabelAlign(0.5, 0.5)
frame.SetProperty("margin", 6)
g, _ := gtk.GridNew()
g.SetRowSpacing(12)
g.SetColumnSpacing(12)
g.SetProperty("margin", 6)
g.SetProperty("hexpand", true)
g.SetProperty("vexpand", true)
frame.Add(g)
lbl, _ := gtk.LabelNew(fmt.Sprintf("%s:", voc["font-hinting"]))
lbl.SetProperty("halign", gtk.ALIGN_END)
g.Attach(lbl, 0, 0, 1, 1)
comboHinting, _ := gtk.ComboBoxTextNew()
comboHinting.Append("none", voc["none"])
comboHinting.Append("slight", voc["slight"])
comboHinting.Append("medium", voc["medium"])
comboHinting.Append("full", voc["full"])
comboHinting.SetActiveID(gsettings.fontHinting)
g.Attach(comboHinting, 1, 0, 1, 1)
comboHinting.Connect("changed", func() {
id := comboHinting.GetActiveID()
gsettings.fontHinting = id
})
lbl, _ = gtk.LabelNew(fmt.Sprintf("%s:", voc["font-antialiasing"]))
lbl.SetProperty("halign", gtk.ALIGN_END)
g.Attach(lbl, 0, 1, 1, 1)
comboRgba, _ := gtk.ComboBoxTextNew()
comboAntialiasing, _ := gtk.ComboBoxTextNew()
comboAntialiasing.Append("none", voc["none"])
comboAntialiasing.Append("grayscale", voc["grayscale"])
comboAntialiasing.Append("rgba", "rgba")
comboAntialiasing.SetActiveID(gsettings.fontAntialiasing)
g.Attach(comboAntialiasing, 1, 1, 1, 1)
comboAntialiasing.Connect("changed", func() {
id := comboAntialiasing.GetActiveID()
gsettings.fontAntialiasing = id
comboRgba.SetSensitive(id == "rgba")
})
lbl, _ = gtk.LabelNew(fmt.Sprintf("%s", voc["font-rgba-order"]))
lbl.SetProperty("halign", gtk.ALIGN_END)
g.Attach(lbl, 0, 2, 1, 1)
comboRgba.Append("rgb", "RGB")
comboRgba.Append("bgr", "BGR")
comboRgba.Append("vrgb", "VRGB")
comboRgba.Append("vbgr", "VBGR")
comboRgba.SetActiveID(gsettings.fontRgbaOrder)
comboRgba.SetSensitive(comboAntialiasing.GetActiveID() == "rgba")
g.Attach(comboRgba, 1, 2, 1, 1)
comboRgba.Connect("changed", func() {
gsettings.fontRgbaOrder = comboRgba.GetActiveID()
})
lbl, _ = gtk.LabelNew(fmt.Sprintf("%s:", voc["text-scaling-factor"]))
lbl.SetProperty("halign", gtk.ALIGN_END)
g.Attach(lbl, 0, 3, 1, 1)
sb, _ := gtk.SpinButtonNewWithRange(0.5, 3, 0.01)
sb.SetValue(gsettings.textScalingFactor)
sb.Connect("value-changed", func() {
v := sb.GetValue()
gsettings.textScalingFactor = v
})
g.Attach(sb, 1, 3, 1, 1)
return frame
}
func setUpOtherSettingsForm() *gtk.Frame {
// We won't be applying these properties to gtk.Settings for preview,
// as they remain unchanged in once open window.
frame, _ := gtk.FrameNew(fmt.Sprintf(" %s ", voc["other-settings"]))
frame.SetLabelAlign(0.5, 0.5)
frame.SetProperty("margin", 6)
g, _ := gtk.GridNew()
g.SetRowSpacing(12)
g.SetColumnSpacing(12)
g.SetProperty("margin", 6)
g.SetProperty("hexpand", true)
g.SetProperty("vexpand", true)
frame.Add(g)
lbl, _ := gtk.LabelNew("")
lbl.SetMarkup(fmt.Sprintf("%s (%s)", voc["ui-settings"], voc["deprecated"]))
lbl.SetProperty("halign", gtk.ALIGN_START)
g.Attach(lbl, 0, 0, 2, 1)
lbl, _ = gtk.LabelNew(fmt.Sprintf("%s:", voc["toolbar-style"]))
lbl.SetProperty("halign", gtk.ALIGN_END)
g.Attach(lbl, 0, 1, 1, 1)
comboToolbarStyle, _ := gtk.ComboBoxTextNew()
comboToolbarStyle.SetTooltipText(fmt.Sprintf("%s, %s", voc["deprecated-since-gtk-310"], voc["ignored"]))
comboToolbarStyle.Append("both", voc["text-below-icons"])
comboToolbarStyle.Append("both-horiz", voc["text-next-to-icons"])
comboToolbarStyle.Append("icons", voc["icons"])
comboToolbarStyle.Append("text", voc["text"])
comboToolbarStyle.SetActiveID(gsettings.toolbarStyle)
g.Attach(comboToolbarStyle, 1, 1, 1, 1)
comboToolbarStyle.Connect("changed", func() {
gsettings.toolbarStyle = comboToolbarStyle.GetActiveID()
switch gsettings.toolbarStyle {
case "both":
gtkConfig.toolbarStyle = "GTK_TOOLBAR_BOTH"
case "icons":
gtkConfig.toolbarStyle = "GTK_TOOLBAR_ICONS"
case "text":
gtkConfig.toolbarStyle = "GTK_TOOLBAR_TEXT"
default:
gtkConfig.toolbarStyle = "GTK_TOOLBAR_BOTH_HORIZ"
}
})
lbl, _ = gtk.LabelNew(fmt.Sprintf("%s:", voc["toolbar-icon-size"]))
lbl.SetProperty("halign", gtk.ALIGN_END)
g.Attach(lbl, 0, 2, 1, 1)
comboToolbarIconSize, _ := gtk.ComboBoxTextNew()
comboToolbarIconSize.SetTooltipText(fmt.Sprintf("%s, %s", voc["deprecated-since-gtk-310"], voc["ignored"]))
comboToolbarIconSize.Append("small", voc["small"])
comboToolbarIconSize.Append("large", voc["large"])
comboToolbarIconSize.SetActiveID(gsettings.toolbarIconsSize)
g.Attach(comboToolbarIconSize, 1, 2, 1, 1)
comboToolbarIconSize.Connect("changed", func() {
gsettings.toolbarIconsSize = comboToolbarIconSize.GetActiveID()
if gsettings.toolbarIconsSize == "small" {
gtkConfig.toolbarIconSize = "GTK_ICON_SIZE_SMALL_TOOLBAR"
} else {
gtkConfig.toolbarIconSize = "GTK_ICON_SIZE_LARGE_TOOLBAR"
}
})
cbBtn, _ := gtk.CheckButtonNewWithLabel(voc["show-button-images"])
cbBtn.SetTooltipText(fmt.Sprintf("%s", voc["deprecated-since-gtk-310"]))
cbBtn.SetActive(gtkConfig.buttonImages)
cbBtn.Connect("toggled", func() {
gtkConfig.buttonImages = cbBtn.GetActive()
})
g.Attach(cbBtn, 0, 3, 1, 1)
cbMnu, _ := gtk.CheckButtonNewWithLabel(voc["show-menu-images"])
cbMnu.SetTooltipText(fmt.Sprintf("%s, %s", voc["deprecated-since-gtk-310"], voc["ignored"]))
cbMnu.SetActive(gtkConfig.menuImages)
cbMnu.Connect("toggled", func() {
gtkConfig.menuImages = cbMnu.GetActive()
})
g.Attach(cbMnu, 0, 4, 1, 1)
lbl, _ = gtk.LabelNew("")
lbl.SetMarkup(fmt.Sprintf("%s", voc["sound-effects"]))
lbl.SetProperty("halign", gtk.ALIGN_START)
g.Attach(lbl, 0, 5, 1, 1)
cbEventSounds, _ := gtk.CheckButtonNewWithLabel(voc["enable-event-sounds"])
cbEventSounds.SetActive(gsettings.eventSounds)
cbEventSounds.Connect("toggled", func() {
gsettings.eventSounds = cbEventSounds.GetActive()
gtkConfig.enableEventSounds = cbEventSounds.GetActive()
})
g.Attach(cbEventSounds, 0, 6, 1, 1)
cbInputSounds, _ := gtk.CheckButtonNewWithLabel(voc["enable-input-feedback-sounds"])
cbInputSounds.SetActive(gsettings.inputFeedbackSounds)
cbInputSounds.Connect("toggled", func() {
gsettings.inputFeedbackSounds = cbInputSounds.GetActive()
gtkConfig.enableInputFeedbackSounds = cbInputSounds.GetActive()
})
g.Attach(cbInputSounds, 0, 7, 2, 1)
return frame
}
func setUpProgramSettingsForm() *gtk.Frame {
frame, _ := gtk.FrameNew(fmt.Sprintf(" %s ", voc["program-settings"]))
frame.SetLabelAlign(0.5, 0.5)
frame.SetProperty("margin", 6)
g, _ := gtk.GridNew()
g.SetRowSpacing(12)
g.SetColumnSpacing(12)
g.SetProperty("margin", 6)
g.SetProperty("hexpand", true)
g.SetProperty("vexpand", true)
frame.Add(g)
lbl, _ := gtk.LabelNew("")
lbl.SetMarkup(fmt.Sprintf("%s", voc["files-to-export"]))
lbl.SetProperty("halign", gtk.ALIGN_START)
g.Attach(lbl, 0, 0, 1, 1)
cb1, _ := gtk.CheckButtonNewWithLabel("~/.config/gtk-3.0/settings.ini")
cb1.SetActive(preferences.ExportSettingsIni)
cb1.Connect("toggled", func() {
preferences.ExportSettingsIni = cb1.GetActive()
})
g.Attach(cb1, 0, 1, 1, 1)
cb2, _ := gtk.CheckButtonNewWithLabel("~/.gtkrc-2.0")
cb2.SetActive(preferences.ExportGtkRc20)
cb2.Connect("toggled", func() {
preferences.ExportGtkRc20 = cb2.GetActive()
})
g.Attach(cb2, 0, 2, 1, 1)
cb3, _ := gtk.CheckButtonNewWithLabel("~/.icons/default/index.theme")
cb3.SetActive(preferences.ExportIndexTheme)
cb3.Connect("toggled", func() {
preferences.ExportIndexTheme = cb3.GetActive()
})
g.Attach(cb3, 0, 3, 1, 1)
cb4, _ := gtk.CheckButtonNewWithLabel("~/.config/xsettingsd/xsettingsd.conf")
cb4.SetActive(preferences.ExportXsettingsd)
cb4.Connect("toggled", func() {
preferences.ExportXsettingsd = cb4.GetActive()
})
g.Attach(cb4, 0, 4, 1, 1)
cb5, _ := gtk.CheckButtonNewWithLabel("~/.config/gtk-4.0/*")
cb5.SetActive(preferences.ExportGtk4Symlinks)
cb5.Connect("toggled", func() {
preferences.ExportGtk4Symlinks = cb5.GetActive()
})
g.Attach(cb5, 0, 5, 1, 1)
btn, _ := gtk.ButtonNewWithLabel(voc["clear"])
btn.Connect("clicked", clearGtk4Symlinks)
g.Attach(btn, 1, 5, 1, 1)
return frame
}