1#!@SH@ 2# 3# Copyright (C) 2021 Claes Nästén 4# 5 6# URL for the pekwm theme site 7PEKWM_THEMES="https://www.pekwm.se/themes" 8# URL for the theme index file 9REMOTE_THEME_INDEX="$PEKWM_THEMES/themes-v1.csv" 10# Path to local copy of the theme index file 11LOCAL_THEME_INDEX="$HOME/.pekwm/themes-v1.csv" 12# Max age of the local theme index before it is re fetchted. 13LOCAL_THEME_INDEX_MAX_AGE="86400" 14# Path to local directory where screenshots are cached, used with the 15# show command. 16SCREENSHOT_CACHE_DIR="$HOME/.cache/pekwm-themes" 17# Field delimiter 18SEP="¤" 19 20# Stop execution with exit code 1 after printing provided error 21# message 22# 23# die error message 24die() 25{ 26 echo "ERROR: $@" 27 exit 1 28} 29 30# Print progress message 31# 32# progress progress message 33progress() 34{ 35 echo "`date '+%Y%m%dT%H:%M:%S'`: $@" 36} 37 38# Print usage information 39# 40# usage exit-code 41usage() 42{ 43 echo "usage: $0 [install|uninstall|show|search|new|update]" 44 echo "" 45 echo " install [-a] name install theme" 46 echo " uninstall name uninstall theme (removes local changes)" 47 echo " show name display theme preview" 48 echo " search [pattern] search for theme" 49 echo " new name create new theme" 50 echo " update update theme index" 51 echo "" 52 exit $1 53} 54 55# Fetch remote file using HTTP(S)/FTP. 56# 57# fetch_file url local-path 58fetch_file() 59{ 60 progress "fetching $1 to $2..." 61 62 CURL=`which curl 2>/dev/null | grep '^/'` 63 WGET=`which wget 2>/dev/null | grep '^/'` 64 FTP=`which ftp 2>/dev/null | grep '^/'` 65 66 if test "x$CURL" != "x"; then 67 $CURL -L -s -o "$2" "$1" 68 elif test "$WGET" != "x"; then 69 $WGET -q -O "$2" "$1" 70 elif test "$FTP" != "x"; then 71 $FTP -h 2>&1 | grep -q http 72 if $? -eq 0; then 73 $FTP -o "$2" "$1" 74 else 75 die "unsupported ftp, no HTTP support" 76 fi 77 else 78 die "no supported HTTP client (curl, wget, BSD ftp)" 79 fi 80} 81 82# Update local copy of the theme index 83# 84# update_index 85update_index() 86{ 87 mkdir -p "$HOME/.pekwm" 88 if test -e "${LOCAL_THEME_INDEX}.stamp"; then 89 created=`cat ${LOCAL_THEME_INDEX}.stamp` 90 now=`pekwm_ctrl -a util timestamp 2>/dev/null` 91 age=$(($now - $created)) 92 if test $age -gt $LOCAL_THEME_INDEX_MAX_AGE; then 93 rm -f "$LOCAL_THEME_INDEX" 94 fi 95 else 96 # no stamp file exists, force a refresh 97 rm -f "$LOCAL_THEME_INDEX" 98 fi 99 100 if test ! -e "$LOCAL_THEME_INDEX"; then 101 fetch_file "$REMOTE_THEME_INDEX" "$LOCAL_THEME_INDEX" 102 pekwm_ctrl -a util timestamp >"${LOCAL_THEME_INDEX}.stamp" 2>/dev/null 103 fi 104} 105 106# Read theme data from the local theme index, populates theme_remote, 107# theme_branch, theme_author and theme_title 108# 109# read_theme name 110read_theme() 111{ 112 line=`sed 1d $LOCAL_THEME_INDEX | grep "^$1$SEP"` 113 if test -z "$line"; then 114 die "no theme named $1" 115 fi 116 theme_name="$1" 117 theme_remote=`echo $line | @AWK@ -F "$SEP" '{ print $2 }'` 118 theme_branch=`echo $line | @AWK@ -F "$SEP" '{ print $3 }'` 119 theme_base_path=`echo $line | @AWK@ -F "$SEP" '{ print $4 }'` 120 theme_author=`echo $line | @AWK@ -F "$SEP" '{ print $6 }'` 121 theme_title=`echo $line | @AWK@ -F "$SEP" '{ print $7 }'` 122} 123 124# Set theme_path to local theme path 125# 126# set_theme_path name 127set_theme_path() 128{ 129 theme_path="$HOME/.pekwm/themes/$1" 130} 131 132# Install theme to $HOME/.pekwm/themes, if activate is given the 133# current theme is changed. 134# 135# theme_install name [activate] 136theme_install() 137{ 138 read_theme "$1" 139 set_theme_path "$1" 140 141 if test -z "$theme_base_path"; then 142 theme_install_plain 143 else 144 theme_install_base_path 145 fi 146 147 if test "x$2" = "xactivate"; then 148 @CMAKE_INSTALL_PREFIX@/share/pekwm/scripts/pekwm_themeset.sh \ 149 "$HOME/.pekwm/themes" "$theme_path" 150 fi 151} 152 153# Install/update theme residing in the root of the GIT repository (use 154# the theme_ variables from read_theme) 155# 156# theme_install_plain 157theme_install_plain() 158{ 159 git_update_repo "$theme_remote" "$theme_branch" "$theme_path" 160} 161 162# Install/update theme not in the root of the GIT repository (use the 163# theme_ variables from read_theme) 164# 165# theme_install_base_path 166theme_install_base_path() 167{ 168 mkdir -p "$HOME/.pekwm/themes_git" 169 repo_path="$HOME/.pekwm/themes_git/`echo $theme_remote | tr -cd '[:alnum:]'`" 170 git_update_repo "$theme_remote" "$theme_branch" "$repo_path" 171 172 ln -sf "$repo_path/$theme_base_path" "$HOME/.pekwm/themes/$theme_name" 173} 174 175# Clone or update GIT repository 176# 177# remote branch local-path 178git_update_repo() 179{ 180 if test -d "$3"; then 181 echo "$3 already exist, updating" 182 git -C "$3" remote set-url origin "$1" 183 git -C "$3" fetch origin 184 else 185 echo "cloning $1" 186 git clone "$1" "$3" 187 fi 188 189 git -C "$3" checkout "$2" 190 git -C "$3" reset --hard "origin/$2" 191} 192 193# Uninstall theme from $HOME/.pekwm/themes 194# 195# theme_uninstall name 196theme_uninstall() 197{ 198 read_theme $1 199 set_theme_path $1 200 if test -d "$theme_path"; then 201 echo "uninstalling $1" 202 rm -rf "$theme_path" 203 fi 204} 205 206# Update local copy of theme screenshot 207# 208# update_theme_screenshot name 209update_theme_screenshot() 210{ 211 mkdir -p $SCREENSHOT_CACHE_DIR 212 path="$SCREENSHOT_CACHE_DIR/$1.png" 213 if test ! -e "$path"; then 214 fetch_file $PEKWM_THEMES/$1.png $path 215 fi 216} 217 218# Search for the 219# 220# theme_search [pattern] 221theme_search() 222{ 223 if test -z "$1"; then 224 echo "available themes:" 225 else 226 echo "themes matching $1:" 227 fi 228 229 search="$1" 230 if test -z "$search"; then 231 search=".*" 232 fi 233 234 sed 1d "$LOCAL_THEME_INDEX" | @AWK@ -F "$SEP" '{ print $1 }' | grep "$search" 235} 236 237# Show theme using pekwm_dialog, if install is choosen the theme is 238# installed and activated. 239# 240# theme_show name 241theme_show() 242{ 243 read_theme "$1" 244 set_theme_path "$1" 245 update_theme_screenshot "$1" 246 pekwm_dialog -i "$SCREENSHOT_CACHE_DIR/$1.png" -t "pekwm theme: $1" \ 247 -o Install -o Close $theme_title 248 if test $? -eq 0; then 249 theme_install "$1" "activate" 250 fi 251} 252 253# Generate a theme directory 254# 255# theme_new name 256theme_new() 257{ 258 theme_name="$1" 259 260 if test -z "$theme_name"; then 261 die "a theme name must be provided to pekwm_theme new" 262 fi 263 if test -e "$theme_name"; then 264 die "a file or directory with the name $theme_name already exist" 265 fi 266 267 mkdir $theme_name || die "failed to create $theme_name" 268 mkdir $theme_name/backgrounds \ 269 || die "failed to create $theme_name/backgrounds" 270 271 cat > $theme_name/theme <<EOF 272\$FONT = "-*-fixed-*-r-*-*-*-*-*-*-*-*-iso10646-*" 273\$MENU_FONT = "-*-fixed-*-r-*-*-*-*-*-*-*-*-iso10646-*" 274 275\$BG_TEX = "Solid #ffffff" 276 277\$COLOR_FO = "#eeeeee" 278\$COLOR_FO_HI = "#ffffff" 279\$COLOR_FO_LO = "#dddddd" 280\$COLOR_BD_FO = "#000000" 281\$TEXT_FO = "#333333" 282 283\$COLOR_FO_SEL = "#efefef" 284\$COLOR_FO_SEL_HI = "#ffffff" 285\$COLOR_FO_SEL_LO = "#dddddd" 286\$TEXT_FO_SEL = "#000000" 287 288\$COLOR_UN = "#aaaaaa" 289\$COLOR_UN_HI = "#bbbbbb" 290\$COLOR_UN_LO = "#999999" 291\$COLOR_BD_UN = "#666666" 292\$TEXT_UN = "#666666" 293 294\$COLOR_UN_SEL = "#afafaf" 295\$COLOR_UN_SEL_HI = "#bbbbbb" 296\$COLOR_UN_SEL_LO = "#999999" 297\$TEXT_UN_SEL = "#333333" 298 299INCLUDE = "\$THEME_DIR/template" 300EOF 301 302 cat > $theme_name/template <<EOF 303\$TEX_FO = "SolidRaised \$COLOR_FO \$COLOR_FO_HI \$COLOR_FO_LO 1 0" 304\$TEX_FO_SEL = "SolidRaised \$COLOR_FO_SEL \$COLOR_FO_SEL_HI \$COLOR_FO_SEL_LO 1 0" 305\$TEX_UN = "SolidRaised \$COLOR_UN \$COLOR_UN_HI \$COLOR_UN_LO 1 0" 306\$TEX_UN_SEL = "SolidRaised \$COLOR_UN_SEL \$COLOR_UN_SEL_HI \$COLOR_UN_SEL_LO" 307\$TEX_SEP_FO = "Solid \$COLOR_FO_HI 2x20" 308\$TEX_SEP_UN = "Solid \$COLOR_UN_HI 2x20" 309 310# Theme format version 311Version = "2" 312 313Require { 314 Templates = "True" 315} 316 317Background { 318 Texture = "\$BG_TEX" 319} 320 321ColorMaps { 322 ColorMap = "Focused" { 323 Map = "#aaaaaa" { To = "\$COLOR_FO" } 324 Map = "#ffffff" { To = "\$COLOR_FO_LO" } 325 } 326 ColorMap = "Unfocused" { 327 Map = "#aaaaaa" { To = "\$COLOR_UN" } 328 Map = "#ffffff" { To = "\$COLOR_UN_LO" } 329 } 330 ColorMap = "Hoover" { 331 Map = "#aaaaaa" { To = "\$COLOR_FO_SEL" } 332 Map = "#ffffff" { To = "\$COLOR_FO_SEL_HI" } 333 Map = "#000000" { To = "#ffffff" } 334 } 335 ColorMap = "Pressed" { 336 Map = "#aaaaaa" { To = "\$COLOR_FO" } 337 Map = "#ffffff" { To = "\$COLOR_FO_LO" } 338 Map = "#000000" { To = "#ffffff" } 339 } 340} 341 342Define = "Border" { 343 Focused { 344 TopLeft = "Solid \$COLOR_BD_FO 2x2" 345 Top = "Solid \$COLOR_BD_FO 2x2" 346 TopRight = "Solid \$COLOR_BD_FO 2x2" 347 Left = "Solid \$COLOR_BD_FO 2x2" 348 Right = "Solid \$COLOR_BD_FO 2x2" 349 BottomLeft = "Solid \$COLOR_BD_FO 2x2" 350 Bottom = "Solid \$COLOR_BD_FO 2x2" 351 BottomRight = "Solid \$COLOR_BD_FO 2x2" 352 } 353 Unfocused { 354 TopLeft = "Solid \$COLOR_BD_UN 2x2" 355 Top = "Solid \$COLOR_BD_UN 2x2" 356 TopRight = "Solid \$COLOR_BD_UN 2x2" 357 Left = "Solid \$COLOR_BD_UN 2x2" 358 Right = "Solid \$COLOR_BD_UN 2x2" 359 BottomLeft = "Solid \$COLOR_BD_UN 2x2" 360 Bottom = "Solid \$COLOR_BD_UN 2x2" 361 BottomRight = "Solid \$COLOR_BD_UN 2x2" 362 } 363} 364 365Define = "EmptyBorder" { 366 Focused { 367 TopLeft = "Empty" 368 Top = "Empty" 369 TopRight = "Empty" 370 Left = "Empty" 371 Right = "Empty" 372 BottomLeft = "Empty" 373 Bottom = "Empty" 374 BottomRight = "Empty" 375 } 376 Unfocused { 377 TopLeft = "Empty" 378 Top = "Empty" 379 TopRight = "Empty" 380 Left = "Empty" 381 Right = "Empty" 382 BottomLeft = "Empty" 383 Bottom = "Empty" 384 BottomRight = "Empty" 385 } 386} 387 388Define = "Buttons" { 389 Right = "Close" { 390 Focused = "ImageMapped Focused button-close.xpm" 391 Unfocused = "ImageMapped Unfocused button-close.xpm" 392 Hoover = "ImageMapped Hoover button-close.xpm" 393 Pressed = "ImageMapped Pressed button-close.xpm" 394 Button = "1" { Actions = "Close" } 395 Button = "3" { Actions = "Kill" } 396 } 397} 398 399Define = "Decor" { 400 Height = "20" 401 Pad = "2 2 5 5" 402 403 Focused = "\$TEX_FO" 404 Unfocused = "\$TEX_UN" 405 406 Tab { 407 Focused = "\$TEX_FO" 408 FocusedSelected = "\$TEX_FO_SEL" 409 Unfocused = "\$TEX_UN" 410 UnfocusedSelected = "\$TEX_UN_SEL" 411 } 412 Separator { 413 Focused = "\$TEX_SEP_FO" 414 Unfocused = "\$TEX_SEP_UN" 415 } 416 Font { 417 Focused = "\$FONT#Center" 418 } 419 FontColor { 420 Focused = "\$TEXT_FO" 421 FocusedSelected = "\$TEXT_FO_SEL" 422 Unfocused = "\$TEXT_UN" 423 UnfocusedSelected = "\$TEXT_UN_SEL" 424 } 425} 426 427Define = "EmptyDecor" { 428 Height = "0" 429 Pad = "0 0 0 0" 430 Focused = "Empty" 431 Unfocused = "Empty" 432 Tab { 433 Focused = "Empty" 434 FocusedSelected = "Empty" 435 Unfocused = "Empty" 436 UnfocusedSelected = "Empty" 437 } 438 Separator { 439 Focused = "Empty" 440 Unfocused = "Empty" 441 } 442 Font { 443 Focused = "Empty" 444 } 445 FontColor { 446 Focused = "Empty" 447 FocusedSelected = "Empty" 448 Unfocused = "Empty" 449 UnfocusedSelected = "Empty" 450 } 451} 452 453Decor = "Default" { 454 @Decor 455 Border { 456 @Border 457 } 458 Buttons { 459 @Buttons 460 } 461} 462 463Decor = "InputDialog" { 464 @EmptyDecor 465 Border { 466 @Border 467 } 468} 469 470Decor = "Menu" { 471 @Decor 472 Border { 473 @Border 474 } 475} 476 477Decor = "StatusWindow" { 478 @EmptyDecor 479 Border { 480 @Border 481 } 482} 483 484Decor = "WorkspaceIndicator" { 485 @EmptyDecor 486 Border { 487 @Border 488 } 489} 490 491CmdDialog { 492 Font = "\$FONT" 493 Texture = "\$TEX_UN" 494 Text = "\$TEXT_UN" 495 Pad = "4 4 4 4" 496} 497 498Dialog { 499 Background = "Solid \$COLOR_UN" 500 TitleFont = "\$MENU_FONT" 501 TitleColor = "\$TEXT_FO_SEL" 502 Font = "\$FONT" 503 Text = "\$TEXT_FO" 504 Pad = "4 4 4 4" 505 506 Button { 507 Font = "\$FONT" 508 Text = "\$TEXT_FO_SEL" 509 510 Focused = "\$TEX_FO" 511 Unfocused = "\$TEX_UN" 512 Hoover = "\$TEX_FO_SEL" 513 Pressed = "\$TEX_UN_SEL" 514 } 515} 516 517Harbour { 518 Texture = "\$TEX_UN" 519} 520 521Menu { 522 Focused { 523 Font = "\$MENU_FONT" 524 Background = "\$TEX_FO" 525 Item = "Empty" 526 Text = "\$TEXT_FO" 527 Separator = "Solid \$COLOR_FO_HI 1x1" 528 Arrow = "Solid \$COLOR_FO_LO 5x5" 529 } 530 Unfocused { 531 Font = "\$MENU_FONT" 532 Background = "\$TEX_UN" 533 Item = "Empty" 534 Text = "\$TEXT_UN" 535 Separator = "Solid \$COLOR_UN_HI 1x1" 536 Arrow = "Solid \$COLOR_UN_LO 5x5" 537 } 538 Selected { 539 Font = "\$MENU_FONT" 540 Background = "\$TEX_FO_SEL" 541 Item = "\$TEX_FO_SEL" 542 Text = "\$TEXT_FO_SEL" 543 Arrow = "Solid \$COLOR_FO_SEL_LO 5x5" 544 } 545 Pad = "2 2 4 4" 546} 547 548Status { 549 Font = "\$FONT" 550 Texture = "Solid \$COLOR_UN" 551 Text = "\$TEXT_UN" 552 Pad = "0 0 0 0" 553} 554 555WorkspaceIndicator { 556 Font = "\$FONT#Center" 557 Background = "Solid \$COLOR_UN" 558 Workspace = "Solid \$COLOR_UN_SEL" 559 WorkspaceActive = "Solid \$COLOR_FO_SEL" 560 Text = "\$TEXT_FO" 561 EdgePadding = "2" 562 WorkspacePadding = "2" 563} 564EOF 565 566 cat > $theme_name/button-close.xpm <<EOF 567/* XPM */ 568static char *button_close[] = { 569/* columns rows colors chars-per-pixel */ 570"20 20 3 1 ", 571" c #aaaaaaaaaaaa", 572". c black", 573"X c white", 574/* pixels */ 575"XXXXXXXXXXXXXXXXXXXX", 576"X.. ......X", 577"X... ...... X", 578"X... ..... X", 579"X.... ..... X", 580"X..... ..... X", 581"X ..... ..... X", 582"X ......... X", 583"X ....... X", 584"X ..... X", 585"X ...... X", 586"X ........ X", 587"X .......... X", 588"X ..... ..... X", 589"X .... ..... X", 590"X.... ..... X", 591"X.... ...... X", 592"X... ......X", 593"X.. .....X", 594"XXXXXXXXXXXXXXXXXXXX" 595}; 596EOF 597 598 echo "generated theme $theme_name" 599} 600 601# Update theme index 602# 603# theme_update_index 604theme_update() 605{ 606 rm -f "$LOCAL_THEME_INDEX.stamp" "$LOCAL_THEME_INDEX" 607 update_index 608} 609 610if test -z "$HOME"; then 611 die "\$HOME is not set, can not proceed" 612fi 613if test -z "`which git 2>/dev/null | grep '^/'`"; then 614 die "git must be available in the path, can not proceed" 615fi 616 617if test -z "$1"; then 618 usage 1 619elif test "x$1" = "xupdate"; then 620 theme_update 621else 622 update_index 623 624 if test "x$1" = "xinstall"; then 625 if test "$2" = "-a"; then 626 theme_install "$3" "activate" 627 else 628 theme_install "$2" 629 fi 630 elif test "x$1" = "xuninstall"; then 631 theme_uninstall "$2" 632 elif test "x$1" = "xshow"; then 633 theme_show "$2" "$3" 634 elif test "x$1" = "xsearch"; then 635 theme_search "$2" 636 elif test "x$1" = "xnew"; then 637 theme_new "$2" 638 else 639 usage 1 640 fi 641fi 642