diff --git a/Makefile b/Makefile
index 1027abb..0c8be77 100644
--- a/Makefile
+++ b/Makefile
@@ -1,12 +1,12 @@
# makefile for layout-switcher
build:
- makepkg
+ makepkg -s
install:
- pamac-installer tromjaro-layout-switcher-*.tar.xz
+ pamac-installer tromjaro-layout-switcher-*.tar.zst
remove:
pamac-installer --remove tromjaro-layout-switcher
clean:
- $(RM) -rf src/ pkg/ *.tar.xz
+ $(RM) -rf src/ pkg/ icons.zip profiles.zip *.tar.zst
diff --git a/PKGBUILD b/PKGBUILD
index 7d4aa59..35e8978 100644
--- a/PKGBUILD
+++ b/PKGBUILD
@@ -3,22 +3,31 @@ pkgname=tromjaro-layout-switcher
pkgver=1.4
pkgrel=1
pkgdesc="TROMjaro Layout switcher application"
-arch=(any)
-url=""
+arch=('x86_64')
+url="https://git.trom.tf/TROMjaro/layout-switcher-package"
license=(unknown)
-depends=('yad'
- 'zensu'
- 'xfce4-panel-profiles'
+depends=('xfce4-panel-profiles'
+ 'util-linux'
+ 'xfconf'
+ 'polkit'
+ 'mate-hud'
+ 'psmisc'
+ 'pacman'
+ 'coreutils'
'vala-panel-appmenu-xfce-git'
'vala-panel-appmenu-common-git'
'libdbusmenu-gtk2'
'appmenu-gtk-module')
+makedepends=('choosenim')
optdepends=('vala-panel-appmenu-registrar')
provides=('tromjaro-layout-switcher')
backup=()
options=(!strip)
-source=("layout-switcher"
+source=("layoutSwitcher.nim"
+ "toggleGlobalMenu.nim"
+ "sharedModule.nim"
"tromjaro-layout-switcher.desktop"
+ "com.tromjaro.LayoutSwitcher.policy"
"tromjaro-layout-switcher.svg"
"icons.zip::https://www.drive.tromsite.com/s/SWmAcokLcxLB3gM/download"
"profiles.zip::https://www.drive.tromsite.com/s/6dHoNjar9wJzeLd/download")
@@ -26,23 +35,51 @@ sha256sums=('SKIP'
'SKIP'
'SKIP'
'SKIP'
+'SKIP'
+'SKIP'
+'SKIP'
'SKIP')
-package() {
- # copy icons
- install -d ${pkgdir}/usr/share/tromjaro-layout-switcher/icons
- install -Dm644 ${srcdir}/icons/* ${pkgdir}/usr/share/tromjaro-layout-switcher/icons
-
- # copy profiles
- install -d ${pkgdir}/usr/share/tromjaro-layout-switcher/profiles
- install -Dm644 ${srcdir}/profiles/* ${pkgdir}/usr/share/tromjaro-layout-switcher/profiles
-
-
- # copy executables
- install -Dm644 ${srcdir}/tromjaro-layout-switcher.desktop ${pkgdir}/usr/share/applications/tromjaro-layout-switcher.desktop
- install -Dm755 ${srcdir}/layout-switcher ${pkgdir}/usr/bin/tromjaro-layout-switcher
-
- # copy icon
- install -d ${pkgdir}/usr/share/icons/hicolor/scalable/apps
- install -Dm644 ${srcdir}/tromjaro-layout-switcher.svg ${pkgdir}/usr/share/icons/hicolor/scalable/apps
-
+prepare() {
+ # Disable telemetry in choosenim if user is running it for the first time
+ [ -f "$HOME"/.choosenim/analytics ] || {
+ mkdir -p "$HOME"/.choosenim
+ touch "$HOME"/.choosenim/analytics
+ }
+ echo 'Install Nim if not already installed:'
+ choosenim stable
+ echo 'Update Nim to the latest stable release:'
+ choosenim update stable
+ echo 'Refresh nimble package list:'
+ "$HOME"/.nimble/bin/nimble --nim:"$HOME"/.nimble/bin/nim refresh
+ echo 'Install owlkettle from the latest git commit:'
+ "$HOME"/.nimble/bin/nimble --nim:"$HOME"/.nimble/bin/nim install owlkettle@#head
+}
+build(){
+ echo 'Compile layoutSwitcher.nim for release build:'
+ "$HOME"/.nimble/bin/nim c -d:release --out:${srcdir}/layoutSwitcher ${srcdir}/layoutSwitcher.nim
+ echo 'Compile toggleGlobalMenu.nim for release build:'
+ "$HOME"/.nimble/bin/nim c -d:release --out:${srcdir}/toggleGlobalMenu ${srcdir}/toggleGlobalMenu.nim
+}
+package() {
+ # copy icons
+ install -d ${pkgdir}/usr/share/tromjaro-layout-switcher/icons
+ install -Dm644 ${srcdir}/icons/* ${pkgdir}/usr/share/tromjaro-layout-switcher/icons
+
+ # copy profiles
+ install -d ${pkgdir}/usr/share/tromjaro-layout-switcher/profiles
+ install -Dm644 ${srcdir}/profiles/* ${pkgdir}/usr/share/tromjaro-layout-switcher/profiles
+
+ # copy executables
+ install -d ${pkgdir}/usr/share/applications
+ install -Dm644 ${srcdir}/tromjaro-layout-switcher.desktop ${pkgdir}/usr/share/applications
+ install -Dm755 ${srcdir}/layoutSwitcher ${pkgdir}/usr/bin/tromjaro-layout-switcher
+ install -Dm755 ${srcdir}/toggleGlobalMenu ${pkgdir}/usr/bin/toggle-global-menu
+
+ # copy polkit action
+ install -d ${pkgdir}/usr/share/polkit-1/actions
+ install -Dm644 com.tromjaro.LayoutSwitcher.policy ${pkgdir}/usr/share/polkit-1/actions
+
+ # copy desktop icon
+ install -d ${pkgdir}/usr/share/icons/hicolor/scalable/apps
+ install -Dm644 ${srcdir}/tromjaro-layout-switcher.svg ${pkgdir}/usr/share/icons/hicolor/scalable/apps
}
diff --git a/com.tromjaro.LayoutSwitcher.policy b/com.tromjaro.LayoutSwitcher.policy
new file mode 100644
index 0000000..a5124ca
--- /dev/null
+++ b/com.tromjaro.LayoutSwitcher.policy
@@ -0,0 +1,19 @@
+
+
+
+ TROMjaro
+ https://www.tromjaro.com/
+ tromjaro-layout-switcher
+
+
diff --git a/layout-switcher b/layout-switcher
deleted file mode 100644
index 661af07..0000000
--- a/layout-switcher
+++ /dev/null
@@ -1,60 +0,0 @@
-#! /bin/bash
-data=/usr/share/tromjaro-layout-switcher
-icons=$data/icons
-profiles=$data/profiles
-
-enable-top-bar-integration() {
- # Enable global menus
- pacman -Qq vala-panel-appmenu-registrar ||
- zensu SKIP_AUTOSNAP= pacman -S --noconfirm vala-panel-appmenu-registrar ||
- { notify-send 'Error' 'Failed installing vala-panel-appmenu-registrar !\nmake sure that you typed the password correctly or that you are not currently installing or updating your system\nEnabling the internet connection might solve the issue'; exit; }
- pacman -Qq vala-panel-appmenu-registrar || exit
-
- # Hide window borders when maximized
- xfconf-query -c xfwm4 -p /general/borderless_maximize -n -t bool -s true
- # Hide top bar of windows when maximized
- xfconf-query -c xfwm4 -p /general/titleless_maximize -n -t bool -s true
- # Put window buttons on left side
- xfconf-query -c xfwm4 -p /general/button_layout -n -t string -s "CMH|"
-}
-
-disable-top-bar-integration() {
- # Disable global menus
- ! pacman -Qq vala-panel-appmenu-registrar ||
- zensu SKIP_AUTOSNAP= pacman -R --noconfirm vala-panel-appmenu-registrar ||
- { notify-send 'Error' 'Failed removing vala-panel-appmenu-registrar !\nmake sure that you typed the password correctly or that you are not currently installing or updating your system'; exit; }
- pacman -Qq vala-panel-appmenu-registrar && exit
- killall appmenu-registrar
-
- # Hide window borders when maximized
- xfconf-query -c xfwm4 -p /general/borderless_maximize -n -t bool -s true
- # Don't hide top bar of windows when maximized
- xfconf-query -c xfwm4 -p /general/titleless_maximize -n -t bool -s false
- # Put window buttons on right side
- xfconf-query -c xfwm4 -p /general/button_layout -n -t string -s "|HMC"
-}
-
-choice=$(
-yad --no-buttons --center --keep-icon-size --separator='' --use-interp --title 'TROMjaro Layout Switcher' --text-align=center --text ' Please use the Panel Profiles to save your current configuration in case you did any manual changes, else you may lose them.
-Changing to or from any layout that has global menus, will require your admin password.' --form --columns 3 \
---field=!"$icons/windows.png"!'Windows-Like (default)':BTN "kill -USR1 \$YAD_PID; echo -n 'Windows-Like'" \
---field=!"$icons/macos.png"!'MacOS-Like (experimental)':BTN "kill -USR1 \$YAD_PID; echo -n 'MacOS-Like'" \
---field=!"$icons/mx.png"!'MX-Like':BTN "kill -USR1 \$YAD_PID; echo -n 'MX-Like'" \
---field=!"$icons/gnome.png"!'Gnome-Like':BTN "kill -USR1 \$YAD_PID; echo -n 'Gnome-Like'" \
---field=!"$icons/unity.png"!'Unity-Like (experimental)':BTN "kill -USR1 \$YAD_PID; echo -n 'Unity-Like'" \
---field=!"$icons/topx.png"!'TopX-Like':BTN "kill -USR1 \$YAD_PID; echo -n 'TopX-Like'"
-)
-
-case "$choice" in
- 'Windows-Like'|'MX-Like'|'Gnome-Like'|'TopX-Like')
- disable-top-bar-integration ;;
- 'Unity-Like'|'MacOS-Like')
- enable-top-bar-integration ;;
- *) exit ;;
-esac
-
-# Apply the XFCE panel profiles for the chosen layout
-xfce4-panel-profiles load "$profiles/$choice.tar.bz2"
-killall mate-hud && setsid -f /usr/lib/mate-hud/mate-hud
-
-sleep 2.5 && notify-send "$choice layout was enabled"
\ No newline at end of file
diff --git a/layoutSwitcher.nim b/layoutSwitcher.nim
new file mode 100644
index 0000000..9992ba1
--- /dev/null
+++ b/layoutSwitcher.nim
@@ -0,0 +1,169 @@
+from std/os import symlinkExists, getConfigDir, `/`, sleep
+from std/envvars import existsEnv, getEnv, delEnv, putEnv
+from std/options import Option, some, get, isNone
+from std/strutils import toLowerAscii, split
+import owlkettle, sharedModule
+
+const
+ dataDir = "/usr/share/tromjaro-layout-switcher"
+ iconsDir = dataDir / "icons"
+ profilesDir = dataDir / "profiles"
+ # GTK CSS for changing the default icon size and shape of our layout buttons
+ gtkCSS = """
+.layout-button > image {
+ -gtk-icon-transform: scale(19);
+}
+
+.layout-button {
+ min-height: 200px;
+ min-width: 300px;
+}"""
+ appID = "com.tromjaro.LayoutSwitcher"
+ layoutsGrid = [
+ [ "Windows-Like (default)", "MacOS-Like (experimental)", "MX-Like" ],
+ [ "Gnome-Like", "Unity-Like (experimental)", "TopX-Like" ]
+ ]
+
+proc enableTopBarIntegration(): bool =
+ if not isGlobalMenuEnabled():
+ # Enable global menu
+ let exitCode = runCommand("/usr/bin/pkexec", ["/usr/bin/toggle-global-menu", "enable"])
+ case exitCode
+ of 0:
+ discard
+ of 126:
+ return false
+ of 127:
+ sendNotification(appID, "Layout Switcher", "Authentication failed!")
+ return false
+ else:
+ sendNotification(appID, "Failed installing global menu!", "Make sure that you are not currently installing any apps or updating the system.\nEnabling internet connection might solve the issue.")
+ return false
+ # Hide window borders when maximized
+ discard runCommand("/usr/bin/xfconf-query",
+ args=["--channel=xfwm4", "--property=/general/borderless_maximize", "--create", "--type=bool", "--set=true"])
+ # Hide top bar of windows when maximized
+ discard runCommand("/usr/bin/xfconf-query",
+ args=["--channel=xfwm4", "--property=/general/titleless_maximize", "--create", "--type=bool", "--set=true"])
+ # Put window buttons on left side
+ discard runCommand("/usr/bin/xfconf-query",
+ args=["--channel=xfwm4", "--property=/general/button_layout", "--create", "--type=string", "--set=CMH|"])
+ return true
+
+proc disableTopBarIntegration(): bool =
+ if isGlobalMenuEnabled():
+ # Disable global menu
+ let exitCode = runCommand("/usr/bin/pkexec", ["/usr/bin/toggle-global-menu", "disable"])
+ case exitCode
+ of 0:
+ discard
+ of 126:
+ return false
+ of 127:
+ sendNotification(appID, "Layout Switcher", "Authentication failed!")
+ else:
+ sendNotification(appID, "Failed removing global menu!", "Make sure that you are not currently installing any apps or updating the system.")
+ return false
+ # Hide window borders when maximized
+ discard runCommand("/usr/bin/xfconf-query",
+ args=["--channel=xfwm4", "--property=/general/borderless_maximize", "--create", "--type=bool", "--set=true"])
+ # Don't hide top bar of windows when maximized
+ discard runCommand("/usr/bin/xfconf-query",
+ args=["--channel=xfwm4", "--property=/general/titleless_maximize", "--create", "--type=bool", "--set=false"])
+ # Put window buttons on right side
+ discard runCommand("/usr/bin/xfconf-query",
+ args=["--channel=xfwm4", "--property=/general/button_layout", "--create", "--type=string", "--set=|HMC"])
+ return true
+
+var
+ oldConfigDir: Option[string]
+ configDirChanged: bool
+
+# Prevent loading GTK theme from ~/.config/gtk-4.0 directory when it is a symlink
+if symlinkExists(getConfigDir() / "gtk-4.0"):
+ if existsEnv("XDG_CONFIG_HOME"):
+ oldConfigDir = some(getEnv("XDG_CONFIG_HOME"))
+ putEnv("XDG_CONFIG_HOME", "/dev/null")
+ configDirChanged = true
+
+viewable App:
+ loading: bool
+ hooks:
+ build:
+ # Reset the user's XDG_CONFIG_HOME variable back to what it was before
+ if configDirChanged == true:
+ if oldConfigDir.isNone():
+ delEnv("XDG_CONFIG_HOME")
+ else:
+ putEnv("XDG_CONFIG_HOME", get(oldConfigDir))
+
+proc enableLayout(args: tuple[layoutName: string, app: AppState]) {.thread.} =
+ let
+ layoutName = args.layoutName
+ app = args.app
+
+ defer:
+ app.loading = false
+ app.redrawFromThread()
+
+ case layoutName
+ of "Windows-Like", "MX-Like", "Gnome-Like", "TopX-Like":
+ if not disableTopBarIntegration():
+ return
+ of "Unity-Like", "MacOS-Like":
+ if not enableTopBarIntegration():
+ return
+ else:
+ return
+
+ # Load the layout profile
+ discard runCommand("/usr/bin/xfce4-panel-profiles", ["load", profilesDir / layoutName & ".tar.bz2"])
+ # Restart mate-hud
+ if runCommand("/usr/bin/killall", ["mate-hud"]) == 0:
+ discard runCommand("/usr/bin/setsid", ["-f", "/usr/lib/mate-hud/mate-hud"])
+ # Wait 5 seconds for the effects to take place on screen
+ sleep(5000)
+ sendNotification(appID, "Layout Switcher", layoutName & " layout was enabled",
+ icon = iconsDir / layoutName.toLowerAscii() & "-layout.png")
+
+var thread: Thread[(string, AppState)]
+
+method view(app: AppState): Widget =
+ result = gui:
+ Window:
+ title = "TROMjaro Layout Switcher"
+ # Shrink window to the smallest size
+ defaultSize = (0, 0)
+ iconName = "tromjaro-layout-switcher"
+ if app.loading:
+ Box(orient = OrientY, margin = 70):
+ Spinner(spinning = true)
+ Label(text = "Loading your layout, please wait...") {.expand: false.}
+ else:
+ Box(orient = OrientY, margin = 7, spacing = 5):
+ Box(orient = OrientX):
+ Label {.hAlign: AlignEnd.}:
+ text = "Please use the"
+ LinkButton {.expand: false.}:
+ text = "Panel Profiles"
+ proc clicked() =
+ discard runCommand("/usr/bin/setsid", ["-f", "/usr/bin/xfce4-panel-profiles"])
+ Label {.hAlign: AlignStart.}:
+ text = "to save your current configuration in case you did any manual changes, else you may lose them."
+ Label:
+ text = "Changing to or from any layout that has global menu will require your admin password."
+ for row in layoutsGrid:
+ Box(orient = OrientX, spacing = 5):
+ for tooltip in row:
+ let layoutName = tooltip.split(' ', 1)[0]
+ Button {.vAlign: AlignCenter, hAlign: AlignCenter.}:
+ icon = layoutName.toLowerAscii() & "-layout"
+ tooltip = tooltip
+ style = [ButtonFlat, StyleClass("layout-button")]
+ proc clicked() =
+ app.loading = true
+ createThread(thread, enableLayout, (layoutName, app))
+
+brew(appID, gui(App()), icons=[iconsDir], stylesheets=[newStylesheet(gtkCSS)])
+
+joinThread(thread)
diff --git a/sharedModule.nim b/sharedModule.nim
new file mode 100644
index 0000000..6c079ec
--- /dev/null
+++ b/sharedModule.nim
@@ -0,0 +1,10 @@
+from std/osproc import startProcess, ProcessOption, waitForExit, close
+
+proc runCommand*(command: string, args: openArray[string]): int =
+ ## This will run a command with the given args and return its exit code upon completion
+ let process = startProcess(command, args=args, options={poParentStreams})
+ result = process.waitForExit()
+ process.close()
+
+proc isGlobalMenuEnabled*(): bool =
+ return runCommand("/usr/bin/pacman", ["-Qq", "vala-panel-appmenu-registrar"]) == 0
diff --git a/toggleGlobalMenu.nim b/toggleGlobalMenu.nim
new file mode 100644
index 0000000..33cd91c
--- /dev/null
+++ b/toggleGlobalMenu.nim
@@ -0,0 +1,44 @@
+from std/os import paramCount, paramStr, extractFilename
+from std/strformat import fmt
+import sharedModule
+
+proc enableGlobalMenus(): int =
+ return runCommand("/usr/bin/env", ["SKIP_AUTOSNAP=", "/usr/bin/pacman", "-S", "--noconfirm", "vala-panel-appmenu-registrar"])
+
+proc disableGlobalMenus(): int =
+ result = runCommand("/usr/bin/env", ["SKIP_AUTOSNAP=", "/usr/bin/pacman", "-R", "--noconfirm", "vala-panel-appmenu-registrar"])
+ if result == 0:
+ discard runCommand("/usr/bin/killall", ["appmenu-registrar"])
+
+proc showHelp() =
+ let executableName = paramStr(0).extractFilename()
+ echo fmt"""
+Usage: {executableName}