from std/os import symlinkExists, getConfigDir, `/` 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, "Layout Switcher", "Failed to enable global menu!") 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, "Layout Switcher", "Failed to disable global menu!") 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"]) 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)]) if running(thread): joinThread(thread)