Implemented basic GUI config in Tcl/Tk

This commit is contained in:
Luxferre
2024-09-30 20:21:33 +03:00
parent 99d164d0cb
commit ff520f5dfb
7 changed files with 463 additions and 1 deletions
+27
View File
@@ -2,6 +2,8 @@
SRC = src SRC = src
DIST = out DIST = out
CONFIGBINNAME = streamgoose-config
TCLDIR = tcldep
CROSSPREFIX=x86_64-w64-mingw32 CROSSPREFIX=x86_64-w64-mingw32
CROSSCC = $(CROSSPREFIX)-gcc CROSSCC = $(CROSSPREFIX)-gcc
CROSSCXX = $(CROSSPREFIX)-g++ CROSSCXX = $(CROSSPREFIX)-g++
@@ -31,6 +33,30 @@ win64-headless:
$(MAKE) $(MFLAGS) win64-headless $(MAKE) $(MFLAGS) win64-headless
$(MAKE) dist $(MAKE) dist
config-linux:
cp $(SRC)/conf.tcl $(TCLDIR)/conf.tcl
cp $(TCLDIR)/tclkit-linux64 $(TCLDIR)/tclruntime
cd $(TCLDIR) && ./tclkit-linux64 ./sdx.kit qwrap conf.tcl -runtime tclruntime
mkdir -p $(DIST)
mv $(TCLDIR)/conf $(DIST)/$(CONFIGBINNAME)
rm $(TCLDIR)/tclruntime
config-win64:
cp $(SRC)/conf.tcl $(TCLDIR)/conf.tcl
cp $(TCLDIR)/tclkit-win64.exe $(TCLDIR)/tclruntime.exe
cd $(TCLDIR) && ./tclkit-win64.exe ./sdx.kit qwrap conf.tcl -runtime tclruntime.exe
mkdir -p $(DIST)
mv $(TCLDIR)/conf $(DIST)/$(CONFIGBINNAME).exe
rm $(TCLDIR)/tclruntime.exe
config-win64-cross:
cp $(SRC)/conf.tcl $(TCLDIR)/conf.tcl
cp $(TCLDIR)/tclkit-win64.exe $(TCLDIR)/tclruntime.exe
cd $(TCLDIR) && ./tclkit-linux64 ./sdx.kit qwrap conf.tcl -runtime tclruntime.exe
mkdir -p $(DIST)
mv $(TCLDIR)/conf $(DIST)/$(CONFIGBINNAME).exe
rm $(TCLDIR)/tclruntime.exe
dist: dist:
mkdir -p $(DIST) mkdir -p $(DIST)
mv $(SRC)/streamgoose* $(DIST)/ mv $(SRC)/streamgoose* $(DIST)/
@@ -38,6 +64,7 @@ dist:
cp -r auth $(DIST)/ cp -r auth $(DIST)/
cp -r external $(DIST)/ cp -r external $(DIST)/
cp config.json $(DIST)/ cp config.json $(DIST)/
cp labels.ini $(DIST)/
clean: clean:
rm -rf $(DIST) rm -rf $(DIST)
+12 -1
View File
@@ -23,11 +23,11 @@ Nevertheless, the following features are already implemented:
- YouTube avatar icon support - YouTube avatar icon support
- Twitch system message support (needs more testing) - Twitch system message support (needs more testing)
- [Noto Color Emoji](https://fonts.google.com/noto/specimen/Noto+Color+Emoji) font bundled for universal Unicode emoji support - [Noto Color Emoji](https://fonts.google.com/noto/specimen/Noto+Color+Emoji) font bundled for universal Unicode emoji support
- GUI configuration utility (for now, only 64-bit Linux and Windows versions are supported)
## Planned features ## Planned features
- Reward redemption message support for Twitch
- "Deleted message" support for YouTube - "Deleted message" support for YouTube
- Badge support for Kick - Badge support for Kick
- Internal chat ranking system by username - Internal chat ranking system by username
@@ -98,12 +98,16 @@ For Linux, you also need to have the `webkit2gtk-4.1` dev package installed.
Just run `make` and get all the resulting files in the `out` directory. Just run `make` and get all the resulting files in the `out` directory.
To compile the GUI configuration utility (x64 Linux only), run `make config-linux`.
### Windows x64 (native) ### Windows x64 (native)
You need to have Go 1.23.1 and above and a POSIX-compatible "make" in your %PATH%. You need to have Go 1.23.1 and above and a POSIX-compatible "make" in your %PATH%.
Just run `make win64` and get all the resulting files in the `out` directory. Just run `make win64` and get all the resulting files in the `out` directory.
To compile the GUI configuration utility (x64 Windows only), run `make config-win64`.
### Windows x64 (cross-compiling from Linux) ### Windows x64 (cross-compiling from Linux)
You need to have Go 1.23.1 and above and a POSIX-compatible "make". You need to have Go 1.23.1 and above and a POSIX-compatible "make".
@@ -121,6 +125,8 @@ Then rerun the command from step 2.
**Note**: When testing the cross-compiled build under Wine, you must have Microsoft Edge WebView2 Runtime installed in order for the GUI to work at all. You can [download](https://developer.microsoft.com/en-us/microsoft-edge/webview2) this engine directly from the Microsoft website (select "Evergreen Standalone Installer") and install it into your Wine profile. Be sure to enable at least Windows 7 emulation in winecfg. **Note**: When testing the cross-compiled build under Wine, you must have Microsoft Edge WebView2 Runtime installed in order for the GUI to work at all. You can [download](https://developer.microsoft.com/en-us/microsoft-edge/webview2) this engine directly from the Microsoft website (select "Evergreen Standalone Installer") and install it into your Wine profile. Be sure to enable at least Windows 7 emulation in winecfg.
To compile the GUI configuration utility (for x64 Windows and from x64 Linux only), run `make config-win64-cross`.
### Headless builds ### Headless builds
In case you don't want to build the GUI, you can run `make headless` or `make win64-headless` depending on the platform. In this case, a binary will be built where you'll be required to point any JS-enabled Web browser of your choice to `http://127.0.0.1:60053` to see the chat window. This can be useful when building StreamGoose on more exotic platforms not supported by the `webview_go` library. In case you don't want to build the GUI, you can run `make headless` or `make win64-headless` depending on the platform. In this case, a binary will be built where you'll be required to point any JS-enabled Web browser of your choice to `http://127.0.0.1:60053` to see the chat window. This can be useful when building StreamGoose on more exotic platforms not supported by the `webview_go` library.
@@ -135,6 +141,7 @@ Enter the distribution directory and run the `./streamgoose` or `streamgoose.exe
### Configuration ### Configuration
StreamGoose is mostly configured through the `config.json` file distributed in the same directory as the main binary. StreamGoose is mostly configured through the `config.json` file distributed in the same directory as the main binary.
Alternatively, if you run Linux or Windows on an x86_64 machine, you can compile a GUI configuration utility (see the previous section) that modifies this file. If you run another OS or have another architecture but happen to have a Tcl/Tk distribution installed, you can directly copy the `src/conf.tcl` file into the output directory and run it with the Wish shell instead.
The following fields in the `config.json` are backend-agnostic: The following fields in the `config.json` are backend-agnostic:
@@ -324,6 +331,10 @@ Sure, anything can happen. The problem with adding a new platform support, as al
Besides the main four, anyway, your best bet would be to create an **external** backend for the platform you're interested in (again, you could use any programming language you want), and then incorporate it into this repo in the `/external` directory. When the backend is mature enough, we can always sit and think about how to convert it into an internal one to be distributed inside the StreamGoose binary. Besides the main four, anyway, your best bet would be to create an **external** backend for the platform you're interested in (again, you could use any programming language you want), and then incorporate it into this repo in the `/external` directory. When the backend is mature enough, we can always sit and think about how to convert it into an internal one to be distributed inside the StreamGoose binary.
### What is the `labels.ini` file in the distribution directory?
This file is now only used by the GUI configuration program but is planned to be used for the main StreamGoose binary as well, for the user to be able to customize or localize all the labels and internal messages of the application.
### Why is there a `twitch-cid.txt` file in the `auth/` directory? ### Why is there a `twitch-cid.txt` file in the `auth/` directory?
This is the Twitch client ID corresponding to the [Twitch Chat OAuth Password Generator](https://twitchapps.com/tmi/) website. If you generate a Twitch token through it, you'll need this ID for external Twitch API such as badges to work correctly. This is the Twitch client ID corresponding to the [Twitch Chat OAuth Password Generator](https://twitchapps.com/tmi/) website. If you generate a Twitch token through it, you'll need this ID for external Twitch API such as badges to work correctly.
+40
View File
@@ -0,0 +1,40 @@
[general]
backends = general tw yt oc kk
[config.gui.general]
windowtitle = StreamGoose config
tabtitle = General
server_port = Server port
server_listen_ip = Server IP
debug = Debug
useragent = User agent (for unofficial APIs)
reset = Reset
save = Save
exit = Exit
credits = Created by Luxferre in 2024 / Public domain
savemodal = Save the configuration file?
resetmodal = Reset the configuration without saving?
[config.gui.tw]
tabtitle = Twitch
tw_backend_enabled = Enable Twitch backend
tw_nickname = Your Twitch nickname
tw_channel = Twitch channel (starts with #)
[config.gui.yt]
tabtitle = YouTube
yt_backend_enabled = Enable YouTube backend
yt_channel = YouTube channel (starts with @)
yt_api_poll_timeout = API poll timeout (s)
[config.gui.oc]
tabtitle = Owncast
oc_backend_enabled = Enable Owncast backend
oc_server = Owncast server URL
oc_api_poll_timeout = API poll timeout (s)
[config.gui.kk]
tabtitle = Kick
kk_backend_enabled = Enable Kick backend
kk_channel = Kick channel name
kk_api_poll_timeout = API poll timeout (s)
+192
View File
@@ -0,0 +1,192 @@
#!/usr/bin/env tclsh
package require Tk
package require json
package require json::write
package require inifile
# overall vars
set appdir [ file dirname [ file normalize [ info script ] ] ]
# check if we're running from a starpack
if [string match *app-conf $appdir] {
set appdir [ file normalize [ file join $appdir ".." ".." ".." ] ]
}
puts "Working directory: $appdir"
set conffile [ file join $appdir "config.json" ]
set labelfile [ file join $appdir "labels.ini" ]
# main configuration read/write procedures
proc readrawjson {fname} {
# read the JSON contents
set fp [open $fname r]
set rawdata [read $fp]
close $fp
# parse the JSON contents
set confdict [::json::json2dict $rawdata]
return $confdict
}
proc readjson {fname} {
set confdict [readrawjson $fname]
# update the controls according to the configuration
dict for {vname vval} $confdict {
global conf_$vname
set conf_$vname $vval
}
return $confdict
}
proc dict2json {dictToEncode} {
::json::write object {*}[dict map {k v} $dictToEncode {
if [string is boolean -strict $v] {
set v $v
} elseif [string is integer -strict $v] {
set v $v
} else {
set v [::json::write string $v]
}
}]
}
proc writejson {fname} {
set confdict [readrawjson $fname]
dict for {vname vval} $confdict {
global conf_$vname
dict set confdict $vname [set conf_$vname]
}
set fdata [dict2json $confdict]
set fp [open $fname w]
puts $fp $fdata
close $fp
}
# dialog button helpers
proc rereadconfig {fname msg} {
set answer [tk_messageBox -message $msg -icon question -type yesno]
switch -- $answer {
yes {readjson $fname}
no {}
}
}
proc saveconfig {fname msg} {
set answer [tk_messageBox -message $msg -icon question -type yesno]
switch -- $answer {
yes {writejson $fname}
no {}
}
}
proc exitapp {fname msg} {
saveconfig $fname $msg
exit
}
# read GUI labels ini file
set lbls [::ini::open $labelfile r]
set backends [::ini::value $lbls general backends]
# overall provider tab layout
ttk::notebook .prov
foreach back $backends {
set framename .prov.$back
set tabname [::ini::value $lbls config.gui.$back tabtitle]
ttk::frame $framename
.prov add $framename -text $tabname
}
# General controls (need to be hardcoded)
set dbglbl [::ini::value $lbls config.gui.general debug]
set iplbl [::ini::value $lbls config.gui.general server_listen_ip]
set portlbl [::ini::value $lbls config.gui.general server_port]
set ualbl [::ini::value $lbls config.gui.general useragent]
ttk::checkbutton .prov.general.debugchk -text $dbglbl -variable conf_debug -onvalue true -offvalue false
ttk::label .prov.general.serviplbl -text $iplbl
ttk::entry .prov.general.serviptxt -textvariable conf_server_listen_ip -width 16
ttk::label .prov.general.servportlbl -text $portlbl
ttk::entry .prov.general.servporttxt -textvariable conf_server_port -width 5
ttk::label .prov.general.ualbl -text $ualbl
ttk::entry .prov.general.uatxt -textvariable conf_useragent -width 60
grid .prov.general.debugchk -column 0 -row 0 -sticky ne -ipadx 5 -ipady 5
grid .prov.general.serviplbl -column 1 -row 0 -sticky ne -ipadx 5 -ipady 5
grid .prov.general.serviptxt -column 2 -row 0 -sticky ew -padx 5 -pady 5
grid .prov.general.servportlbl -column 3 -row 0 -sticky ne -ipadx 5 -ipady 5
grid .prov.general.servporttxt -column 4 -row 0 -sticky ew -padx 5 -pady 5
grid .prov.general.ualbl -column 0 -row 2 -sticky ne -ipadx 5 -ipady 5
grid .prov.general.uatxt -column 1 -row 2 -sticky ew -columnspan 4 -padx 5 -pady 5
# backend config controls
foreach back $backends {
if {$back ne "general"} {
set section config.gui.$back
set params [::ini::keys $lbls $section]
set rowidx 1
foreach key $params {
if {$key ne "tabtitle"} {
set lbltext [::ini::value $lbls $section $key]
set cvarname [string cat conf_ $key]
set guiname .prov.$back.$key
if {$key eq [string cat $back _backend_enabled]} {
set chkname [string cat $guiname chk]
ttk::checkbutton $chkname -text $lbltext \
-variable $cvarname -onvalue true -offvalue false
grid $chkname -column 0 -row 0 -sticky ne -ipadx 5 -ipady 5
} else {
set ewidth 40
set lblname [string cat $guiname lbl]
set txtname [string cat $guiname txt]
ttk::label $lblname -text $lbltext
ttk::entry $txtname -textvariable $cvarname -width $ewidth
grid $lblname -column 0 -row $rowidx -sticky ne -ipadx 5 -ipady 5
grid $txtname -column 1 -row $rowidx -sticky ew -padx 5 -pady 5
}
incr rowidx
}
}
}
}
# lower button row
foreach lname {reset exit save credits savemodal resetmodal} {
set vname [string cat $lname lbl]
set $vname [::ini::value $lbls config.gui.general $lname]
}
ttk::frame .btns -padding 4
ttk::button .btns.reset -text $resetlbl -command {rereadconfig $conffile $resetmodallbl}
ttk::button .btns.exit -text $exitlbl -command {exitapp $conffile $savemodallbl}
ttk::button .btns.save -text $savelbl -command {saveconfig $conffile $savemodallbl}
ttk::label .btns.lbl -text $creditslbl
pack .btns.save -side right -padx 3 -pady 3
pack .btns.exit -side right -padx 3 -pady 3
pack .btns.reset -side right -padx 3 -pady 3
pack .btns.lbl -side left -padx 3 -pady 3
# place the tabs and buttons
grid .prov -column 0 -row 0 -sticky nsew
grid .btns -column 0 -row 1 -sticky sew
# root grid geometry: don't resize the button row
grid columnconfigure . 0 -weight 1
grid rowconfigure . 0 -weight 1
grid rowconfigure . 1 -weight 0
# iterate over available platform-dependent themes, apply "clam" if none found
ttk::style theme use clam
catch {ttk::style theme use gtkTtk}
catch {ttk::style theme use aqua}
catch {ttk::style theme use winnative}
catch {ttk::style theme use vista}
wm title . "StreamGoose config"
catch {wm title . [::ini::value $lbls config.gui.general windowtitle]}
# stop working with the labels file
::ini::close $lbls
# read the initial config
readjson $conffile
+192
View File
@@ -0,0 +1,192 @@
#!/usr/bin/env tclsh
package require Tk
package require json
package require json::write
package require inifile
# overall vars
set appdir [ file dirname [ file normalize [ info script ] ] ]
# check if we're running from a starpack
if [string match *app-conf $appdir] {
set appdir [ file normalize [ file join $appdir ".." ".." ".." ] ]
}
puts "Working directory: $appdir"
set conffile [ file join $appdir "config.json" ]
set labelfile [ file join $appdir "labels.ini" ]
# main configuration read/write procedures
proc readrawjson {fname} {
# read the JSON contents
set fp [open $fname r]
set rawdata [read $fp]
close $fp
# parse the JSON contents
set confdict [::json::json2dict $rawdata]
return $confdict
}
proc readjson {fname} {
set confdict [readrawjson $fname]
# update the controls according to the configuration
dict for {vname vval} $confdict {
global conf_$vname
set conf_$vname $vval
}
return $confdict
}
proc dict2json {dictToEncode} {
::json::write object {*}[dict map {k v} $dictToEncode {
if [string is boolean -strict $v] {
set v $v
} elseif [string is integer -strict $v] {
set v $v
} else {
set v [::json::write string $v]
}
}]
}
proc writejson {fname} {
set confdict [readrawjson $fname]
dict for {vname vval} $confdict {
global conf_$vname
dict set confdict $vname [set conf_$vname]
}
set fdata [dict2json $confdict]
set fp [open $fname w]
puts $fp $fdata
close $fp
}
# dialog button helpers
proc rereadconfig {fname msg} {
set answer [tk_messageBox -message $msg -icon question -type yesno]
switch -- $answer {
yes {readjson $fname}
no {}
}
}
proc saveconfig {fname msg} {
set answer [tk_messageBox -message $msg -icon question -type yesno]
switch -- $answer {
yes {writejson $fname}
no {}
}
}
proc exitapp {fname msg} {
saveconfig $fname $msg
exit
}
# read GUI labels ini file
set lbls [::ini::open $labelfile r]
set backends [::ini::value $lbls general backends]
# overall provider tab layout
ttk::notebook .prov
foreach back $backends {
set framename .prov.$back
set tabname [::ini::value $lbls config.gui.$back tabtitle]
ttk::frame $framename
.prov add $framename -text $tabname
}
# General controls (need to be hardcoded)
set dbglbl [::ini::value $lbls config.gui.general debug]
set iplbl [::ini::value $lbls config.gui.general server_listen_ip]
set portlbl [::ini::value $lbls config.gui.general server_port]
set ualbl [::ini::value $lbls config.gui.general useragent]
ttk::checkbutton .prov.general.debugchk -text $dbglbl -variable conf_debug -onvalue true -offvalue false
ttk::label .prov.general.serviplbl -text $iplbl
ttk::entry .prov.general.serviptxt -textvariable conf_server_listen_ip -width 16
ttk::label .prov.general.servportlbl -text $portlbl
ttk::entry .prov.general.servporttxt -textvariable conf_server_port -width 5
ttk::label .prov.general.ualbl -text $ualbl
ttk::entry .prov.general.uatxt -textvariable conf_useragent -width 60
grid .prov.general.debugchk -column 0 -row 0 -sticky ne -ipadx 5 -ipady 5
grid .prov.general.serviplbl -column 1 -row 0 -sticky ne -ipadx 5 -ipady 5
grid .prov.general.serviptxt -column 2 -row 0 -sticky ew -padx 5 -pady 5
grid .prov.general.servportlbl -column 3 -row 0 -sticky ne -ipadx 5 -ipady 5
grid .prov.general.servporttxt -column 4 -row 0 -sticky ew -padx 5 -pady 5
grid .prov.general.ualbl -column 0 -row 2 -sticky ne -ipadx 5 -ipady 5
grid .prov.general.uatxt -column 1 -row 2 -sticky ew -columnspan 4 -padx 5 -pady 5
# backend config controls
foreach back $backends {
if {$back ne "general"} {
set section config.gui.$back
set params [::ini::keys $lbls $section]
set rowidx 1
foreach key $params {
if {$key ne "tabtitle"} {
set lbltext [::ini::value $lbls $section $key]
set cvarname [string cat conf_ $key]
set guiname .prov.$back.$key
if {$key eq [string cat $back _backend_enabled]} {
set chkname [string cat $guiname chk]
ttk::checkbutton $chkname -text $lbltext \
-variable $cvarname -onvalue true -offvalue false
grid $chkname -column 0 -row 0 -sticky ne -ipadx 5 -ipady 5
} else {
set ewidth 40
set lblname [string cat $guiname lbl]
set txtname [string cat $guiname txt]
ttk::label $lblname -text $lbltext
ttk::entry $txtname -textvariable $cvarname -width $ewidth
grid $lblname -column 0 -row $rowidx -sticky ne -ipadx 5 -ipady 5
grid $txtname -column 1 -row $rowidx -sticky ew -padx 5 -pady 5
}
incr rowidx
}
}
}
}
# lower button row
foreach lname {reset exit save credits savemodal resetmodal} {
set vname [string cat $lname lbl]
set $vname [::ini::value $lbls config.gui.general $lname]
}
ttk::frame .btns -padding 4
ttk::button .btns.reset -text $resetlbl -command {rereadconfig $conffile $resetmodallbl}
ttk::button .btns.exit -text $exitlbl -command {exitapp $conffile $savemodallbl}
ttk::button .btns.save -text $savelbl -command {saveconfig $conffile $savemodallbl}
ttk::label .btns.lbl -text $creditslbl
pack .btns.save -side right -padx 3 -pady 3
pack .btns.exit -side right -padx 3 -pady 3
pack .btns.reset -side right -padx 3 -pady 3
pack .btns.lbl -side left -padx 3 -pady 3
# place the tabs and buttons
grid .prov -column 0 -row 0 -sticky nsew
grid .btns -column 0 -row 1 -sticky sew
# root grid geometry: don't resize the button row
grid columnconfigure . 0 -weight 1
grid rowconfigure . 0 -weight 1
grid rowconfigure . 1 -weight 0
# iterate over available platform-dependent themes, apply "clam" if none found
ttk::style theme use clam
catch {ttk::style theme use gtkTtk}
catch {ttk::style theme use aqua}
catch {ttk::style theme use winnative}
catch {ttk::style theme use vista}
wm title . "StreamGoose config"
catch {wm title . [::ini::value $lbls config.gui.general windowtitle]}
# stop working with the labels file
::ini::close $lbls
# read the initial config
readjson $conffile
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.