├── .editorconfig ├── .gitattributes ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.yml │ └── feature_request.yml └── workflows │ ├── publish.yml │ └── testing.yml ├── .gitignore ├── .husky └── pre-commit ├── .prettierignore ├── .prettierrc.json ├── LICENSE ├── Makefile ├── README.md ├── TESTS.md ├── config └── windows.json ├── extension.js ├── lib ├── css │ ├── LICENSE │ ├── README.md │ └── index.js ├── extension │ ├── extension-theme-manager.js │ ├── indicator.js │ ├── keybindings.js │ ├── tree.js │ ├── utils.js │ └── window.js ├── prefs │ ├── appearance.js │ ├── keyboard.js │ ├── prefs-theme-manager.js │ ├── settings.js │ └── widgets.js └── shared │ ├── logger.js │ ├── settings.js │ └── theme.js ├── metadata.json ├── package-lock.json ├── package.json ├── po ├── es.po ├── fr.po ├── it.po ├── nl.po └── pt_BR.po ├── prefs.js ├── resources └── icons │ └── hicolor │ └── scalable │ ├── actions │ ├── applications-science-symbolic.svg │ ├── appointment-soon-symbolic.svg │ ├── brush-symbolic.svg │ ├── bug-symbolic.svg │ ├── code-context-symbolic.svg │ ├── color-picker-symbolic.svg │ ├── color-select-symbolic.svg │ ├── colorfx-symbolic.svg │ ├── forge-logo-symbolic.svg │ ├── go-home-symbolic.svg │ ├── go-next-symbolic.svg │ ├── input-keyboard-symbolic.svg │ ├── larger-brush-symbolic.svg │ ├── preferences-desktop-apps-symbolic.svg │ ├── preferences-desktop-keyboard-symbolic.svg │ ├── preferences-desktop-wallpaper-symbolic.svg │ ├── settings-symbolic.svg │ ├── shell-overview-symbolic.svg │ ├── tab-new-symbolic.svg │ ├── tool-brush-symbolic.svg │ ├── tool-rectangle-symbolic.svg │ ├── utilities-tweak-tool-symbolic.svg │ ├── view-grid-symbolic.svg │ └── window-symbolic.svg │ └── panel │ ├── focus-windows-symbolic.svg │ ├── view-dual-symbolic.svg │ └── window-duplicate-symbolic.svg ├── schemas └── org.gnome.shell.extensions.forge.gschema.xml ├── stylesheet.css ├── templates ├── README.md └── new-module.js.txt └── tsconfig.json /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [{src,scripts}/**.{ts,json,js}] 4 | end_of_line = crlf 5 | charset = utf-8 6 | trim_trailing_whitespace = true 7 | indent_style = space 8 | indent_size = 4 -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | 3 | # Source code 4 | *.sh text eol=lf 5 | *.bash text eol=lf 6 | *.js text 7 | *.ts text 8 | 9 | # Documentation 10 | *.md text 11 | AUTHORS text 12 | CHANGELOG text 13 | CHANGES text 14 | CONTRIBUTING text 15 | COPYING text 16 | copyright text 17 | *COPYRIGHT* text 18 | INSTALL text 19 | license text 20 | LICENSE text 21 | NEWS text 22 | readme text 23 | *README* text 24 | TODO text 25 | 26 | # Configs 27 | .editorconfig text 28 | .gitattributes text 29 | .gitconfig text 30 | Makefile text 31 | makefile text 32 | 33 | # Graphics 34 | *.gif binary 35 | *.ico binary 36 | *.jpg binary 37 | *.jpeg binary 38 | *.png binary 39 | *.svg text 40 | *.webp binary 41 | 42 | # Ignore files (like .npmignore or .gitignore) 43 | *.*ignore text 44 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.yml: -------------------------------------------------------------------------------- 1 | name: Bug Report 2 | description: File a bug report. Add a description after "Bug:" 3 | title: "Bug: " 4 | labels: ["bug"] 5 | body: 6 | - type: textarea 7 | id: issue-description 8 | attributes: 9 | label: Issue/Bug Description/Video Capture/Screenshots 10 | description: Please describe the issue/bug in detail. Screenshots, screen recordings, and Extension Manager logs can be very helpful. 11 | validations: 12 | required: true 13 | - type: textarea 14 | id: steps-to-reproduce 15 | attributes: 16 | label: Steps to reproduce 17 | description: List the steps to get your problem to happen, if you know them. 18 | placeholder: "1. Do something\n2. Do something else\n3. Watch your computer go up in flames" 19 | validations: 20 | required: false 21 | - type: textarea 22 | id: distribution-and-linux-version 23 | attributes: 24 | label: Distribution and Linux version 25 | description: Run `cat /etc/os-release && uname -a` and paste the output here. 26 | placeholder: "NAME=\nPRETTY_NAME=\nID=\nBUILD_ID=\nANSI_COLOR=\nHOME_URL=\nDOCUMENTATION_URL=\nSUPPORT_URL=\nBUG_REPORT_URL=\nPRIVACY_POLICY_URL=\nLOGO=\nLinux ..." 27 | render: shell 28 | validations: 29 | required: true 30 | - type: textarea 31 | id: journal-log 32 | attributes: 33 | label: Journal Logs from the last hour 34 | description: Run `journalctl --since='1 hour ago' --follow /usr/bin/gnome-shell` and paste the output here. 35 | placeholder: "X 00 00:00:00 x gnome-shell[0000]: x" 36 | render: shell 37 | validations: 38 | required: true 39 | - type: input 40 | id: gnome-shell-version 41 | attributes: 42 | label: GNOME Shell version 43 | description: Run `gnome-shell --version` and paste the output here. 44 | placeholder: GNOME Shell 00.0 45 | validations: 46 | required: true 47 | - type: input 48 | id: forge-version 49 | attributes: 50 | label: Forge version 51 | description: Put the extensions.gnome.org version or the commit sha if compiled from source. 52 | placeholder: "00" 53 | validations: 54 | required: true 55 | - type: textarea 56 | id: other-extensions 57 | attributes: 58 | label: Other installed/enabled extensions 59 | description: Run `gnome-extensions list --enabled --details` and paste the output here. 60 | placeholder: "forge@jmmaranan.com\n Name: Forge\n Description: Tiling and window manager for GNOME\n\nPlease report bugs/issues on https://github.com/forge-ext/forge/issues\n Path: /home/x/.local/share/gnome-shell/extensions/forge@jmmaranan.com\n URL: https://github.com/forge-ext/forge\n Version: 00\n State: ENABLED" 61 | render: shell 62 | validations: 63 | required: true 64 | - type: textarea 65 | id: monitor-setup 66 | attributes: 67 | label: Monitor Setup 68 | description: Specifying the monitor/display setup helps a lot for tiling troubleshooting. 69 | placeholder: "Examples: 2 x 1080p, 4K, Primary(Horizontal), Secondary(Vertical)" 70 | validations: 71 | required: true 72 | - type: textarea 73 | id: other-notes 74 | attributes: 75 | label: Other Notes 76 | description: Anything else you want to add. 77 | validations: 78 | required: false 79 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.yml: -------------------------------------------------------------------------------- 1 | name: Feature request 2 | description: Suggest an idea for this project. Add a description after "Feat:" 3 | title: "Feat: " 4 | labels: ["enhancement"] 5 | body: 6 | - type: textarea 7 | id: issue-description 8 | attributes: 9 | label: Description 10 | description: Please describe the feature you're looking for. 11 | validations: 12 | required: true 13 | - type: textarea 14 | id: alternatives 15 | attributes: 16 | label: Alternatives I've considered 17 | description: A clear and concise description of any alternative solutions or features you've considered, should you have any. 18 | validations: 19 | required: false 20 | - type: textarea 21 | id: additional-context 22 | attributes: 23 | label: Design / Screenshots / Mockups 24 | description: Any other context or screenshots about the feature request. Design concepts are appreciated. 25 | validations: 26 | required: false 27 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | # This workflow builds the gnome extension for release on extensions.gnome.org 2 | 3 | # For more information see: 4 | #@ https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-nodejs 5 | 6 | name: release 7 | permissions: 8 | contents: read 9 | pull-requests: write 10 | on: 11 | release: 12 | types: [published] 13 | workflow_dispatch: 14 | inputs: 15 | ref: 16 | description: "Override git ref" 17 | type: string 18 | default: "" 19 | required: false 20 | 21 | jobs: 22 | publishing: 23 | strategy: 24 | matrix: 25 | node_version: ["latest"] 26 | runs-on: ubuntu-latest 27 | steps: 28 | - name: Checkout source code 29 | uses: actions/checkout@v4 30 | with: 31 | ref: ${{ inputs.ref }} 32 | 33 | - name: Install linux tools 34 | run: | 35 | sudo apt-get update 36 | sudo apt-get install -y --no-install-recommends gettext make zip curl 37 | 38 | - name: Set up Node.js ${{ matrix.node_version }} 39 | uses: actions/setup-node@v4 40 | with: 41 | node-version: ${{ matrix.node_version }} 42 | 43 | - name: Install Node.js project dependencies 44 | run: npm ci --verbose 45 | 46 | # - name: Build Node.js project 47 | # run: npm run build --if-present --verbose 48 | 49 | # - name: Test Node.js project 50 | # run: npm test --verbose 51 | 52 | - name: Test Packaging 53 | run: make dist 54 | 55 | - name: Archive release artifacts 56 | uses: actions/upload-artifact@v4 57 | with: 58 | name: forge-gnome-extension 59 | path: forge@jmmaranan.com.zip 60 | 61 | # - name: Upload extension to Goe 62 | # shell: bash 63 | # env: 64 | # USERNAME: ${{ secrets.GOE_USERNAME }} 65 | # PASSWORD: ${{ secrets.GOE_PASSWORD }} 66 | # run: | 67 | # printf '::group::Getting CSRF Midleware Token for accounts/login form\n'; 68 | # if test -z "${USERNAME:-}"; then printf 'Missing USERNAME from environment variable.\n::endgroup::\n'; exit 1; fi 69 | # if test -z "${PASSWORD:-}"; then printf 'Missing PASSWORD from environment variable\n::endgroup::\n'; exit 1; fi 70 | # curl 'https://extensions.gnome.org/accounts/login/' \ 71 | # --junk-session-cookies \ 72 | # -b 00-cookies-jar.txt -c 00-cookies-jar.txt \ 73 | # --location --fail-with-body \ 74 | # --no-progress-meter --verbose \ 75 | # -o 01-get-accounts-login.html; 76 | 77 | # export CSRFMIDDLEWARETOKEN="$(grep --color=never 'csrfmiddlewaretoken' < 01-get-accounts-login.html | grep --only-matching --perl-regexp '(?<=value=.)([^\"]+)' | head -n 1)"; 78 | # if test -z "${CSRFMIDDLEWARETOKEN:-}"; then printf 'Missing CSRFMIDDLEWARETOKEN from accounts/login form.\n::endgroup::\n'; exit 1; fi 79 | # printf '::endgroup::\n'; 80 | 81 | # printf '::group::Authenticating in extensions.gnome.org\n'; 82 | # if ! curl 'https://extensions.gnome.org/accounts/login/' \ 83 | # -H 'origin: https://extensions.gnome.org' \ 84 | # -H 'referer: https://extensions.gnome.org/accounts/login/' \ 85 | # -H 'content-type: application/x-www-form-urlencoded' \ 86 | # --data-urlencode 'next=' \ 87 | # --data-urlencode "csrfmiddlewaretoken=${CSRFMIDDLEWARETOKEN}" \ 88 | # --data-urlencode "username=${USERNAME}" \ 89 | # --data-urlencode "password=${PASSWORD}" \ 90 | # -b 00-cookies-jar.txt -c 00-cookies-jar.txt \ 91 | # --location --fail-with-body \ 92 | # --no-progress-meter --verbose \ 93 | # -o 02-post-accounts-login.html; 94 | # then 95 | # printf 'Login failed with error: %s\n::endgroup::\n' "$?"; 96 | # exit 2; 97 | # fi 98 | # printf '::endgroup::\n'; 99 | 100 | # printf '::group::Getting CSRF Midleware Token for upload form\n'; 101 | # curl 'https://extensions.gnome.org/upload/' \ 102 | # -b 00-cookies-jar.txt -c 00-cookies-jar.txt \ 103 | # --location --fail-with-body \ 104 | # --no-progress-meter --verbose \ 105 | # -o 03-get-upload.html; 106 | # export CSRFMIDDLEWARETOKEN="$(grep --color=never 'csrfmiddlewaretoken' < 03-get-upload.html | grep --only-matching --perl-regexp '(?<=value=.)([^\"]+)' | head -n 1)"; 107 | # if test -z "${CSRFMIDDLEWARETOKEN:-}"; then printf 'Missing CSRFMIDDLEWARETOKEN from upload form.\n::endgroup::\n'; exit 3; fi 108 | # printf '::endgroup::\n'; 109 | 110 | # printf '::group::Uploading extension zip\n'; 111 | # curl 'https://extensions.gnome.org/upload/' \ 112 | # -H 'origin: https://extensions.gnome.org' \ 113 | # -H 'referer: https://extensions.gnome.org/upload/' \ 114 | # -F "csrfmiddlewaretoken=${CSRFMIDDLEWARETOKEN}" \ 115 | # -F 'tos_compliant=on' \ 116 | # -F 'shell_license_compliant=on' \ 117 | # -F 'source=@forge@jmmaranan.com.zip' \ 118 | # -b 00-cookies-jar.txt -c 00-cookies-jar.txt \ 119 | # --location --fail-with-body \ 120 | # --no-progress-meter --verbose \ 121 | # -o 04-post-upload.html; 122 | # printf '::endgroup::\n'; 123 | -------------------------------------------------------------------------------- /.github/workflows/testing.yml: -------------------------------------------------------------------------------- 1 | # This workflow will install Node/NPM dependencies, run tests and lint source code 2 | 3 | # For more information see: 4 | #@ https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-nodejs 5 | 6 | name: testing 7 | permissions: 8 | contents: read 9 | pull-requests: write 10 | on: 11 | push: 12 | branches: ["main"] 13 | paths-ignore: 14 | - .git* 15 | - .husky/** 16 | - "*.md" 17 | pull_request: 18 | branches: ["main"] 19 | types: [opened, reopened, synchronize, ready_for_review] 20 | paths-ignore: 21 | - .git* 22 | - .husky/** 23 | - "*.md" 24 | # workflow_dispatch: 25 | # inputs: 26 | # ref: 27 | # description: 'Override git ref' 28 | # type: string 29 | # default: '' 30 | # required: false 31 | 32 | jobs: 33 | linting: 34 | strategy: 35 | fail-fast: true 36 | matrix: 37 | os: ["ubuntu-latest"] 38 | node_version: ["23.x", "latest"] 39 | 40 | runs-on: "${{ matrix.os }}" 41 | steps: 42 | - name: Checkout source code 43 | uses: actions/checkout@v4 44 | with: 45 | ref: ${{ inputs.ref }} 46 | 47 | - name: Install linux tools 48 | run: | 49 | sudo apt-get update 50 | sudo apt-get install -y --no-install-recommends gettext make zip 51 | 52 | - name: Set up Node.js ${{ matrix.node_version }} 53 | uses: actions/setup-node@v4 54 | with: 55 | node-version: ${{ matrix.node_version }} 56 | 57 | - name: Install Node.js project dependencies 58 | run: npm ci --verbose 59 | 60 | - name: Build Node.js project 61 | run: npm run build --if-present --verbose 62 | 63 | - name: Test Node.js project 64 | run: npm test --verbose 65 | 66 | - name: Test Packaging 67 | run: make dist 68 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | dist 2 | *~ 3 | temp 4 | gschemas.compiled 5 | *.zip 6 | *.exe 7 | *.mo 8 | po/forge.pot 9 | .vscode 10 | node_modules 11 | lib/prefs/metadata.js 12 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | . "$(dirname -- "$0")/_/husky.sh" 3 | 4 | npx lint-staged 5 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | css/README.md 2 | templates 3 | *.zip 4 | LICENSE 5 | Makefile 6 | *.md 7 | 8 | dist 9 | *~ 10 | temp 11 | gschemas.compiled 12 | *.zip 13 | *.exe 14 | *.mo 15 | po 16 | .vscode 17 | node_modules 18 | .github 19 | -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 100, 3 | "tabWidth": 2 4 | } 5 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | UUID = "forge@jmmaranan.com" 2 | INSTALL_PATH = $(HOME)/.local/share/gnome-shell/extensions/$(UUID) 3 | MSGSRC = $(wildcard po/*.po) 4 | 5 | .PHONY: all clean install schemas uninstall enable disable log debug patchcss 6 | 7 | all: build install enable restart 8 | 9 | # When developing locally 10 | test-x: disable uninstall build debug install enable restart log 11 | 12 | test-wayland: clean build debug install test-shell log 13 | 14 | dev: build debug install 15 | 16 | prod: build install enable restart log 17 | 18 | schemas: schemas/gschemas.compiled 19 | touch $@ 20 | 21 | schemas/gschemas.compiled: schemas/*.gschema.xml 22 | glib-compile-schemas schemas 23 | 24 | patchcss: 25 | # TODO: add the script to update css tag when delivering theme.js 26 | 27 | metadata: 28 | echo "export const developers = Object.entries([" > lib/prefs/metadata.js 29 | git shortlog -sne || echo "" >> lib/prefs/metadata.js 30 | awk -i inplace '!/dependabot|noreply/' lib/prefs/metadata.js 31 | sed -i 's/^[[:space:]]*[0-9]*[[:space:]]*\(.*\) <\(.*\)>/ {name:"\1", email:"\2"},/g' lib/prefs/metadata.js 32 | echo "].reduce((acc, x) => ({ ...acc, [x.email]: acc[x.email] ?? x.name }), {})).map(([email, name]) => name + ' <' + email + '>')" >> lib/prefs/metadata.js 33 | 34 | build: clean metadata.json schemas compilemsgs metadata 35 | rm -rf temp 36 | mkdir -p temp 37 | cp metadata.json temp 38 | cp -r resources temp 39 | cp -r schemas temp 40 | cp -r config temp 41 | cp -r lib temp 42 | cp *.js temp 43 | cp *.css temp 44 | cp LICENSE temp 45 | mkdir -p temp/locale 46 | for msg in $(MSGSRC:.po=.mo); do \ 47 | msgf=temp/locale/`basename $$msg .mo`; \ 48 | mkdir -p $$msgf; \ 49 | mkdir -p $$msgf/LC_MESSAGES; \ 50 | cp $$msg $$msgf/LC_MESSAGES/forge.mo; \ 51 | done; 52 | 53 | ./po/%.mo: ./po/%.po 54 | msgfmt -c $< -o $@ 55 | 56 | debug: 57 | sed -i 's/export const production = true/export const production = false/' temp/lib/shared/settings.js 58 | #sed -i 's|1.*-alpha|4999|' temp/metadata.json 59 | 60 | potfile: ./po/forge.pot 61 | 62 | ./po/forge.pot: metadata ./prefs.js ./extension.js ./lib/**/*.js 63 | mkdir -p po 64 | xgettext --from-code=UTF-8 --output=po/forge.pot --package-name "Forge" ./prefs.js ./extension.js ./lib/**/*.js 65 | 66 | compilemsgs: potfile $(MSGSRC:.po=.mo) 67 | for msg in $(MSGSRC); do \ 68 | msgmerge -U $$msg ./po/forge.pot; \ 69 | done; 70 | 71 | clean: 72 | rm -f lib/prefs/metadata.js 73 | rm "$(UUID).zip" || echo "Nothing to delete" 74 | rm -rf temp schemas/gschemas.compiled 75 | 76 | enable: 77 | gnome-extensions enable "$(UUID)" 78 | 79 | disable: 80 | gnome-extensions disable "$(UUID)" 81 | 82 | install: 83 | mkdir -p $(INSTALL_PATH) 84 | cp -r temp/* $(INSTALL_PATH) 85 | 86 | uninstall: 87 | rm -rf $(INSTALL_PATH) 88 | rm -rf .config/forge 89 | 90 | # When releasing 91 | dist: build 92 | cd temp && \ 93 | zip -qr "../${UUID}.zip" . 94 | 95 | restart: 96 | if bash -c 'xprop -root &> /dev/null'; then \ 97 | killall -HUP gnome-shell; \ 98 | else \ 99 | gnome-session-quit --logout; \ 100 | fi 101 | 102 | log: 103 | journalctl -o cat -n 0 -f "$$(which gnome-shell)" | grep -v -E 'warning|g_variant' 104 | 105 | journal: 106 | journalctl -b 0 -r --since "1 hour ago" 107 | 108 | test-shell: 109 | env GNOME_SHELL_SLOWDOWN_FACTOR=2 \ 110 | MUTTER_DEBUG_DUMMY_MODE_SPECS=1500x1000 \ 111 | MUTTER_DEBUG_DUMMY_MONITOR_SCALES=1 \ 112 | dbus-run-session -- gnome-shell --nested --wayland --wayland-display=wayland-forge 113 | 114 | # Usage: 115 | # make test-shell-open & 116 | # make test-shell-open CMD=gnome-text-editor 117 | # make test-shell-open CMD=gnome-terminal ARGS='--app-id app.x' 118 | # make test-shell-open CMD=firefox ARGS='--safe-mode' ENVVARS='MOZ_DBUS_REMOTE=1 MOZ_ENABLE_WAYLAND=1' 119 | # 120 | test-shell-open: CMD=nautilus 121 | test-shell-open: 122 | GDK_BACKEND=wayland WAYLAND_DISPLAY=wayland-forge $(ENVVARS) $(CMD) $(ARGS)& 123 | 124 | format: 125 | npm run format 126 | 127 | # npx prettier --list-different "./**/*.{js,jsx,ts,tsx,json}" 128 | lint: 129 | npm test 130 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Forge needs a NEW MAINTAINER 2 | 3 | Forge is a GNOME Shell extension that provides tiling/window management **_AND_** is looking for a new owner or maintainer: 4 | - https://github.com/orgs/forge-ext/discussions/276 5 | - https://github.com/forge-ext/forge/issues/336 6 | 7 | ## Features 8 | 9 | - Works on GNOME 3.36+ (feature-freeze) and 40+. X11 and Wayland 10 | - Tree-based tiling with vertical and horizontal split containers similar to i3-wm and sway-wm 11 | - Vim-like keybindings for navigation/swapping windows/moving windows in the containers 12 | - Drag and drop tiling 13 | - Support for floating windows, smart gaps and focus hint 14 | - Customizable shortcuts in extension preferences 15 | - Some support for multi-display 16 | - Tiling support per workspace 17 | - Update hint color scheme from preferences 18 | - Stacked tiling layout 19 | - Swap current window with the last active window 20 | - Auto Split or Quarter Tiling 21 | - Show/hide tab decoration via keybinding https://github.com/forge-ext/forge/issues/180 22 | - Window resize using keyboard shortcuts 23 | 24 | ## Known Issues / Limitations 25 | 26 | - Does not support dynamic workspaces 27 | - Does not support vertical monitor setup 28 | 29 | ## Installation 30 | 31 | - Build it yourself via `make install` or `make dev`. 32 | - Download from [GNOME extensions website](https://extensions.gnome.org/extension/4481/forge/). 33 | - [AUR Package](https://aur.archlinux.org/packages/gnome-shell-extension-forge) - thanks to [@Radeox](https://github.com/Radeox) 34 | - [Fedora Package](https://packages.fedoraproject.org/pkgs/gnome-shell-extension-forge/gnome-shell-extension-forge/) - thanks to [@carlwgeorge](https://github.com/carlwgeorge) 35 | 36 | ![image](https://user-images.githubusercontent.com/348125/146386593-8f53ea8b-2cf3-4d44-a613-bbcaf89f9d4a.png) 37 | 38 | ## Forge Keybinding Defaults 39 | 40 | See the acceptable key combinations on the [wiki](https://github.com/forge-ext/forge/wiki/Keyboard-Shortcuts) 41 | 42 | | Action | Shortcut | 43 | | --- | --- | 44 | | Increase active window size left | ` + + y` | 45 | | Decrease active window size left | ` + + + o` | 46 | | Increase active window size bottom | ` + + u` | 47 | | Decrease active window size bottom | ` + + + i` | 48 | | Increase active window size top | ` + + i` | 49 | | Decrease active window size top | ` + + + u` | 50 | | Increase active window size right | ` + + o` | 51 | | Decrease active window size right | ` + + + y` | 52 | | Open preferences | ` + period` | 53 | | Toggle tiling mode |` + w` | 54 | | Focus left | ` + h` | 55 | | Focus right | ` + l` | 56 | | Focus up | ` + k` | 57 | | Focus down | ` + j` | 58 | | Swap current window with last active | ` + Return` | 59 | | Swap active window left | ` + + h` | 60 | | Swap active window right | ` + + l` | 61 | | Swap active window up | ` + + k` | 62 | | Swap active window down | ` + + j` | 63 | | Move active window left | ` + + h` | 64 | | Move active window right | ` + + l` | 65 | | Move active window up | ` + + k` | 66 | | Move active window down | ` + + j` | 67 | | Split container horizontally | ` + z` | 68 | | Split container vertically | ` + v` | 69 | | Toggle split container | ` + g` | 70 | | Gap increase | ` + + Plus` | 71 | | Gap decrease | ` + + Minus` | 72 | | Toggle focus hint | ` + x` | 73 | | Toggle active workspace tiling | ` + + w` | 74 | | Toggle stacked layout | ` + + s` | 75 | | Toggle tabbed layout | ` + + t` | 76 | | Show/hide tab decoration | ` + + y` | 77 | | Activate tile drag-drop | `Start dragging - Mod key configuration in prefs` | 78 | | Snap active window left two thirds | ` + + e` | 79 | | Snap active window right two thirds | ` + + t` | 80 | | Snap active window left third | ` + + d` | 81 | | Snap active window right third | ` + + g` | 82 | | Persist toggle floating for active window | ` + c` | 83 | | Persist toggle floating for active window and its window class | ` + c` | 84 | 85 | For any shortcut conflicts, the user has to manually configure those for now from the 86 | `GNOME Control Center > Keyboard > Customize Shortcuts`. https://github.com/forge-ext/forge/issues/37 87 | 88 | ## Forge Override Paths 89 | 90 | - Window Overrides: `$HOME/.config/forge/config/windows.json` 91 | - Stylesheet Overrides: `$HOME/.config/forge/stylesheet/forge/stylesheet.css` 92 | 93 | ## GNOME Defaults 94 | 95 | GNOME Shell has built in support for workspace management and seems to work well - so Forge will not touch those. 96 | 97 | User is encouraged to bind the following: 98 | - Switching/moving windows to different workspaces 99 | - Switching to numbered, previous or next workspace 100 | 101 | ## Development 102 | 103 | - The `main` branch contains gnome-4x code. 104 | - The `legacy` and `gnome-3-36` are the same and is now the source for gnome-3x. 105 | 106 | ## Local Development Setup 107 | 108 | - Install NodeJS 16+ 109 | - Install `gettext` 110 | - Run `npm install` 111 | - Commands: 112 | 113 | ```bash 114 | # Compile and override the gnome-shell update repo 115 | make dev 116 | 117 | # Or run below, and restart the shell manually 118 | make build && make debug && make install 119 | 120 | # X11 - build from source and restarts gnome-shell 121 | make test-x 122 | 123 | # Wayland - build from source and starts a wayland instance (no restart) 124 | make test-wayland 125 | 126 | # Formatting, when you do npm install, 127 | # husky gets installed should force prettier formatting during commit 128 | 129 | npm run format 130 | ``` 131 | 132 | ## Contributing 133 | 134 | - Please be nice, friendly and welcoming on discussions/tickets. 135 | - See existing [Issues](https://github.com/forge-ext/forge/issues), or create a new Issue with the "Bug report" format if it doesn't exist. 136 | 137 | ## Credits 138 | 139 | Thank you to: 140 | - Forge extension contributors 141 | - Michael Stapelberg/contributors for i3 142 | - System76/contributors for pop-shell 143 | - ReworkCSS/contributors for css-parse/css-stringify -------------------------------------------------------------------------------- /TESTS.md: -------------------------------------------------------------------------------- 1 | # Forge Tests 2 | 3 | ## Configurations and Preferences 4 | 5 | ### Preferences #39 6 | 7 | - [ ] - Should open prefs.js via ` + Period` in GNOME 3.3x+. 8 | 9 | On opening Production Mode Preferences window: 10 | 11 | - [ ] - Should close prefs.js via `Esc` in GNOME 3.3x+. 12 | - [ ] - Should show `Home, Appearance, Workspace, Keyboard, Experimental,` on the parent-level settings list. 13 | - [ ] - Should show `Appearance` right-arrow indicator, since Appearance have child-level settings options. 14 | - [ ] - Should show `Keyboard` right-arrow indicator, since Keyboard have child-level settings options. 15 | 16 | On opening Development Mode Preferences window, _includes_ all of Production Mode checks plus below: 17 | 18 | - [ ] - Should show `Development, About,` on the parent-level settings list. 19 | 20 | On navigating `Home` parent item, 21 | 22 | - [ ] - Should show a _work in progress_ panel showing Forge version information depending if it was built using Production or Development mode. 23 | 24 | On navigating `Appearance` parent item, 25 | 26 | - [ ] - Should transition to sub-list which includes: `Windows,`. The initial sub-item's panel box should show immediately. 27 | - [ ] - Should show the `back button` on the header bar of the Preferences Window. 28 | - [ ] - Should update the header bar `title` of the Preferences Window and appends `- Windows`. 29 | 30 | ### Production and Dev Modes 31 | 32 | ## Window Effects 33 | 34 | When changing Preferences on Appearance > Colors: 35 | 36 | - [ ] - Tiled Focus Hint updates border size and color 37 | - [ ] - Tiled Focus Hint color updates also updates preview tiled hint 38 | - [ ] - Tiled Focus Hint color updates also updates overview and workspace thumbnail hints 39 | - [ ] - Tiled Focus Hint updates can be reset 40 | - [ ] - Floated Focus Hint updates border size and color 41 | - [ ] - Stacked Focus Hint updates border size and color 42 | - [ ] - Stacked Focus Hint color updates also updates preview stacked hint 43 | - [ ] - Stacked Focus Hint updates can be reset 44 | - [ ] - Tabbed Focus Hint updates border size and color 45 | - [ ] - Tabbed Focus Hint color updates also updates preview tabbed hint 46 | - [ ] - Tabbed Focus Hint updates can be reset 47 | 48 | ## Tiling Mode 49 | 50 | When dragging a window: 51 | 52 | - [ ] - Should show a preview hint where the window would be tiled. If Tile Modifier is set, Super or Ctrl or Alt would show preview otherwise shows preview automatically: 53 | - [ ] - For split layout, should show preview hint left/right on horizontal, top/bottom on vertical following the mouse pointer. 54 | - [ ] - For tabbed layout, should show preview hint same size as the current front window. 55 | - [ ] - For stacked layout, should show preview hint same size as the current front window. 56 | - [ ] - There should be the following preview hint regions: LEFT, TOP, RIGHT, BOTTOM and CENTER 57 | - [ ] - On dropping, should tile the window on the preview hint position shown before dropping. 58 | - [ ] - On dropping to a different monitor, should tile based on the preview hint position shown unless empty monitor. 59 | - [ ] - Empty monitors will not show a preview hint. 60 | 61 | ## Floating Mode 62 | 63 | ## Layout Mode 64 | 65 | ## Focus 66 | 67 | While alternating between windows, the mouse cursor position should follow the focus: 68 | 69 | - [ ] - when moving the focus to another window with keyboard or mouse. 70 | - [ ] - when swapping two windows positions. 71 | - [ ] - when alternating to a new window using `Alt+Tab` or `Super+Tab`. 72 | - [ ] - when exiting on Overview mode. 73 | - [ ] - at the same position it was previouly on the window. 74 | -------------------------------------------------------------------------------- /config/windows.json: -------------------------------------------------------------------------------- 1 | { 2 | "overrides": [ 3 | { "wmClass": "org.gnome.Shell.Extensions", "wmTitle": "Forge Settings", "mode": "float" }, 4 | { "wmClass": "jetbrains-toolbox", "mode": "float" }, 5 | { "wmClass": "jetbrains-goland", "wmTitle": "splash", "mode": "float" }, 6 | { "wmClass": "jetbrains-webstorm", "wmTitle": "splash", "mode": "float" }, 7 | { "wmClass": "jetbrains-phpstorm", "wmTitle": "splash", "mode": "float" }, 8 | { "wmClass": "jetbrains-datagrip", "wmTitle": "splash", "mode": "float" }, 9 | { "wmClass": "jetbrains-rubymine", "wmTitle": "splash", "mode": "float" }, 10 | { "wmClass": "jetbrains-idea", "wmTitle": "splash", "mode": "float" }, 11 | { "wmClass": "Com.github.amezin.ddterm", "mode": "float" }, 12 | { "wmClass": "Com.github.donadigo.eddy", "mode": "float" }, 13 | { "wmClass": "Conky", "mode": "float" }, 14 | { "wmClass": "Gnome-initial-setup", "mode": "float" }, 15 | { "wmClass": "org.gnome.Calculator", "mode": "float" }, 16 | { "wmClass": "gnome-terminal-server", "wmTitle": "Preferences – General", "mode": "float" }, 17 | { "wmClass": "gnome-terminal-preferences", "mode": "float" }, 18 | { "wmClass": "Guake", "mode": "float" }, 19 | { "wmClass": "zoom", "mode": "float" }, 20 | { "wmClass": "firefox", "wmTitle": "About Mozilla Firefox", "mode": "float" }, 21 | { "wmClass": "firefox", "wmTitle": "!Mozilla Firefox", "mode": "float" }, 22 | { 23 | "wmClass": "org.mozilla.firefox.desktop", 24 | "wmTitle": "About Mozilla Firefox", 25 | "mode": "float" 26 | }, 27 | { "wmClass": "org.mozilla.firefox.desktop", "wmTitle": "!Mozilla Firefox", "mode": "float" }, 28 | { "wmClass": "thunderbird", "wmTitle": "About Mozilla Thunderbird", "mode": "float" }, 29 | { "wmClass": "thunderbird", "wmTitle": "!Mozilla Thunderbird", "mode": "float" }, 30 | { 31 | "wmClass": "org.mozilla.Thunderbird.desktop", 32 | "wmTitle": "About Mozilla Thunderbird", 33 | "mode": "float" 34 | }, 35 | { 36 | "wmClass": "org.mozilla.Thunderbird.desktop", 37 | "wmTitle": "!Mozilla Thunderbird", 38 | "mode": "float" 39 | }, 40 | { 41 | "wmClass": "evolution-alarm-notify", 42 | "mode": "float" 43 | }, 44 | { 45 | "wmClass": "variety", 46 | "mode": "float" 47 | }, 48 | { 49 | "wmClass": "update-manager", 50 | "mode": "float" 51 | } 52 | ] 53 | } 54 | -------------------------------------------------------------------------------- /extension.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the Forge GNOME extension 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | * 17 | */ 18 | 19 | // Gnome imports 20 | import * as Main from "resource:///org/gnome/shell/ui/main.js"; 21 | import { Extension, gettext as _ } from "resource:///org/gnome/shell/extensions/extension.js"; 22 | 23 | // Shared state 24 | import { Logger } from "./lib/shared/logger.js"; 25 | import { ConfigManager } from "./lib/shared/settings.js"; 26 | 27 | // Application imports 28 | import { Keybindings } from "./lib/extension/keybindings.js"; 29 | import { WindowManager } from "./lib/extension/window.js"; 30 | import { FeatureIndicator, FeatureMenuToggle } from "./lib/extension/indicator.js"; 31 | import { ExtensionThemeManager } from "./lib/extension/extension-theme-manager.js"; 32 | 33 | export default class ForgeExtension extends Extension { 34 | enable() { 35 | this.settings = this.getSettings(); 36 | this.kbdSettings = this.getSettings("org.gnome.shell.extensions.forge.keybindings"); 37 | Logger.init(this.settings); 38 | Logger.info("enable"); 39 | 40 | this.configMgr = new ConfigManager(this); 41 | this.theme = new ExtensionThemeManager(this); 42 | this.extWm = new WindowManager(this); 43 | this.keybindings = new Keybindings(this); 44 | 45 | this._onSessionModeChanged(Main.sessionMode); 46 | this._sessionId = Main.sessionMode.connect("updated", this._onSessionModeChanged.bind(this)); 47 | 48 | this.theme.patchCss(); 49 | this.theme.reloadStylesheet(); 50 | this.extWm.enable(); 51 | Logger.info(`enable: finalized vars`); 52 | } 53 | 54 | disable() { 55 | Logger.info("disable"); 56 | 57 | // See session mode unlock-dialog explanation on _onSessionModeChanged() 58 | if (this._sessionId) { 59 | Main.sessionMode.disconnect(this._sessionId); 60 | this._sessionId = null; 61 | } 62 | 63 | this._removeIndicator(); 64 | this.extWm?.disable(); 65 | this.keybindings?.disable(); 66 | this.keybindings = null; 67 | this.extWm = null; 68 | this.themeWm = null; 69 | this.configMgr = null; 70 | this.settings = null; 71 | this.kbdSettings = null; 72 | } 73 | 74 | _onSessionModeChanged(session) { 75 | if (session.currentMode === "user" || session.parentMode === "user") { 76 | Logger.info("user on session change"); 77 | this._addIndicator(); 78 | this.keybindings?.enable(); 79 | } else if (session.currentMode === "unlock-dialog") { 80 | // To the reviewer and maintainer: this extension needs to persist the window data structure in memory so it has to keep running on lock screen. 81 | // This is previous feature but was removed during GNOME 45 update due to the session-mode rule review. 82 | // The argument is that users will keep re-arranging windows when it times out or locks up. 83 | // Intent to serialize/deserialize to disk but that will take a longer time or probably a longer argument during review. 84 | // To keep following, added to only disable keybindings() and re-enable them during user session. 85 | // https://gjs.guide/extensions/review-guidelines/review-guidelines.html#session-modes 86 | Logger.info("lock-screen on session change"); 87 | this.keybindings?.disable(); 88 | this._removeIndicator(); 89 | } 90 | } 91 | 92 | _addIndicator() { 93 | this.indicator ??= new FeatureIndicator(this); 94 | this.indicator.quickSettingsItems.push(new FeatureMenuToggle(this)); 95 | Main.panel.statusArea.quickSettings.addExternalIndicator(this.indicator); 96 | } 97 | 98 | _removeIndicator() { 99 | this.indicator?.quickSettingsItems.forEach((item) => item.destroy()); 100 | this.indicator?.destroy(); 101 | this.indicator = null; 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /lib/css/LICENSE: -------------------------------------------------------------------------------- 1 | (The MIT License) 2 | 3 | Copyright (c) 2012 TJ Holowaychuk 4 | 5 | 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: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | 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. 10 | -------------------------------------------------------------------------------- /lib/css/README.md: -------------------------------------------------------------------------------- 1 | # CSS module in Forge 2 | 3 | API to work with CSS code and files and update the extension's stylesheet.css 4 | 5 | ## CSS Parser from ReworkCSS 6 | Credits: https://github.com/reworkcss/css 7 | Modified to work in GNOME-Shell by Forge 8 | 9 | ### Usage 10 | 11 | ```js 12 | import { 13 | parse, 14 | stringify, 15 | write, 16 | load, 17 | } from './css/index.js'; 18 | 19 | // Raw APIs from ReworkCSS 20 | let obj = parse('body { font-size: 12px; }'); 21 | let code = stringify(obj); 22 | 23 | // Convenience 24 | write(code, "/path/to/stylesheet.css"); 25 | let ast = load("/path/to/stylesheet.css"); 26 | 27 | // ... Do something with AST ... 28 | 29 | ``` 30 | 31 | ### Example 32 | 33 | CSS: 34 | 35 | ```css 36 | body { 37 | background: #eee; 38 | color: #888; 39 | } 40 | ``` 41 | 42 | Parse tree: 43 | 44 | ```json 45 | { 46 | "type": "stylesheet", 47 | "stylesheet": { 48 | "rules": [ 49 | { 50 | "type": "rule", 51 | "selectors": [ 52 | "body" 53 | ], 54 | "declarations": [ 55 | { 56 | "type": "declaration", 57 | "property": "background", 58 | "value": "#eee", 59 | "position": { 60 | "start": { 61 | "line": 2, 62 | "column": 3 63 | }, 64 | "end": { 65 | "line": 2, 66 | "column": 19 67 | } 68 | } 69 | }, 70 | { 71 | "type": "declaration", 72 | "property": "color", 73 | "value": "#888", 74 | "position": { 75 | "start": { 76 | "line": 3, 77 | "column": 3 78 | }, 79 | "end": { 80 | "line": 3, 81 | "column": 14 82 | } 83 | } 84 | } 85 | ], 86 | "position": { 87 | "start": { 88 | "line": 1, 89 | "column": 1 90 | }, 91 | "end": { 92 | "line": 4, 93 | "column": 2 94 | } 95 | } 96 | } 97 | ] 98 | } 99 | } 100 | ``` 101 | -------------------------------------------------------------------------------- /lib/extension/extension-theme-manager.js: -------------------------------------------------------------------------------- 1 | import GObject from "gi://GObject"; 2 | 3 | import St from "gi://St"; 4 | 5 | import { ThemeManagerBase } from "../shared/theme.js"; 6 | import { Logger } from "../shared/logger.js"; 7 | import { production } from "../shared/settings.js"; 8 | 9 | export class ExtensionThemeManager extends ThemeManagerBase { 10 | static { 11 | GObject.registerClass(this); 12 | } 13 | 14 | /** 15 | * @param {import("../../extension.js").default} extension 16 | */ 17 | constructor(extension) { 18 | super(extension); 19 | this.metadata = extension.metadata; 20 | } 21 | 22 | reloadStylesheet() { 23 | const uuid = this.metadata.uuid; 24 | const stylesheetFile = this.configMgr.stylesheetFile; 25 | const defaultStylesheetFile = this.configMgr.defaultStylesheetFile; 26 | let theme = St.ThemeContext.get_for_stage(global.stage).get_theme(); 27 | 28 | try { 29 | theme.unload_stylesheet(defaultStylesheetFile); 30 | theme.unload_stylesheet(stylesheetFile); 31 | if (production) { 32 | theme.load_stylesheet(stylesheetFile); 33 | this.stylesheet = stylesheetFile; 34 | } else { 35 | theme.load_stylesheet(defaultStylesheetFile); 36 | this.stylesheet = defaultStylesheetFile; 37 | } 38 | } catch (e) { 39 | Logger.error(`${uuid} - ${e}`); 40 | return; 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /lib/extension/indicator.js: -------------------------------------------------------------------------------- 1 | import GObject from "gi://GObject"; 2 | import Gio from "gi://Gio"; 3 | 4 | import * as Main from "resource:///org/gnome/shell/ui/main.js"; 5 | import { gettext as _ } from "resource:///org/gnome/shell/extensions/extension.js"; 6 | import { QuickMenuToggle, SystemIndicator } from "resource:///org/gnome/shell/ui/quickSettings.js"; 7 | import { 8 | PopupSwitchMenuItem, 9 | PopupSeparatorMenuItem, 10 | } from "resource:///org/gnome/shell/ui/popupMenu.js"; 11 | 12 | import * as Utils from "./utils.js"; 13 | import { Logger } from "../shared/logger.js"; 14 | 15 | const iconName = "view-grid-symbolic"; 16 | 17 | /** @typedef {import('../../extension.js').default} ForgeExtension */ 18 | 19 | class SettingsPopupSwitch extends PopupSwitchMenuItem { 20 | static { 21 | GObject.registerClass(this); 22 | } 23 | 24 | /** @type {ForgeExtension} extension */ 25 | extension; 26 | 27 | /** 28 | * @param {string} title 29 | * @param {ForgeExtension} extension 30 | * @param {string} bind 31 | */ 32 | constructor(title, extension, bind) { 33 | const active = !!extension.settings.get_boolean(bind); 34 | super(title, active); 35 | this.extension = extension; 36 | Logger.info(bind, active); 37 | this.connect("toggled", (item) => this.extension.settings.set_boolean(bind, item.state)); 38 | } 39 | } 40 | 41 | export class FeatureMenuToggle extends QuickMenuToggle { 42 | static { 43 | GObject.registerClass(this); 44 | } 45 | 46 | constructor(extension) { 47 | const title = _("Tiling"); 48 | const initSettings = Utils.isGnomeGTE(45) 49 | ? { title, iconName, toggleMode: true } 50 | : { label: title, iconName, toggleMode: true }; 51 | super(initSettings); 52 | this.extension = extension; 53 | this.extension.settings.bind( 54 | "tiling-mode-enabled", 55 | this, 56 | "checked", 57 | Gio.SettingsBindFlags.DEFAULT 58 | ); 59 | this.extension.settings.bind( 60 | "quick-settings-enabled", 61 | this, 62 | "visible", 63 | Gio.SettingsBindFlags.DEFAULT 64 | ); 65 | 66 | this.menu.setHeader(iconName, _("Forge"), _("Tiling Window Management")); 67 | 68 | this.menu.addMenuItem( 69 | (this._singleSwitch = new SettingsPopupSwitch( 70 | _("Gaps Hidden when Single"), 71 | this.extension, 72 | "window-gap-hidden-on-single" 73 | )) 74 | ); 75 | 76 | this.menu.addMenuItem( 77 | (this._focusHintSwitch = new SettingsPopupSwitch( 78 | _("Show Focus Hint Border"), 79 | this.extension, 80 | "focus-border-toggle" 81 | )) 82 | ); 83 | 84 | this.menu.addMenuItem( 85 | (this._focusMovePointer = new SettingsPopupSwitch( 86 | _("Move Pointer with the Focus"), 87 | this.extension, 88 | "move-pointer-focus-enabled" 89 | )) 90 | ); 91 | 92 | // Add an entry-point for more settings 93 | this.menu.addMenuItem(new PopupSeparatorMenuItem()); 94 | const settingsItem = this.menu.addAction(_("Settings"), () => this.extension.openPreferences()); 95 | 96 | // Ensure the settings are unavailable when the screen is locked 97 | settingsItem.visible = Main.sessionMode.allowSettings; 98 | this.menu._settingsActions[this.extension.uuid] = settingsItem; 99 | } 100 | } 101 | 102 | export class FeatureIndicator extends SystemIndicator { 103 | static { 104 | GObject.registerClass(this); 105 | } 106 | 107 | constructor(extension) { 108 | super(); 109 | 110 | this.extension = extension; 111 | 112 | // Create the icon for the indicator 113 | this._indicator = this._addIndicator(); 114 | this._indicator.icon_name = iconName; 115 | 116 | const tilingModeEnabled = this.extension.settings.get_boolean("tiling-mode-enabled"); 117 | const quickSettingsEnabled = this.extension.settings.get_boolean("quick-settings-enabled"); 118 | 119 | this._indicator.visible = tilingModeEnabled && quickSettingsEnabled; 120 | 121 | this.extension.settings.connect("changed", (_, name) => { 122 | switch (name) { 123 | case "tiling-mode-enabled": 124 | case "quick-settings-enabled": 125 | this._indicator.visible = this.extension.settings.get_boolean(name); 126 | } 127 | }); 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /lib/extension/keybindings.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the Forge extension for GNOME 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | * 17 | */ 18 | 19 | // Gnome imports 20 | import GObject from "gi://GObject"; 21 | import Meta from "gi://Meta"; 22 | import Shell from "gi://Shell"; 23 | 24 | // Gnome Shell imports 25 | import * as Main from "resource:///org/gnome/shell/ui/main.js"; 26 | 27 | // Shared state 28 | import { Logger } from "../shared/logger.js"; 29 | 30 | export class Keybindings extends GObject.Object { 31 | static { 32 | GObject.registerClass(this); 33 | } 34 | 35 | /** @type {import('./extension.js').default} */ 36 | ext; 37 | 38 | constructor(ext) { 39 | super(); 40 | Logger.debug(`created keybindings`); 41 | this._grabbers = new Map(); 42 | // this._bindSignals(); 43 | this.ext = ext; 44 | this.extWm = ext.extWm; 45 | this.kbdSettings = ext.kbdSettings; 46 | this.settings = ext.settings; 47 | this.buildBindingDefinitions(); 48 | } 49 | 50 | // @deprecated 51 | _acceleratorActivate(action) { 52 | let grabber = this._grabbers.get(action); 53 | if (grabber) { 54 | Logger.debug(`Firing accelerator ${grabber.accelerator} : ${grabber.name}`); 55 | grabber.callback(); 56 | } else { 57 | Logger.error(`No listeners [action={${action}}]`); 58 | } 59 | } 60 | 61 | // @deprecated 62 | _bindSignals() { 63 | global.display.connect("accelerator-activated", (_display, action, _deviceId, _timestamp) => { 64 | this._acceleratorActivate(action); 65 | }); 66 | } 67 | 68 | enable() { 69 | let keybindings = this._bindings; 70 | 71 | for (const key in keybindings) { 72 | Main.wm.addKeybinding( 73 | key, 74 | this.kbdSettings, 75 | Meta.KeyBindingFlags.NONE, 76 | Shell.ActionMode.NORMAL, 77 | keybindings[key] 78 | ); 79 | } 80 | 81 | Logger.debug(`keybindings:enable`); 82 | } 83 | 84 | disable() { 85 | let keybindings = this._bindings; 86 | 87 | for (const key in keybindings) { 88 | Main.wm.removeKeybinding(key); 89 | } 90 | 91 | Logger.debug(`keybindings:disable`); 92 | } 93 | 94 | // @deprecated 95 | enableListenForBindings() { 96 | windowConfig.forEach((config) => { 97 | config.shortcut.forEach((shortcut) => { 98 | this.listenFor(shortcut, () => { 99 | config.actions.forEach((action) => { 100 | this.extWm.command(action); 101 | }); 102 | }); 103 | }); 104 | }); 105 | } 106 | 107 | // @deprecated 108 | disableListenForBindings() { 109 | // The existing grabber items are from the custom config by 110 | // this extension. 111 | this._grabbers.forEach((grabber) => { 112 | global.display.ungrab_accelerator(grabber.action); 113 | Main.wm.allowKeybinding(grabber.name, Shell.ActionMode.NONE); 114 | }); 115 | 116 | this._grabbers.clear(); 117 | } 118 | 119 | /** 120 | * API for quick binding of keys to function. This is going to be useful with SpaceMode 121 | * 122 | * @param {String} accelerator - keybinding combinations 123 | * @param {Function} callback - function to call when the accelerator is invoked 124 | * 125 | * Credits: 126 | * - https://superuser.com/a/1182899 127 | * - Adapted based on current Gnome-shell API or syntax 128 | */ 129 | listenFor(accelerator, callback) { 130 | let grabFlags = Meta.KeyBindingFlags.NONE; 131 | let action = global.display.grab_accelerator(accelerator, grabFlags); 132 | 133 | if (action == Meta.KeyBindingAction.NONE) { 134 | Logger.error(`Unable to grab accelerator [binding={${accelerator}}]`); 135 | // TODO - check the gnome keybindings for conflicts and notify the user 136 | } else { 137 | let name = Meta.external_binding_name_for_action(action); 138 | 139 | Logger.debug(`Requesting WM to allow binding [name={${name}}]`); 140 | Main.wm.allowKeybinding(name, Shell.ActionMode.ALL); 141 | 142 | this._grabbers.set(action, { 143 | name: name, 144 | accelerator: accelerator, 145 | callback: callback, 146 | action: action, 147 | }); 148 | } 149 | } 150 | 151 | get modifierState() { 152 | const [_x, _y, state] = this.extWm.getPointer(); 153 | return state; 154 | } 155 | 156 | allowDragDropTile() { 157 | const tileModifier = this.kbdSettings.get_string("mod-mask-mouse-tile"); 158 | const modState = this.modifierState; 159 | // Using Clutter.ModifierType values and also testing for pointer 160 | // being grabbed (256). E.g. grabbed + pressing Super = 256 + 64 = 320 161 | // See window.js#_handleMoving() - an overlay preview is shown. 162 | // See window.js#_handleGrabOpEnd() - when the drag has been dropped 163 | switch (tileModifier) { 164 | case "Super": 165 | return modState === 64 || modState === 320; 166 | case "Alt": 167 | return modState === 8 || modState === 264; 168 | case "Ctrl": 169 | return modState === 4 || modState === 260; 170 | case "None": 171 | return true; 172 | } 173 | return false; 174 | } 175 | 176 | buildBindingDefinitions() { 177 | this._bindings = { 178 | "window-toggle-float": () => { 179 | let actions = [ 180 | { 181 | name: "FloatToggle", 182 | mode: "float", 183 | x: "center", 184 | y: "center", 185 | width: 0.65, 186 | height: 0.75, 187 | }, 188 | ]; 189 | actions.forEach((action) => { 190 | this.extWm.command(action); 191 | }); 192 | }, 193 | "window-toggle-always-float": () => { 194 | let action = { 195 | name: "FloatClassToggle", 196 | mode: "float", 197 | x: "center", 198 | y: "center", 199 | width: 0.65, 200 | height: 0.75, 201 | }; 202 | this.extWm.command(action); 203 | }, 204 | "window-focus-left": () => { 205 | let actions = [ 206 | { 207 | name: "Focus", 208 | direction: "Left", 209 | }, 210 | ]; 211 | actions.forEach((action) => { 212 | this.extWm.command(action); 213 | }); 214 | }, 215 | "window-focus-down": () => { 216 | let actions = [ 217 | { 218 | name: "Focus", 219 | direction: "Down", 220 | }, 221 | ]; 222 | actions.forEach((action) => { 223 | this.extWm.command(action); 224 | }); 225 | }, 226 | "window-focus-up": () => { 227 | let actions = [ 228 | { 229 | name: "Focus", 230 | direction: "Up", 231 | }, 232 | ]; 233 | actions.forEach((action) => { 234 | this.extWm.command(action); 235 | }); 236 | }, 237 | "window-focus-right": () => { 238 | let actions = [ 239 | { 240 | name: "Focus", 241 | direction: "Right", 242 | }, 243 | ]; 244 | actions.forEach((action) => { 245 | this.extWm.command(action); 246 | }); 247 | }, 248 | "window-swap-left": () => { 249 | let actions = [ 250 | { 251 | name: "Swap", 252 | direction: "Left", 253 | }, 254 | ]; 255 | actions.forEach((action) => { 256 | this.extWm.command(action); 257 | }); 258 | }, 259 | "window-swap-down": () => { 260 | let actions = [ 261 | { 262 | name: "Swap", 263 | direction: "Down", 264 | }, 265 | ]; 266 | actions.forEach((action) => { 267 | this.extWm.command(action); 268 | }); 269 | }, 270 | "window-swap-up": () => { 271 | let actions = [ 272 | { 273 | name: "Swap", 274 | direction: "Up", 275 | }, 276 | ]; 277 | actions.forEach((action) => { 278 | this.extWm.command(action); 279 | }); 280 | }, 281 | "window-swap-right": () => { 282 | let actions = [ 283 | { 284 | name: "Swap", 285 | direction: "Right", 286 | }, 287 | ]; 288 | actions.forEach((action) => { 289 | this.extWm.command(action); 290 | }); 291 | }, 292 | "window-move-left": () => { 293 | let actions = [ 294 | { 295 | name: "Move", 296 | direction: "Left", 297 | }, 298 | ]; 299 | actions.forEach((action) => { 300 | this.extWm.command(action); 301 | }); 302 | }, 303 | "window-move-down": () => { 304 | let actions = [ 305 | { 306 | name: "Move", 307 | direction: "Down", 308 | }, 309 | ]; 310 | actions.forEach((action) => { 311 | this.extWm.command(action); 312 | }); 313 | }, 314 | "window-move-up": () => { 315 | let actions = [ 316 | { 317 | name: "Move", 318 | direction: "Up", 319 | }, 320 | ]; 321 | actions.forEach((action) => { 322 | this.extWm.command(action); 323 | }); 324 | }, 325 | "window-move-right": () => { 326 | let actions = [ 327 | { 328 | name: "Move", 329 | direction: "Right", 330 | }, 331 | ]; 332 | actions.forEach((action) => { 333 | this.extWm.command(action); 334 | }); 335 | }, 336 | "con-split-layout-toggle": () => { 337 | let actions = [{ name: "LayoutToggle" }]; 338 | actions.forEach((action) => { 339 | this.extWm.command(action); 340 | }); 341 | }, 342 | "con-split-vertical": () => { 343 | let actions = [{ name: "Split", orientation: "vertical" }]; 344 | actions.forEach((action) => { 345 | this.extWm.command(action); 346 | }); 347 | }, 348 | "con-split-horizontal": () => { 349 | let actions = [{ name: "Split", orientation: "horizontal" }]; 350 | actions.forEach((action) => { 351 | this.extWm.command(action); 352 | }); 353 | }, 354 | "con-stacked-layout-toggle": () => { 355 | let action = { name: "LayoutStackedToggle" }; 356 | this.extWm.command(action); 357 | }, 358 | "con-tabbed-layout-toggle": () => { 359 | let action = { name: "LayoutTabbedToggle" }; 360 | this.extWm.command(action); 361 | }, 362 | "con-tabbed-showtab-decoration-toggle": () => { 363 | let action = { name: "ShowTabDecorationToggle" }; 364 | this.extWm.command(action); 365 | }, 366 | "focus-border-toggle": () => { 367 | let action = { name: "FocusBorderToggle" }; 368 | this.extWm.command(action); 369 | }, 370 | "prefs-tiling-toggle": () => { 371 | let action = { name: "TilingModeToggle" }; 372 | this.extWm.command(action); 373 | }, 374 | "window-gap-size-increase": () => { 375 | let action = { name: "GapSize", amount: 1 }; 376 | this.extWm.command(action); 377 | }, 378 | "window-gap-size-decrease": () => { 379 | let action = { name: "GapSize", amount: -1 }; 380 | this.extWm.command(action); 381 | }, 382 | "workspace-active-tile-toggle": () => { 383 | let action = { name: "WorkspaceActiveTileToggle" }; 384 | this.extWm.command(action); 385 | }, 386 | "prefs-open": () => { 387 | let action = { name: "PrefsOpen" }; 388 | this.extWm.command(action); 389 | }, 390 | "window-swap-last-active": () => { 391 | let action = { 392 | name: "WindowSwapLastActive", 393 | }; 394 | this.extWm.command(action); 395 | }, 396 | "window-snap-one-third-right": () => { 397 | let action = { 398 | name: "SnapLayoutMove", 399 | direction: "Right", 400 | amount: 1 / 3, 401 | }; 402 | this.extWm.command(action); 403 | }, 404 | "window-snap-two-third-right": () => { 405 | let action = { 406 | name: "SnapLayoutMove", 407 | direction: "Right", 408 | amount: 2 / 3, 409 | }; 410 | this.extWm.command(action); 411 | }, 412 | "window-snap-one-third-left": () => { 413 | let action = { 414 | name: "SnapLayoutMove", 415 | direction: "Left", 416 | amount: 1 / 3, 417 | }; 418 | this.extWm.command(action); 419 | }, 420 | "window-snap-two-third-left": () => { 421 | let action = { 422 | name: "SnapLayoutMove", 423 | direction: "Left", 424 | amount: 2 / 3, 425 | }; 426 | this.extWm.command(action); 427 | }, 428 | "window-snap-center": () => { 429 | let action = { 430 | name: "SnapLayoutMove", 431 | direction: "Center", 432 | }; 433 | this.extWm.command(action); 434 | }, 435 | "window-resize-top-increase": () => { 436 | let action = { 437 | name: "WindowResizeTop", 438 | amount: this.settings.get_uint("resize-amount"), 439 | }; 440 | this.extWm.command(action); 441 | }, 442 | "window-resize-top-decrease": () => { 443 | let action = { 444 | name: "WindowResizeTop", 445 | amount: -1 * this.settings.get_uint("resize-amount"), 446 | }; 447 | this.extWm.command(action); 448 | }, 449 | "window-resize-bottom-increase": () => { 450 | let action = { 451 | name: "WindowResizeBottom", 452 | amount: this.settings.get_uint("resize-amount"), 453 | }; 454 | this.extWm.command(action); 455 | }, 456 | "window-resize-bottom-decrease": () => { 457 | let action = { 458 | name: "WindowResizeBottom", 459 | amount: -1 * this.settings.get_uint("resize-amount"), 460 | }; 461 | this.extWm.command(action); 462 | }, 463 | "window-resize-left-increase": () => { 464 | let action = { 465 | name: "WindowResizeLeft", 466 | amount: this.settings.get_uint("resize-amount"), 467 | }; 468 | this.extWm.command(action); 469 | }, 470 | "window-resize-left-decrease": () => { 471 | let action = { 472 | name: "WindowResizeLeft", 473 | amount: -1 * this.settings.get_uint("resize-amount"), 474 | }; 475 | this.extWm.command(action); 476 | }, 477 | "window-resize-right-increase": () => { 478 | let action = { 479 | name: "WindowResizeRight", 480 | amount: this.settings.get_uint("resize-amount"), 481 | }; 482 | this.extWm.command(action); 483 | }, 484 | "window-resize-right-decrease": () => { 485 | let action = { 486 | name: "WindowResizeRight", 487 | amount: -1 * this.settings.get_uint("resize-amount"), 488 | }; 489 | this.extWm.command(action); 490 | }, 491 | }; 492 | } 493 | } 494 | -------------------------------------------------------------------------------- /lib/extension/utils.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the Forge extension for GNOME 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | * 17 | * Credits: 18 | * This file has some code from Dash-To-Panel extension: convenience.js 19 | * Some code was also adapted from the upstream Gnome Shell source code. 20 | */ 21 | 22 | // Gnome imports 23 | import Meta from "gi://Meta"; 24 | import St from "gi://St"; 25 | 26 | // Gnome-shell imports 27 | import { PACKAGE_VERSION } from "resource:///org/gnome/shell/misc/config.js"; 28 | 29 | // App imports 30 | import { ORIENTATION_TYPES, LAYOUT_TYPES, POSITION } from "./tree.js"; 31 | import { GRAB_TYPES } from "./window.js"; 32 | 33 | const [major] = PACKAGE_VERSION.split(".").map((s) => Number(s)); 34 | 35 | /** 36 | * 37 | * Turns an array into an immutable enum-like object 38 | * 39 | */ 40 | export function createEnum(anArray) { 41 | const enumObj = {}; 42 | for (const val of anArray) { 43 | enumObj[val] = val; 44 | } 45 | return Object.freeze(enumObj); 46 | } 47 | 48 | export function resolveX(rectRequest, metaWindow) { 49 | let metaRect = metaWindow.get_frame_rect(); 50 | let monitorRect = metaWindow.get_work_area_current_monitor(); 51 | let val = metaRect.x; 52 | let x = rectRequest.x; 53 | switch (typeof x) { 54 | case "string": //center, 55 | switch (x) { 56 | case "center": 57 | val = monitorRect.width * 0.5 - this.resolveWidth(rectRequest, metaWindow) * 0.5; 58 | break; 59 | case "left": 60 | val = 0; 61 | break; 62 | case "right": 63 | val = monitorRect.width - this.resolveWidth(rectRequest, metaWindow); 64 | break; 65 | default: 66 | break; 67 | } 68 | break; 69 | case "number": 70 | val = x; 71 | break; 72 | default: 73 | break; 74 | } 75 | val = monitorRect.x + val; 76 | return val; 77 | } 78 | 79 | export function resolveY(rectRequest, metaWindow) { 80 | let metaRect = metaWindow.get_frame_rect(); 81 | let monitorRect = metaWindow.get_work_area_current_monitor(); 82 | let val = metaRect.y; 83 | let y = rectRequest.y; 84 | switch (typeof y) { 85 | case "string": //center, 86 | switch (y) { 87 | case "center": 88 | val = monitorRect.height * 0.5 - this.resolveHeight(rectRequest, metaWindow) * 0.5; 89 | break; 90 | case "top": 91 | val = 0; 92 | break; 93 | case "bottom": // inverse of y=0 94 | val = monitorRect.height - this.resolveHeight(rectRequest, metaWindow); 95 | break; 96 | default: 97 | break; 98 | } 99 | break; 100 | case "number": 101 | val = y; 102 | break; 103 | default: 104 | break; 105 | } 106 | val = monitorRect.y + val; 107 | return val; 108 | } 109 | 110 | export function resolveWidth(rectRequest, metaWindow) { 111 | let metaRect = metaWindow.get_frame_rect(); 112 | let monitorRect = metaWindow.get_work_area_current_monitor(); 113 | let val = metaRect.width; 114 | let width = rectRequest.width; 115 | switch (typeof width) { 116 | case "number": 117 | if (Number.isInteger(width) && width != 1) { 118 | val = width; 119 | } else { 120 | let monitorWidth = monitorRect.width; 121 | val = monitorWidth * width; 122 | } 123 | break; 124 | default: 125 | break; 126 | } 127 | return val; 128 | } 129 | 130 | export function resolveHeight(rectRequest, metaWindow) { 131 | let metaRect = metaWindow.get_frame_rect(); 132 | let monitorRect = metaWindow.get_work_area_current_monitor(); 133 | let val = metaRect.height; 134 | let height = rectRequest.height; 135 | switch (typeof height) { 136 | case "number": 137 | if (Number.isInteger(height) && height != 1) { 138 | val = height; 139 | } else { 140 | let monitorHeight = monitorRect.height; 141 | val = monitorHeight * height; 142 | } 143 | break; 144 | default: 145 | break; 146 | } 147 | return val; 148 | } 149 | 150 | export function orientationFromDirection(direction) { 151 | return direction === Meta.MotionDirection.LEFT || direction === Meta.MotionDirection.RIGHT 152 | ? ORIENTATION_TYPES.HORIZONTAL 153 | : ORIENTATION_TYPES.VERTICAL; 154 | } 155 | 156 | export function orientationFromLayout(layout) { 157 | switch (layout) { 158 | case LAYOUT_TYPES.HSPLIT: 159 | case LAYOUT_TYPES.TABBED: 160 | return ORIENTATION_TYPES.HORIZONTAL; 161 | case LAYOUT_TYPES.VSPLIT: 162 | case LAYOUT_TYPES.STACKED: 163 | return ORIENTATION_TYPES.VERTICAL; 164 | default: 165 | break; 166 | } 167 | } 168 | 169 | export function positionFromDirection(direction) { 170 | return direction === Meta.MotionDirection.LEFT || direction === Meta.MotionDirection.UP 171 | ? POSITION.BEFORE 172 | : POSITION.AFTER; 173 | } 174 | 175 | export function resolveDirection(directionString) { 176 | if (directionString) { 177 | directionString = directionString.toUpperCase(); 178 | 179 | if (directionString === "LEFT") { 180 | return Meta.MotionDirection.LEFT; 181 | } 182 | 183 | if (directionString === "RIGHT") { 184 | return Meta.MotionDirection.RIGHT; 185 | } 186 | 187 | if (directionString === "UP") { 188 | return Meta.MotionDirection.UP; 189 | } 190 | 191 | if (directionString === "DOWN") { 192 | return Meta.MotionDirection.DOWN; 193 | } 194 | } 195 | 196 | return null; 197 | } 198 | 199 | export function directionFrom(position, orientation) { 200 | if (position === POSITION.AFTER) { 201 | if (orientation === ORIENTATION_TYPES.HORIZONTAL) { 202 | return Meta.DisplayDirection.RIGHT; 203 | } else { 204 | return Meta.DisplayDirection.DOWN; 205 | } 206 | } else if (position === POSITION.BEFORE) { 207 | if (orientation === ORIENTATION_TYPES.HORIZONTAL) { 208 | return Meta.DisplayDirection.LEFT; 209 | } else { 210 | return Meta.DisplayDirection.UP; 211 | } 212 | } 213 | } 214 | 215 | export function rectContainsPoint(rect, pointP) { 216 | if (!(rect && pointP)) return false; 217 | return ( 218 | rect.x <= pointP[0] && 219 | pointP[0] <= rect.x + rect.width && 220 | rect.y <= pointP[1] && 221 | pointP[1] <= rect.y + rect.height 222 | ); 223 | } 224 | 225 | export function orientationFromGrab(grabOp) { 226 | if ( 227 | grabOp === Meta.GrabOp.RESIZING_N || 228 | grabOp === Meta.GrabOp.RESIZING_S || 229 | grabOp === Meta.GrabOp.KEYBOARD_RESIZING_N || 230 | grabOp === Meta.GrabOp.KEYBOARD_RESIZING_S 231 | ) { 232 | return ORIENTATION_TYPES.VERTICAL; 233 | } else if ( 234 | grabOp === Meta.GrabOp.RESIZING_E || 235 | grabOp === Meta.GrabOp.RESIZING_W || 236 | grabOp === Meta.GrabOp.KEYBOARD_RESIZING_E || 237 | grabOp === Meta.GrabOp.KEYBOARD_RESIZING_W 238 | ) { 239 | return ORIENTATION_TYPES.HORIZONTAL; 240 | } 241 | return ORIENTATION_TYPES.NONE; 242 | } 243 | 244 | export function positionFromGrabOp(grabOp) { 245 | if ( 246 | grabOp === Meta.GrabOp.RESIZING_W || 247 | grabOp === Meta.GrabOp.RESIZING_N || 248 | grabOp === Meta.GrabOp.KEYBOARD_RESIZING_W || 249 | grabOp === Meta.GrabOp.KEYBOARD_RESIZING_N 250 | ) { 251 | return POSITION.BEFORE; 252 | } else if ( 253 | grabOp === Meta.GrabOp.RESIZING_E || 254 | grabOp === Meta.GrabOp.RESIZING_S || 255 | grabOp === Meta.GrabOp.KEYBOARD_RESIZING_E || 256 | grabOp === Meta.GrabOp.KEYBOARD_RESIZING_S 257 | ) { 258 | return POSITION.AFTER; 259 | } 260 | return POSITION.UNKNOWN; 261 | } 262 | 263 | export function allowResizeGrabOp(grabOp) { 264 | grabOp &= ~1024; // ignore META_GRAB_OP_WINDOW_FLAG_UNCONSTRAINED 265 | return ( 266 | grabOp === Meta.GrabOp.RESIZING_N || 267 | grabOp === Meta.GrabOp.RESIZING_E || 268 | grabOp === Meta.GrabOp.RESIZING_W || 269 | grabOp === Meta.GrabOp.RESIZING_S || 270 | grabOp === Meta.GrabOp.RESIZING_NE || 271 | grabOp === Meta.GrabOp.RESIZING_NW || 272 | grabOp === Meta.GrabOp.RESIZING_SE || 273 | grabOp === Meta.GrabOp.RESIZING_SW || 274 | grabOp === Meta.GrabOp.KEYBOARD_RESIZING_N || 275 | grabOp === Meta.GrabOp.KEYBOARD_RESIZING_E || 276 | grabOp === Meta.GrabOp.KEYBOARD_RESIZING_W || 277 | grabOp === Meta.GrabOp.KEYBOARD_RESIZING_S || 278 | grabOp === Meta.GrabOp.KEYBOARD_RESIZING_UNKNOWN 279 | ); 280 | } 281 | 282 | export function grabMode(grabOp) { 283 | grabOp &= ~1024; // ignore META_GRAB_OP_WINDOW_FLAG_UNCONSTRAINED 284 | if ( 285 | grabOp === Meta.GrabOp.RESIZING_N || 286 | grabOp === Meta.GrabOp.RESIZING_E || 287 | grabOp === Meta.GrabOp.RESIZING_W || 288 | grabOp === Meta.GrabOp.RESIZING_S || 289 | grabOp === Meta.GrabOp.RESIZING_NE || 290 | grabOp === Meta.GrabOp.RESIZING_NW || 291 | grabOp === Meta.GrabOp.RESIZING_SE || 292 | grabOp === Meta.GrabOp.RESIZING_SW || 293 | grabOp === Meta.GrabOp.KEYBOARD_RESIZING_N || 294 | grabOp === Meta.GrabOp.KEYBOARD_RESIZING_E || 295 | grabOp === Meta.GrabOp.KEYBOARD_RESIZING_W || 296 | grabOp === Meta.GrabOp.KEYBOARD_RESIZING_S || 297 | grabOp === Meta.GrabOp.KEYBOARD_RESIZING_UNKNOWN 298 | ) { 299 | return GRAB_TYPES.RESIZING; 300 | } else if ( 301 | grabOp === Meta.GrabOp.KEYBOARD_MOVING || 302 | grabOp === Meta.GrabOp.MOVING || 303 | grabOp === Meta.GrabOp.MOVING_UNCONSTRAINED 304 | ) { 305 | return GRAB_TYPES.MOVING; 306 | } 307 | return GRAB_TYPES.UNKNOWN; 308 | } 309 | 310 | export function decomposeGrabOp(grabOp) { 311 | grabOp &= ~1024; // ignore META_GRAB_OP_WINDOW_FLAG_UNCONSTRAINED 312 | switch (grabOp) { 313 | case Meta.GrabOp.RESIZING_NE: 314 | return [Meta.GrabOp.RESIZING_N, Meta.GrabOp.RESIZING_E]; 315 | case Meta.GrabOp.RESIZING_NW: 316 | return [Meta.GrabOp.RESIZING_N, Meta.GrabOp.RESIZING_W]; 317 | case Meta.GrabOp.RESIZING_SE: 318 | return [Meta.GrabOp.RESIZING_S, Meta.GrabOp.RESIZING_E]; 319 | case Meta.GrabOp.RESIZING_SW: 320 | return [Meta.GrabOp.RESIZING_S, Meta.GrabOp.RESIZING_W]; 321 | default: 322 | return [grabOp]; 323 | } 324 | } 325 | 326 | export function directionFromGrab(grabOp) { 327 | if (grabOp === Meta.GrabOp.RESIZING_E || grabOp === Meta.GrabOp.KEYBOARD_RESIZING_E) { 328 | return Meta.MotionDirection.RIGHT; 329 | } else if (grabOp === Meta.GrabOp.RESIZING_W || grabOp === Meta.GrabOp.KEYBOARD_RESIZING_W) { 330 | return Meta.MotionDirection.LEFT; 331 | } else if (grabOp === Meta.GrabOp.RESIZING_N || grabOp === Meta.GrabOp.KEYBOARD_RESIZING_N) { 332 | return Meta.MotionDirection.UP; 333 | } else if (grabOp === Meta.GrabOp.RESIZING_S || grabOp === Meta.GrabOp.KEYBOARD_RESIZING_S) { 334 | return Meta.MotionDirection.DOWN; 335 | } 336 | } 337 | 338 | export function removeGapOnRect(rectWithGap, gap) { 339 | rectWithGap.x = rectWithGap.x -= gap; 340 | rectWithGap.y = rectWithGap.y -= gap; 341 | rectWithGap.width = rectWithGap.width += gap * 2; 342 | rectWithGap.height = rectWithGap.height += gap * 2; 343 | return rectWithGap; 344 | } 345 | 346 | // Credits: PopShell 347 | export function findWindowWith(title) { 348 | let display = global.display; 349 | let type = Meta.TabList.NORMAL_ALL; 350 | let workspaceMgr = display.get_workspace_manager(); 351 | let workspaces = workspaceMgr.get_n_workspaces(); 352 | 353 | for (let wsId = 1; wsId <= workspaces; wsId++) { 354 | let workspace = workspaceMgr.get_workspace_by_index(wsId); 355 | for (const metaWindow of display.get_tab_list(type, workspace)) { 356 | if ( 357 | metaWindow.title && 358 | title && 359 | (metaWindow.title === title || metaWindow.title.includes(title)) 360 | ) { 361 | return metaWindow; 362 | } 363 | } 364 | } 365 | 366 | return undefined; 367 | } 368 | 369 | export function oppositeDirectionOf(direction) { 370 | if (direction === Meta.MotionDirection.LEFT) { 371 | return Meta.MotionDirection.RIGHT; 372 | } else if (direction === Meta.MotionDirection.RIGHT) { 373 | return Meta.MotionDirection.LEFT; 374 | } else if (direction === Meta.MotionDirection.UP) { 375 | return Meta.MotionDirection.DOWN; 376 | } else if (direction === Meta.MotionDirection.DOWN) { 377 | return Meta.MotionDirection.UP; 378 | } 379 | } 380 | 381 | export function monitorIndex(monitorValue) { 382 | if (!monitorValue) return -1; 383 | let wsIndex = monitorValue.indexOf("ws"); 384 | let indexVal = monitorValue.slice(0, wsIndex); 385 | indexVal = indexVal.replace("mo", ""); 386 | return parseInt(indexVal); 387 | } 388 | 389 | export function _disableDecorations() { 390 | let decos = global.window_group.get_children().filter((a) => a.type != null); 391 | decos.forEach((d) => { 392 | global.window_group.remove_child(d); 393 | d.destroy(); 394 | }); 395 | } 396 | 397 | export function dpi() { 398 | return St.ThemeContext.get_for_stage(global.stage).scale_factor; 399 | } 400 | 401 | export function isGnome(majorVersion) { 402 | return major == majorVersion; 403 | } 404 | 405 | export function isGnomeGTE(majorVersion) { 406 | return major >= majorVersion; 407 | } 408 | -------------------------------------------------------------------------------- /lib/prefs/appearance.js: -------------------------------------------------------------------------------- 1 | // Gnome imports 2 | import Adw from "gi://Adw"; 3 | import GObject from "gi://GObject"; 4 | import Gdk from "gi://Gdk"; 5 | 6 | // Extension imports 7 | import { gettext as _ } from "resource:///org/gnome/Shell/Extensions/js/extensions/prefs.js"; 8 | 9 | // Shared state 10 | import { ConfigManager } from "../shared/settings.js"; 11 | 12 | import { PrefsThemeManager } from "./prefs-theme-manager.js"; 13 | 14 | // Prefs UI 15 | import { ColorRow, PreferencesPage, ResetButton, SpinButtonRow, SwitchRow } from "./widgets.js"; 16 | import { Logger } from "../shared/logger.js"; 17 | 18 | export class AppearancePage extends PreferencesPage { 19 | static { 20 | GObject.registerClass(this); 21 | } 22 | 23 | /** 24 | * @param {string} selector 25 | */ 26 | static getCssSelectorAsMessage(selector) { 27 | switch (selector) { 28 | // TODO: make separate color selection for preview hint 29 | case ".window-tiled-border": 30 | return _("Tiled window"); 31 | case ".window-tabbed-border": 32 | return _("Tabbed window"); 33 | case ".window-stacked-border": 34 | return _("Stacked window"); 35 | case ".window-floated-border": 36 | return _("Floating window"); 37 | case ".window-split-border": 38 | return _("Split direction hint"); 39 | } 40 | } 41 | 42 | constructor({ settings, dir }) { 43 | super({ title: _("Appearance"), icon_name: "brush-symbolic" }); 44 | this.settings = settings; 45 | this.configMgr = new ConfigManager({ dir }); 46 | this.themeMgr = new PrefsThemeManager(this); 47 | this.add_group({ 48 | title: _("Gaps"), 49 | description: _("Change the gap size between windows"), 50 | children: [ 51 | new SpinButtonRow({ 52 | title: _("Gap size"), 53 | range: [0, 32, 1], 54 | settings, 55 | bind: "window-gap-size", 56 | }), 57 | new SpinButtonRow({ 58 | title: _("Gap size multiplier"), 59 | range: [0, 32, 1], 60 | settings, 61 | bind: "window-gap-size-increment", 62 | }), 63 | new SwitchRow({ 64 | title: _("Disable gaps for single window"), 65 | subtitle: _("Disables window gaps when only a single window is present"), 66 | settings, 67 | bind: "window-gap-hidden-on-single", 68 | }), 69 | ], 70 | }); 71 | this.add_group({ 72 | title: _("Style"), 73 | description: _("Change how the shell looks"), 74 | children: [ 75 | new SwitchRow({ 76 | title: _("Preview hint"), 77 | subtitle: _("Shows where the window will be tiled when you let go of it"), 78 | experimental: true, 79 | settings, 80 | bind: "preview-hint-enabled", 81 | }), 82 | new SwitchRow({ 83 | title: _("Border around focused window"), 84 | subtitle: _("Display a colored border around the focused window"), 85 | settings, 86 | bind: "focus-border-toggle", 87 | }), 88 | new SwitchRow({ 89 | title: _("Window split hint border"), 90 | subtitle: _("Show split direction border on focused window"), 91 | settings, 92 | bind: "split-border-toggle", 93 | }), 94 | new SwitchRow({ 95 | title: _("Forge in quick settings"), 96 | subtitle: _("Toggles the Forge tile in quick settings"), 97 | experimental: true, 98 | settings, 99 | bind: "quick-settings-enabled", 100 | }), 101 | ], 102 | }); 103 | this.add_group({ 104 | title: _("Color"), 105 | description: _("Changes the focused window's border and preview hint colors"), 106 | children: [ 107 | "window-tiled-border", 108 | "window-tabbed-border", 109 | "window-stacked-border", 110 | "window-floated-border", 111 | "window-split-border", 112 | ].map((x) => this._createColorOptionWidget(x)), 113 | }); 114 | } 115 | 116 | /** 117 | * @param {string} prefix 118 | */ 119 | _createColorOptionWidget(prefix) { 120 | const selector = `.${prefix}`; 121 | const theme = this.themeMgr; 122 | const title = AppearancePage.getCssSelectorAsMessage(selector); 123 | const colorScheme = theme.getColorSchemeBySelector(selector); 124 | const row = new Adw.ExpanderRow({ title }); 125 | 126 | const borderSizeRow = new SpinButtonRow({ 127 | title: _("Border size"), 128 | range: [1, 6, 1], 129 | // subtitle: 'Properties of the focus hint', 130 | max_width_chars: 1, 131 | max_length: 1, 132 | width_chars: 2, 133 | xalign: 1, 134 | init: theme.removePx(theme.getCssProperty(selector, "border-width").value), 135 | onChange: (value) => { 136 | const px = theme.addPx(value); 137 | Logger.debug(`Setting border width for selector: ${selector} ${px}`); 138 | theme.setCssProperty(selector, "border-width", px); 139 | }, 140 | }); 141 | 142 | borderSizeRow.add_suffix( 143 | new ResetButton({ 144 | onReset: () => { 145 | const borderDefault = theme.defaultPalette[colorScheme]["border-width"]; 146 | theme.setCssProperty(selector, "border-width", theme.addPx(borderDefault)); 147 | borderSizeRow.activatable_widget.value = borderDefault; 148 | }, 149 | }) 150 | ); 151 | 152 | const updateCssColors = (rgbaString) => { 153 | const rgba = new Gdk.RGBA(); 154 | 155 | if (rgba.parse(rgbaString)) { 156 | Logger.debug(`Setting color for selector: ${selector} ${rgbaString}`); 157 | const previewBorderRgba = rgba.copy(); 158 | const previewBackgroundRgba = rgba.copy(); 159 | const overviewBackgroundRgba = rgba.copy(); 160 | 161 | previewBorderRgba.alpha = 0.3; 162 | previewBackgroundRgba.alpha = 0.2; 163 | overviewBackgroundRgba.alpha = 0.5; 164 | 165 | // The primary color updates the focus hint: 166 | theme.setCssProperty(selector, "border-color", rgba.to_string()); 167 | 168 | // Only apply below on the tabbed scheme 169 | if (colorScheme === "tabbed") { 170 | const tabBorderRgba = rgba.copy(); 171 | const tabActiveBackgroundRgba = rgba.copy(); 172 | tabBorderRgba.alpha = 0.6; 173 | theme.setCssProperty( 174 | `.window-${colorScheme}-tab`, 175 | "border-color", 176 | tabBorderRgba.to_string() 177 | ); 178 | theme.setCssProperty( 179 | `.window-${colorScheme}-tab-active`, 180 | "background-color", 181 | tabActiveBackgroundRgba.to_string() 182 | ); 183 | } 184 | // And then finally the preview when doing drag/drop tiling: 185 | theme.setCssProperty( 186 | `.window-tilepreview-${colorScheme}`, 187 | "border-color", 188 | previewBorderRgba.to_string() 189 | ); 190 | theme.setCssProperty( 191 | `.window-tilepreview-${colorScheme}`, 192 | "background-color", 193 | previewBackgroundRgba.to_string() 194 | ); 195 | } 196 | }; 197 | 198 | const borderColorRow = new ColorRow({ 199 | title: _("Border color"), 200 | init: theme.getCssProperty(selector, "border-color").value, 201 | onChange: updateCssColors, 202 | }); 203 | 204 | borderColorRow.add_suffix( 205 | new ResetButton({ 206 | onReset: () => { 207 | const selectorColor = theme.defaultPalette[colorScheme].color; 208 | updateCssColors(selectorColor); 209 | const rgba = new Gdk.RGBA(); 210 | if (rgba.parse(selectorColor)) { 211 | borderColorRow.colorButton.set_rgba(rgba); 212 | } 213 | }, 214 | }) 215 | ); 216 | 217 | row.add_row(borderColorRow); 218 | row.add_row(borderSizeRow); 219 | 220 | return row; 221 | } 222 | } 223 | -------------------------------------------------------------------------------- /lib/prefs/keyboard.js: -------------------------------------------------------------------------------- 1 | // Gnome imports 2 | import Adw from "gi://Adw"; 3 | import GObject from "gi://GObject"; 4 | import Gtk from "gi://Gtk"; 5 | 6 | // Extension Imports 7 | import { gettext as _ } from "resource:///org/gnome/Shell/Extensions/js/extensions/prefs.js"; 8 | 9 | // Prefs UI 10 | import { EntryRow, PreferencesPage, RadioRow } from "./widgets.js"; 11 | import { Logger } from "../shared/logger.js"; 12 | 13 | export class KeyboardPage extends PreferencesPage { 14 | static { 15 | GObject.registerClass(this); 16 | } 17 | 18 | constructor({ kbdSettings }) { 19 | super({ title: _("Keyboard"), icon_name: "input-keyboard-symbolic" }); 20 | 21 | this.add_group({ 22 | title: _("Drag-and-drop modifier key"), 23 | description: _( 24 | "Change the modifier key for tiling windows via drag-and-drop. Select 'None' to always tile" 25 | ), 26 | children: [ 27 | new RadioRow({ 28 | title: _("Modifier key"), 29 | settings: kbdSettings, 30 | bind: "mod-mask-mouse-tile", 31 | options: { 32 | Super: _("Super"), 33 | Ctrl: _("Ctrl"), 34 | Alt: _("Alt"), 35 | None: _("None"), 36 | }, 37 | }), 38 | ], 39 | }); 40 | this.add_group({ 41 | title: _("Shortcuts"), 42 | description: _( 43 | 'Change the tiling shortcuts. To clear a shortcut clear the input field. To apply a shortcut press enter. Syntax examples' 44 | ), 45 | children: Object.entries({ 46 | window: "Tiling shortcuts", 47 | con: "Container shortcuts", 48 | workspace: "Workspace shortcuts", 49 | focus: "Appearance shortcuts", 50 | prefs: "Other shortcuts", 51 | }).map(([prefix, gettextKey]) => 52 | KeyboardPage.makeKeygroupExpander(prefix, gettextKey, kbdSettings) 53 | ), 54 | }); 55 | } 56 | 57 | static makeKeygroupExpander(prefix, gettextKey, settings) { 58 | const expander = new Adw.ExpanderRow({ title: _(gettextKey) }); 59 | KeyboardPage.createKeyList(settings, prefix).forEach((key) => 60 | expander.add_row( 61 | new EntryRow({ 62 | title: key, 63 | settings, 64 | bind: key, 65 | map: { 66 | from(settings, bind) { 67 | return settings.get_strv(bind).join(","); 68 | }, 69 | to(settings, bind, value) { 70 | if (!!value) { 71 | const mappings = value.split(",").map((x) => { 72 | const [, key, mods] = Gtk.accelerator_parse(x); 73 | return Gtk.accelerator_valid(key, mods) && Gtk.accelerator_name(key, mods); 74 | }); 75 | if (mappings.every((x) => !!x)) { 76 | Logger.info("setting", bind, "to", mappings); 77 | settings.set_strv(bind, mappings); 78 | } 79 | } else { 80 | // If value deleted, unset the mapping 81 | settings.set_strv(bind, []); 82 | } 83 | }, 84 | }, 85 | }) 86 | ) 87 | ); 88 | return expander; 89 | } 90 | 91 | static createKeyList(settings, categoryName) { 92 | return settings 93 | .list_keys() 94 | .filter((keyName) => !!keyName && !!categoryName && keyName.startsWith(categoryName)) 95 | .sort((a, b) => { 96 | const aUp = a.toUpperCase(); 97 | const bUp = b.toUpperCase(); 98 | if (aUp < bUp) return -1; 99 | if (aUp > bUp) return 1; 100 | return 0; 101 | }); 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /lib/prefs/prefs-theme-manager.js: -------------------------------------------------------------------------------- 1 | import GObject from "gi://GObject"; 2 | 3 | import { ThemeManagerBase } from "../shared/theme.js"; 4 | 5 | export class PrefsThemeManager extends ThemeManagerBase { 6 | static { 7 | GObject.registerClass(this); 8 | } 9 | 10 | reloadStylesheet() { 11 | this.settings.set_string("css-updated", Date.now().toString()); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /lib/prefs/settings.js: -------------------------------------------------------------------------------- 1 | // Gnome imports 2 | import Adw from "gi://Adw"; 3 | import Gtk from "gi://Gtk"; 4 | import GObject from "gi://GObject"; 5 | 6 | // Shared state 7 | import { Logger } from "../shared/logger.js"; 8 | import { production } from "../shared/settings.js"; 9 | 10 | // Prefs UI 11 | import { DropDownRow, SwitchRow, PreferencesPage, EntryRow } from "./widgets.js"; 12 | 13 | // Extension imports 14 | import { gettext as _ } from "resource:///org/gnome/Shell/Extensions/js/extensions/prefs.js"; 15 | import { PACKAGE_VERSION } from "resource:///org/gnome/Shell/Extensions/js/misc/config.js"; 16 | 17 | import { developers } from "./metadata.js"; 18 | 19 | function showAboutWindow(parent, { version, description: comments }) { 20 | const abt = new Adw.AboutWindow({ 21 | ...(parent && { transient_for: parent }), 22 | // TODO: fetch these from github at build time 23 | application_name: _("Forge"), 24 | application_icon: "forge-logo-symbolic", 25 | version: `${PACKAGE_VERSION}-${version.toString()}`, 26 | copyright: `© 2021-${new Date().getFullYear()} jmmaranan`, 27 | issue_url: "https://github.com/forge-ext/forge/issues/new", 28 | license_type: Gtk.License.GPL_3_0, 29 | website: "https://github.com/forge-ext/forge", 30 | developers, 31 | comments, 32 | designers: [], 33 | translator_credits: _("translator-credits"), 34 | }); 35 | abt.present(); 36 | } 37 | 38 | function makeAboutButton(parent, metadata) { 39 | const button = new Gtk.Button({ 40 | icon_name: "help-about-symbolic", 41 | tooltip_text: _("About"), 42 | valign: Gtk.Align.CENTER, 43 | }); 44 | button.connect("clicked", () => showAboutWindow(parent, metadata)); 45 | return button; 46 | } 47 | 48 | export class SettingsPage extends PreferencesPage { 49 | static { 50 | GObject.registerClass(this); 51 | } 52 | 53 | constructor({ settings, window, metadata }) { 54 | super({ title: _("Tiling"), icon_name: "view-grid-symbolic" }); 55 | this.add_group({ 56 | title: _("Behavior"), 57 | description: _("Change how the tiling behaves"), 58 | header_suffix: makeAboutButton(window, metadata), 59 | children: [ 60 | new SwitchRow({ 61 | title: _("Focus on Hover"), 62 | subtitle: _("Window focus follows the pointer"), 63 | experimental: true, 64 | settings, 65 | bind: "focus-on-hover-enabled", 66 | }), 67 | new SwitchRow({ 68 | title: _("Move pointer with focused window"), 69 | subtitle: _("Moves the pointer when focusing or swapping via keyboard"), 70 | experimental: true, 71 | settings, 72 | bind: "move-pointer-focus-enabled", 73 | }), 74 | new SwitchRow({ 75 | title: _("Quarter tiling"), 76 | subtitle: _("Places new windows in a clock-wise fashion"), 77 | experimental: true, 78 | settings, 79 | bind: "auto-split-enabled", 80 | }), 81 | new SwitchRow({ 82 | title: _("Stacked tiling"), 83 | subtitle: _("Stacks windows on top of each other while still tiling them"), 84 | experimental: true, 85 | settings, 86 | bind: "stacked-tiling-mode-enabled", 87 | }), 88 | new SwitchRow({ 89 | title: _("Tabbed tiling"), 90 | subtitle: _("Groups windows as tabs"), 91 | experimental: true, 92 | settings, 93 | bind: "tabbed-tiling-mode-enabled", 94 | }), 95 | new SwitchRow({ 96 | title: _("Auto exit tabbed tiling"), 97 | subtitle: _("Exit tabbed tiling mode when only a single tab remains"), 98 | settings, 99 | bind: "auto-exit-tabbed", 100 | bind: "move-pointer-focus-enabled", 101 | }), 102 | new DropDownRow({ 103 | title: _("Drag-and-drop behavior"), 104 | subtitle: _("What to do when dragging one window on top of another"), 105 | settings, 106 | type: "s", 107 | bind: "dnd-center-layout", 108 | items: [ 109 | { id: "swap", name: _("Swap") }, 110 | { id: "tabbed", name: _("Tabbed") }, 111 | { id: "stacked", name: _("Stacked") }, 112 | ], 113 | }), 114 | new SwitchRow({ 115 | title: _("Always on Top mode for floating windows"), 116 | subtitle: _("Makes floating windows appear above tiled windows"), 117 | experimental: true, 118 | settings, 119 | bind: "float-always-on-top-enabled", 120 | }), 121 | ], 122 | }); 123 | this.add_group({ 124 | title: _("Non-tiling workspaces"), 125 | description: _("Disables tiling on specified workspaces. Starts from 0, separated by commas"), 126 | children: [ 127 | new EntryRow({ 128 | title: _("Example: 0,1,2"), 129 | settings, 130 | bind: "workspace-skip-tile", 131 | }), 132 | ], 133 | }); 134 | if (!production) { 135 | this.add_group({ 136 | title: _("Logger"), 137 | children: [ 138 | new DropDownRow({ 139 | title: _("Logger Level"), 140 | settings, 141 | bind: "log-level", 142 | items: Object.entries(Logger.LOG_LEVELS).map(([name, id]) => ({ id, name })), 143 | type: "u", 144 | }), 145 | ], 146 | }); 147 | } 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /lib/prefs/widgets.js: -------------------------------------------------------------------------------- 1 | /** @license (c) aylur. GPL v3 */ 2 | 3 | import Adw from "gi://Adw"; 4 | import Gio from "gi://Gio"; 5 | import Gdk from "gi://Gdk"; 6 | import Gtk from "gi://Gtk"; 7 | import GObject from "gi://GObject"; 8 | 9 | // GNOME imports 10 | import { gettext as _ } from "resource:///org/gnome/Shell/Extensions/js/extensions/prefs.js"; 11 | 12 | // Shared state 13 | import { Logger } from "../shared/logger.js"; 14 | 15 | export class PreferencesPage extends Adw.PreferencesPage { 16 | static { 17 | GObject.registerClass(this); 18 | } 19 | 20 | add_group({ title, description = "", children, header_suffix = "" }) { 21 | const group = new Adw.PreferencesGroup({ title, description }); 22 | for (const child of children) group.add(child); 23 | if (header_suffix) group.set_header_suffix(header_suffix); 24 | this.add(group); 25 | } 26 | } 27 | 28 | export class SwitchRow extends Adw.ActionRow { 29 | static { 30 | GObject.registerClass(this); 31 | } 32 | 33 | constructor({ title, settings, bind, subtitle = "", experimental = false }) { 34 | super({ title, subtitle }); 35 | const gswitch = new Gtk.Switch({ 36 | active: settings.get_boolean(bind), 37 | valign: Gtk.Align.CENTER, 38 | }); 39 | settings.bind(bind, gswitch, "active", Gio.SettingsBindFlags.DEFAULT); 40 | if (experimental) { 41 | const icon = new Gtk.Image({ icon_name: "bug-symbolic" }); 42 | icon.set_tooltip_markup( 43 | _("CAUTION: Enabling this setting can lead to bugs or cause the shell to crash") 44 | ); 45 | this.add_suffix(icon); 46 | } 47 | this.add_suffix(gswitch); 48 | this.activatable_widget = gswitch; 49 | } 50 | } 51 | 52 | export class ColorRow extends Adw.ActionRow { 53 | static { 54 | GObject.registerClass(this); 55 | } 56 | 57 | constructor({ title, init, onChange, subtitle = "" }) { 58 | super({ title, subtitle }); 59 | let rgba = new Gdk.RGBA(); 60 | rgba.parse(init); 61 | this.colorButton = new Gtk.ColorButton({ rgba, use_alpha: true, valign: Gtk.Align.CENTER }); 62 | this.colorButton.connect("color-set", () => { 63 | onChange(this.colorButton.get_rgba().to_string()); 64 | }); 65 | this.add_suffix(this.colorButton); 66 | this.activatable_widget = this.colorButton; 67 | } 68 | } 69 | 70 | export class SpinButtonRow extends Adw.ActionRow { 71 | static { 72 | GObject.registerClass(this); 73 | } 74 | 75 | constructor({ 76 | title, 77 | range: [low, high, step], 78 | subtitle = "", 79 | init = undefined, 80 | onChange = undefined, 81 | max_width_chars = undefined, 82 | max_length = undefined, 83 | width_chars = undefined, 84 | xalign = undefined, 85 | settings = undefined, 86 | bind = undefined, 87 | }) { 88 | super({ title, subtitle }); 89 | const gspin = Gtk.SpinButton.new_with_range(low, high, step); 90 | gspin.valign = Gtk.Align.CENTER; 91 | if (bind && settings) { 92 | settings.bind(bind, gspin, "value", Gio.SettingsBindFlags.DEFAULT); 93 | } else if (init) { 94 | gspin.value = init; 95 | gspin.connect("value-changed", (widget) => { 96 | onChange?.(widget.value); 97 | }); 98 | } 99 | this.add_suffix(gspin); 100 | this.activatable_widget = gspin; 101 | } 102 | } 103 | 104 | export class DropDownRow extends Adw.ActionRow { 105 | static { 106 | GObject.registerClass(this); 107 | } 108 | 109 | /** 110 | * @type {string} 111 | * Name of the gsetting key to bind to 112 | */ 113 | bind; 114 | 115 | /** 116 | * @type {'b'|'y'|'n'|'q'|'i'|'u'|'x'|'t'|'h'|'d'|'s'|'o'|'g'|'?'|'a'|'m'} 117 | * - b: the type string of G_VARIANT_TYPE_BOOLEAN; a boolean value. 118 | * - y: the type string of G_VARIANT_TYPE_BYTE; a byte. 119 | * - n: the type string of G_VARIANT_TYPE_INT16; a signed 16 bit integer. 120 | * - q: the type string of G_VARIANT_TYPE_UINT16; an unsigned 16 bit integer. 121 | * - i: the type string of G_VARIANT_TYPE_INT32; a signed 32 bit integer. 122 | * - u: the type string of G_VARIANT_TYPE_UINT32; an unsigned 32 bit integer. 123 | * - x: the type string of G_VARIANT_TYPE_INT64; a signed 64 bit integer. 124 | * - t: the type string of G_VARIANT_TYPE_UINT64; an unsigned 64 bit integer. 125 | * - h: the type string of G_VARIANT_TYPE_HANDLE; a signed 32 bit value that, by convention, is used as an index into an array of file descriptors that are sent alongside a D-Bus message. 126 | * - d: the type string of G_VARIANT_TYPE_DOUBLE; a double precision floating point value. 127 | * - s: the type string of G_VARIANT_TYPE_STRING; a string. 128 | * - o: the type string of G_VARIANT_TYPE_OBJECT_PATH; a string in the form of a D-Bus object path. 129 | * - g: the type string of G_VARIANT_TYPE_SIGNATURE; a string in the form of a D-Bus type signature. 130 | * - ?: the type string of G_VARIANT_TYPE_BASIC; an indefinite type that is a supertype of any of the basic types. 131 | * - v: the type string of G_VARIANT_TYPE_VARIANT; a container type that contain any other type of value. 132 | * - a: used as a prefix on another type string to mean an array of that type; the type string “ai”, for example, is the type of an array of signed 32-bit integers. 133 | * - m: used as a prefix on another type string to mean a “maybe”, or “nullable”, version of that type; the type string “ms”, for example, is the type of a value that maybe contains a string, or maybe contains nothing. 134 | */ 135 | type; 136 | 137 | selected = 0; 138 | 139 | /** @type {{name: string; id: string}[]} */ 140 | items; 141 | 142 | model = new Gtk.StringList(); 143 | 144 | /** @type {Gtk.DropDown} */ 145 | dropdown; 146 | 147 | constructor({ title, settings, bind, items, subtitle = "", type }) { 148 | super({ title, subtitle }); 149 | this.settings = settings; 150 | this.items = items; 151 | this.bind = bind; 152 | this.type = type ?? this.settings.get_value(bind)?.get_type() ?? "?"; 153 | this.#build(); 154 | this.add_suffix(this.dropdown); 155 | this.add_suffix(new ResetButton({ settings, bind, onReset: () => this.reset() })); 156 | } 157 | 158 | reset() { 159 | this.dropdown.selected = 0; 160 | this.selected = 0; 161 | } 162 | 163 | #build() { 164 | for (const { name, id } of this.items) { 165 | this.model.append(name); 166 | if (this.#get() === id) this.selected = this.items.findIndex((x) => x.id === id); 167 | } 168 | const { model, selected } = this; 169 | this.dropdown = new Gtk.DropDown({ valign: Gtk.Align.CENTER, model, selected }); 170 | this.dropdown.connect("notify::selected", () => this.#onSelected()); 171 | this.activatable_widget = this.dropdown; 172 | } 173 | 174 | #onSelected() { 175 | this.selected = this.dropdown.selected; 176 | const { id } = this.items[this.selected]; 177 | Logger.debug("setting", id, this.selected); 178 | this.#set(this.bind, id); 179 | } 180 | 181 | static #settingsTypes = { 182 | b: "boolean", 183 | y: "byte", 184 | n: "int16", 185 | q: "uint16", 186 | i: "int32", 187 | u: "uint", 188 | x: "int64", 189 | t: "uint64", 190 | d: "double", 191 | s: "string", 192 | o: "objv", 193 | }; 194 | 195 | /** 196 | * @param {string} x 197 | */ 198 | #get(x = this.bind) { 199 | const methodName = `get_${DropDownRow.#settingsTypes[this.type] ?? "value"}`; 200 | return this.settings[methodName]?.(x); 201 | } 202 | 203 | /** 204 | * @param {string} x 205 | * @param {unknown} y 206 | */ 207 | #set(x, y) { 208 | const methodName = `set_${DropDownRow.#settingsTypes[this.type] ?? "value"}`; 209 | Logger.log(`${methodName}(${x}, ${y})`); 210 | return this.settings[methodName]?.(x, y); 211 | } 212 | } 213 | 214 | export class ClearButton extends Gtk.Button { 215 | static { 216 | GObject.registerClass(this); 217 | } 218 | constructor({ settings = undefined, bind = undefined, onClear }) { 219 | super({ 220 | icon_name: "edit-clear-symbolic", 221 | tooltip_text: _("Clear shortcut"), 222 | css_classes: ["flat", "circular"], 223 | valign: Gtk.Align.CENTER, 224 | }); 225 | this.connect("clicked", () => { 226 | onClear?.(); 227 | }); 228 | } 229 | } 230 | 231 | export class ResetButton extends Gtk.Button { 232 | static { 233 | GObject.registerClass(this); 234 | } 235 | constructor({ settings = undefined, bind = undefined, onReset }) { 236 | super({ 237 | icon_name: "edit-undo-symbolic", 238 | tooltip_text: _("Reset to default"), 239 | css_classes: ["flat", "circular"], 240 | valign: Gtk.Align.CENTER, 241 | }); 242 | this.connect("clicked", () => { 243 | settings?.reset(bind); 244 | onReset?.(); 245 | }); 246 | } 247 | } 248 | 249 | export class EntryRow extends Adw.EntryRow { 250 | static { 251 | GObject.registerClass(this); 252 | } 253 | 254 | constructor({ title, settings, bind, map }) { 255 | super({ title }); 256 | this.connect("changed", () => { 257 | const text = this.get_text(); 258 | if (typeof text === "string") 259 | if (map) { 260 | map.to(settings, bind, text); 261 | } else { 262 | settings.set_string(bind, text); 263 | } 264 | }); 265 | const current = map ? map.from(settings, bind) : settings.get_string(bind); 266 | this.set_text(current ?? ""); 267 | this.add_suffix( 268 | new ClearButton({ 269 | settings, 270 | bind, 271 | onClear: () => { 272 | this.set_text(""); 273 | }, 274 | }) 275 | ); 276 | this.add_suffix( 277 | new ResetButton({ 278 | settings, 279 | bind, 280 | onReset: () => { 281 | this.set_text((map ? map.from(settings, bind) : settings.get_string(bind)) ?? ""); 282 | }, 283 | }) 284 | ); 285 | } 286 | } 287 | 288 | export class RadioRow extends Adw.ActionRow { 289 | static { 290 | GObject.registerClass(this); 291 | } 292 | 293 | static orientation = Gtk.Orientation.HORIZONTAL; 294 | 295 | static spacing = 10; 296 | 297 | static valign = Gtk.Align.CENTER; 298 | 299 | constructor({ title, subtitle = "", settings, bind, options }) { 300 | super({ title, subtitle }); 301 | const current = settings.get_string(bind); 302 | const labels = Object.fromEntries(Object.entries(options).map(([k, v]) => [v, k])); 303 | const { orientation, spacing, valign } = RadioRow; 304 | const hbox = new Gtk.Box({ orientation, spacing, valign }); 305 | let group; 306 | for (const [key, label] of Object.entries(options)) { 307 | const toggle = new Gtk.ToggleButton({ label, ...(group && { group }) }); 308 | group ||= toggle; 309 | toggle.active = key === current; 310 | toggle.connect("clicked", () => { 311 | if (toggle.active) { 312 | settings.set_string(bind, labels[toggle.label]); 313 | } 314 | }); 315 | hbox.append(toggle); 316 | } 317 | this.add_suffix(hbox); 318 | } 319 | } 320 | -------------------------------------------------------------------------------- /lib/shared/logger.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the Forge extension for GNOME 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | * 17 | */ 18 | 19 | import { production } from "./settings.js"; 20 | 21 | export class Logger { 22 | static #settings; 23 | 24 | static LOG_LEVELS = { 25 | OFF: 0, 26 | FATAL: 1, 27 | ERROR: 2, 28 | WARN: 3, 29 | INFO: 4, 30 | DEBUG: 5, 31 | TRACE: 6, 32 | ALL: 7, 33 | }; 34 | 35 | static init(settings) { 36 | this.#settings = settings; 37 | } 38 | 39 | static get #level() { 40 | if (this.#settings?.get_boolean?.("logging-enabled")) { 41 | return production 42 | ? Logger.LOG_LEVELS.OFF 43 | : this.#settings?.get_uint?.("log-level") ?? Logger.LOG_LEVELS.OFF; 44 | } 45 | return Logger.LOG_LEVELS.OFF; 46 | } 47 | 48 | // TODO: use console.* methods 49 | static format(msg, ...params) { 50 | return params.reduce((acc, val) => acc.replace("{}", val), msg); 51 | } 52 | 53 | static fatal(...args) { 54 | if (this.#level > Logger.LOG_LEVELS.OFF) log(`[Forge] [FATAL]`, ...args); 55 | } 56 | 57 | static error(...args) { 58 | if (this.#level > Logger.LOG_LEVELS.FATAL) log(`[Forge] [ERROR]`, ...args); 59 | } 60 | 61 | static warn(...args) { 62 | if (this.#level > Logger.LOG_LEVELS.ERROR) log(`[Forge] [WARN]`, ...args); 63 | } 64 | 65 | static info(...args) { 66 | if (this.#level > Logger.LOG_LEVELS.WARN) log(`[Forge] [INFO]`, ...args); 67 | } 68 | 69 | static debug(...args) { 70 | if (this.#level > Logger.LOG_LEVELS.INFO) log(`[Forge] [DEBUG]`, ...args); 71 | } 72 | 73 | static trace(...args) { 74 | if (this.#level > Logger.LOG_LEVELS.DEBUG) log(`[Forge] [TRACE]`, ...args); 75 | } 76 | 77 | static log(...args) { 78 | if (this.#level > Logger.LOG_LEVELS.OFF) log(`[Forge] [LOG]`, ...args); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /lib/shared/settings.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the Forge Window Manager extension for Gnome 3 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | * 17 | */ 18 | 19 | // Gnome imports 20 | import Gio from "gi://Gio"; 21 | import GLib from "gi://GLib"; 22 | import GObject from "gi://GObject"; 23 | 24 | import { Logger } from "./logger.js"; 25 | 26 | // Dev or Prod mode, see Makefile:debug 27 | export const production = true; 28 | 29 | export class ConfigManager extends GObject.Object { 30 | static { 31 | GObject.registerClass(this); 32 | } 33 | 34 | #confDir = GLib.get_user_config_dir(); 35 | 36 | constructor({ dir }) { 37 | super(); 38 | this.extensionPath = dir.get_path(); 39 | } 40 | 41 | get confDir() { 42 | return `${this.#confDir}/forge`; 43 | } 44 | 45 | get defaultStylesheetFile() { 46 | const defaultStylesheet = GLib.build_filenamev([this.extensionPath, `stylesheet.css`]); 47 | 48 | Logger.trace(`default-stylesheet: ${defaultStylesheet}`); 49 | 50 | const defaultStylesheetFile = Gio.File.new_for_path(defaultStylesheet); 51 | if (defaultStylesheetFile.query_exists(null)) { 52 | return defaultStylesheetFile; 53 | } 54 | 55 | return null; 56 | } 57 | 58 | get stylesheetFile() { 59 | const profileSettingPath = `${this.confDir}/stylesheet/forge`; 60 | const settingFile = "stylesheet.css"; 61 | const defaultSettingFile = this.defaultStylesheetFile; 62 | return this.loadFile(profileSettingPath, settingFile, defaultSettingFile); 63 | } 64 | 65 | get defaultWindowConfigFile() { 66 | const defaultWindowConfig = GLib.build_filenamev([ 67 | this.extensionPath, 68 | `config`, 69 | `windows.json`, 70 | ]); 71 | 72 | Logger.trace(`default-window-config: ${defaultWindowConfig}`); 73 | const defaultWindowConfigFile = Gio.File.new_for_path(defaultWindowConfig); 74 | 75 | if (defaultWindowConfigFile.query_exists(null)) { 76 | return defaultWindowConfigFile; 77 | } 78 | 79 | return null; 80 | } 81 | 82 | get windowConfigFile() { 83 | const profileSettingPath = `${this.confDir}/config`; 84 | const settingFile = "windows.json"; 85 | const defaultSettingFile = this.defaultWindowConfigFile; 86 | return this.loadFile(profileSettingPath, settingFile, defaultSettingFile); 87 | } 88 | 89 | loadFile(path, file, defaultFile) { 90 | const customSetting = GLib.build_filenamev([path, file]); 91 | Logger.trace(`custom-setting-file: ${customSetting}`); 92 | 93 | const customSettingFile = Gio.File.new_for_path(customSetting); 94 | if (customSettingFile.query_exists(null)) { 95 | return customSettingFile; 96 | } else { 97 | const profileCustomSettingDir = Gio.File.new_for_path(path); 98 | if (!profileCustomSettingDir.query_exists(null)) { 99 | if (profileCustomSettingDir.make_directory_with_parents(null)) { 100 | const createdStream = customSettingFile.create(Gio.FileCreateFlags.NONE, null); 101 | const defaultContents = this.loadFileContents(defaultFile); 102 | Logger.trace(defaultContents); 103 | createdStream.write_all(defaultContents, null); 104 | } 105 | } 106 | } 107 | 108 | return null; 109 | } 110 | 111 | loadFileContents(configFile) { 112 | let [success, contents] = configFile.load_contents(null); 113 | if (success) { 114 | const stringContents = imports.byteArray.toString(contents); 115 | return stringContents; 116 | } 117 | } 118 | 119 | get windowProps() { 120 | let windowConfigFile = this.windowConfigFile; 121 | let windowProps = null; 122 | if (!windowConfigFile || !production) { 123 | windowConfigFile = this.defaultWindowConfigFile; 124 | } 125 | 126 | let [success, contents] = windowConfigFile.load_contents(null); 127 | if (success) { 128 | const windowConfigContents = imports.byteArray.toString(contents); 129 | Logger.trace(`${windowConfigContents}`); 130 | windowProps = JSON.parse(windowConfigContents); 131 | } 132 | return windowProps; 133 | } 134 | 135 | set windowProps(props) { 136 | let windowConfigFile = this.windowConfigFile; 137 | if (!windowConfigFile || !production) { 138 | windowConfigFile = this.defaultWindowConfigFile; 139 | } 140 | 141 | let windowConfigContents = JSON.stringify(props, null, 4); 142 | 143 | const PERMISSIONS_MODE = 0o744; 144 | 145 | if (GLib.mkdir_with_parents(windowConfigFile.get_parent().get_path(), PERMISSIONS_MODE) === 0) { 146 | let [_, _tag] = windowConfigFile.replace_contents( 147 | windowConfigContents, 148 | null, 149 | false, 150 | Gio.FileCreateFlags.REPLACE_DESTINATION, 151 | null 152 | ); 153 | } 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /lib/shared/theme.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the Forge extension for GNOME 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | * 17 | */ 18 | 19 | // Gnome imports 20 | import Gio from "gi://Gio"; 21 | import GLib from "gi://GLib"; 22 | import GObject from "gi://GObject"; 23 | 24 | // Application imports 25 | import { stringify, parse } from "../css/index.js"; 26 | import { production } from "./settings.js"; 27 | 28 | export class ThemeManagerBase extends GObject.Object { 29 | static { 30 | GObject.registerClass(this); 31 | } 32 | 33 | constructor({ configMgr, settings }) { 34 | super(); 35 | this.configMgr = configMgr; 36 | this.settings = settings; 37 | this._importCss(); 38 | this.defaultPalette = this.getDefaultPalette(); 39 | 40 | // A random number to denote an update on the css, usually the possible next version 41 | // in extensions.gnome.org 42 | // TODO: need to research the most effective way to bring in CSS updates 43 | // since the schema css-last-update might be triggered when there is a 44 | // code change on the schema unrelated to css updates. 45 | // For now tagging works. See @this.patchCss() and @this._needUpdate(). 46 | this.cssTag = 37; 47 | 48 | // TODO: should the patchCss() call be done here? 49 | } 50 | 51 | /** 52 | * @param {string} value 53 | */ 54 | addPx(value) { 55 | return `${value}px`; 56 | } 57 | 58 | /** 59 | * @param {string} value 60 | */ 61 | removePx(value) { 62 | return value.replace("px", ""); 63 | } 64 | 65 | getDefaultPalette() { 66 | return { 67 | tiled: this.getDefaults("tiled"), 68 | split: this.getDefaults("split"), 69 | floated: this.getDefaults("floated"), 70 | stacked: this.getDefaults("stacked"), 71 | tabbed: this.getDefaults("tabbed"), 72 | }; 73 | } 74 | 75 | /** 76 | * The scheme name is in between the CSS selector name 77 | * E.g. window-tiled-color should return `tiled` 78 | * @param {string} selector 79 | */ 80 | getColorSchemeBySelector(selector) { 81 | if (!selector.includes("-")) return null; 82 | let firstDash = selector.indexOf("-"); 83 | let secondDash = selector.indexOf("-", firstDash + 1); 84 | const scheme = selector.substr(firstDash + 1, secondDash - firstDash - 1); 85 | return scheme; 86 | } 87 | 88 | /** 89 | * @param {string} color 90 | */ 91 | getDefaults(color) { 92 | return { 93 | color: this.getCssProperty(`.${color}`, "color").value, 94 | "border-width": this.removePx(this.getCssProperty(`.${color}`, "border-width").value), 95 | opacity: this.getCssProperty(`.${color}`, "opacity").value, 96 | }; 97 | } 98 | 99 | /** 100 | * @param {any} selector 101 | */ 102 | getCssRule(selector) { 103 | if (this.cssAst) { 104 | const rules = this.cssAst.stylesheet.rules; 105 | // return only the first match, Forge CSS authors should make sure class names are unique :) 106 | const matchRules = rules.filter((r) => r.selectors.filter((s) => s === selector).length > 0); 107 | return matchRules.length > 0 ? matchRules[0] : {}; 108 | } 109 | return {}; 110 | } 111 | 112 | /** 113 | * @param {string} selector 114 | * @param {string} propertyName 115 | */ 116 | getCssProperty(selector, propertyName) { 117 | const cssRule = this.getCssRule(selector); 118 | 119 | if (cssRule) { 120 | const matchDeclarations = cssRule.declarations.filter((d) => d.property === propertyName); 121 | return matchDeclarations.length > 0 ? matchDeclarations[0] : {}; 122 | } 123 | 124 | return {}; 125 | } 126 | 127 | /** 128 | * @param {string} selector 129 | * @param {string} propertyName 130 | * @param {string} propertyValue 131 | */ 132 | setCssProperty(selector, propertyName, propertyValue) { 133 | const cssProperty = this.getCssProperty(selector, propertyName); 134 | if (cssProperty) { 135 | cssProperty.value = propertyValue; 136 | this._updateCss(); 137 | return true; 138 | } 139 | return false; 140 | } 141 | 142 | /** 143 | * Returns the AST for stylesheet.css 144 | */ 145 | _importCss() { 146 | let cssFile = this.configMgr.stylesheetFile; 147 | if (!cssFile || !production) { 148 | cssFile = this.configMgr.defaultStylesheetFile; 149 | } 150 | 151 | let [success, contents] = cssFile.load_contents(null); 152 | if (success) { 153 | const cssContents = new TextDecoder().decode(contents); 154 | this.cssAst = parse(cssContents); 155 | } 156 | } 157 | 158 | /** 159 | * Writes the AST back to stylesheet.css and reloads the theme 160 | */ 161 | _updateCss() { 162 | if (!this.cssAst) { 163 | return; 164 | } 165 | 166 | let cssFile = this.configMgr.stylesheetFile; 167 | if (!cssFile || !production) { 168 | cssFile = this.configMgr.defaultStylesheetFile; 169 | } 170 | 171 | const cssContents = stringify(this.cssAst); 172 | const PERMISSIONS_MODE = 0o744; 173 | 174 | if (GLib.mkdir_with_parents(cssFile.get_parent().get_path(), PERMISSIONS_MODE) === 0) { 175 | let [success, _tag] = cssFile.replace_contents( 176 | cssContents, 177 | null, 178 | false, 179 | Gio.FileCreateFlags.REPLACE_DESTINATION, 180 | null 181 | ); 182 | if (success) { 183 | this.reloadStylesheet(); 184 | } 185 | } 186 | } 187 | 188 | /** 189 | * BREAKING: Patches the CSS by overriding the $HOME/.config stylesheet 190 | * at the moment. 191 | * 192 | * TODO: work needed to consolidate the existing config stylesheet and 193 | * when the extension default stylesheet gets an update. 194 | */ 195 | patchCss() { 196 | if (this._needUpdate()) { 197 | let originalCss = this.configMgr.defaultStylesheetFile; 198 | let configCss = this.configMgr.stylesheetFile; 199 | let copyConfigCss = Gio.File.new_for_path(this.configMgr.stylesheetFileName + ".bak"); 200 | let backupFine = configCss.copy(copyConfigCss, Gio.FileCopyFlags.OVERWRITE, null, null); 201 | let copyFine = originalCss.copy(configCss, Gio.FileCopyFlags.OVERWRITE, null, null); 202 | if (backupFine && copyFine) { 203 | this.settings.set_uint("css-last-update", this.cssTag); 204 | return true; 205 | } 206 | } 207 | return false; 208 | } 209 | 210 | /** 211 | * Credits: ExtensionSystem.js:_callExtensionEnable() 212 | */ 213 | reloadStylesheet() { 214 | throw new Error("Must implement reloadStylesheet"); 215 | } 216 | 217 | _needUpdate() { 218 | let cssTag = this.cssTag; 219 | return this.settings.get_uint("css-last-update") !== cssTag; 220 | } 221 | } 222 | 223 | /** 224 | * Credits: Color Space conversion functions from CSS Tricks 225 | * https://css-tricks.com/converting-color-spaces-in-javascript/ 226 | */ 227 | export function RGBAToHexA(rgba) { 228 | let sep = rgba.indexOf(",") > -1 ? "," : " "; 229 | rgba = rgba.substr(5).split(")")[0].split(sep); 230 | 231 | // Strip the slash if using space-separated syntax 232 | if (rgba.indexOf("/") > -1) rgba.splice(3, 1); 233 | 234 | for (let R in rgba) { 235 | let r = rgba[R]; 236 | if (r.indexOf("%") > -1) { 237 | let p = r.substr(0, r.length - 1) / 100; 238 | 239 | if (R < 3) { 240 | rgba[R] = Math.round(p * 255); 241 | } else { 242 | rgba[R] = p; 243 | } 244 | } 245 | } 246 | let r = (+rgba[0]).toString(16), 247 | g = (+rgba[1]).toString(16), 248 | b = (+rgba[2]).toString(16), 249 | a = Math.round(+rgba[3] * 255).toString(16); 250 | 251 | if (r.length == 1) r = "0" + r; 252 | if (g.length == 1) g = "0" + g; 253 | if (b.length == 1) b = "0" + b; 254 | if (a.length == 1) a = "0" + a; 255 | 256 | return "#" + r + g + b + a; 257 | } 258 | 259 | export function hexAToRGBA(h) { 260 | let r = 0, 261 | g = 0, 262 | b = 0, 263 | a = 1; 264 | 265 | if (h.length == 5) { 266 | r = "0x" + h[1] + h[1]; 267 | g = "0x" + h[2] + h[2]; 268 | b = "0x" + h[3] + h[3]; 269 | a = "0x" + h[4] + h[4]; 270 | } else if (h.length == 9) { 271 | r = "0x" + h[1] + h[2]; 272 | g = "0x" + h[3] + h[4]; 273 | b = "0x" + h[5] + h[6]; 274 | a = "0x" + h[7] + h[8]; 275 | } 276 | a = +(a / 255).toFixed(3); 277 | 278 | return "rgba(" + +r + "," + +g + "," + +b + "," + a + ")"; 279 | } 280 | -------------------------------------------------------------------------------- /metadata.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Forge", 3 | "description": "Tiling and window manager for GNOME\n- Please report bugs/issues on https://github.com/forge-ext/forge/issues\n- Needs a new maintainer.", 4 | "gettext-domain": "forge", 5 | "uuid": "forge@jmmaranan.com", 6 | "settings-schema": "org.gnome.shell.extensions.forge", 7 | "shell-version": ["45", "46", "47", "48"], 8 | "session-modes": ["user", "unlock-dialog"], 9 | "url": "https://github.com/forge-ext/forge" 10 | } 11 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "forge", 3 | "version": "22.51.0", 4 | "description": "Forge - Tiling Extension for GNOME", 5 | "main": "extension.js", 6 | "scripts": { 7 | "test": "prettier --list-different \"./**/*.{js,jsx,ts,tsx,json}\"", 8 | "prepare": "husky install", 9 | "format": "prettier --write \"./**/*.{js,jsx,ts,tsx,json}\"" 10 | }, 11 | "repository": { 12 | "type": "git", 13 | "url": "git+https://github.com/forge-ext/forge.git" 14 | }, 15 | "keywords": [ 16 | "gnome", 17 | "forge", 18 | "tiling", 19 | "javascript", 20 | "gnome-shell" 21 | ], 22 | "author": "Jose Maranan", 23 | "contributors": [ 24 | "Benny Powers " 25 | ], 26 | "license": "GPL-3.0-or-later", 27 | "bugs": { 28 | "url": "https://github.com/forge-ext/forge/issues" 29 | }, 30 | "homepage": "https://github.com/forge-ext/forge#readme", 31 | "devDependencies": { 32 | "@girs/adw-1": "^1.3.3-3.2.0", 33 | "@girs/clutter-12": "^12.0.0-3.2.0", 34 | "@girs/gdk-4.0": "^4.0.0-3.2.0", 35 | "@girs/gio-2.0": "^2.76.1-3.2.0", 36 | "@girs/gjs": "^3.2.0", 37 | "@girs/gobject-2.0": "^2.76.1-3.2.0", 38 | "@girs/gtk-4.0": "^4.10.4-3.2.0", 39 | "@girs/meta-12": "^12.0.0-3.2.0", 40 | "@girs/st-12": "^12.0.0-3.2.0", 41 | "husky": "^8.0.1", 42 | "lint-staged": "^13.0.3", 43 | "prettier": "^2.7.1" 44 | }, 45 | "lint-staged": { 46 | "**/*": "prettier --write --ignore-unknown" 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /po/es.po: -------------------------------------------------------------------------------- 1 | # Forge Sample Translation 2 | # Fran Rodriguez , 2024. 3 | # 4 | msgid "" 5 | msgstr "" 6 | "Project-Id-Version: Forge\n" 7 | "Report-Msgid-Bugs-To: \n" 8 | "POT-Creation-Date: 2025-05-10 17:37+0100\n" 9 | "PO-Revision-Date: 2021-09-18 16:25-0400\n" 10 | "Last-Translator: Jose Maranan \n" 11 | "Language-Team: \n" 12 | "Language: Spanish\n" 13 | "MIME-Version: 1.0\n" 14 | "Content-Type: text/plain; charset=UTF-8\n" 15 | "Content-Transfer-Encoding: 8bit\n" 16 | 17 | #: lib/extension/indicator.js:47 lib/prefs/settings.js:54 18 | msgid "Tiling" 19 | msgstr "Mosaico" 20 | 21 | #: lib/extension/indicator.js:66 lib/prefs/settings.js:23 22 | msgid "Forge" 23 | msgstr "Forge" 24 | 25 | #: lib/extension/indicator.js:66 26 | msgid "Tiling Window Management" 27 | msgstr "Gestor de ventanas en mosaico" 28 | 29 | #: lib/extension/indicator.js:70 30 | msgid "Gaps Hidden when Single" 31 | msgstr "Espacios ocultos cuando hay una sola ventana" 32 | 33 | #: lib/extension/indicator.js:78 34 | msgid "Show Focus Hint Border" 35 | msgstr "Mostrar borde de sugerencia de enfoque" 36 | 37 | #: lib/extension/indicator.js:86 38 | msgid "Move Pointer with the Focus" 39 | msgstr "Mover el puntero con el enfoque" 40 | 41 | #: lib/extension/indicator.js:94 lib/extension/window.js:67 42 | msgid "Settings" 43 | msgstr "Configuración" 44 | 45 | #: lib/prefs/appearance.js:30 46 | #, fuzzy 47 | msgid "Tiled window" 48 | msgstr "Modo de mosaico en pestañas" 49 | 50 | #: lib/prefs/appearance.js:32 51 | #, fuzzy 52 | msgid "Tabbed window" 53 | msgstr "Modo de mosaico en pestañas" 54 | 55 | #: lib/prefs/appearance.js:34 56 | #, fuzzy 57 | msgid "Stacked window" 58 | msgstr "Modo de mosaico apilado" 59 | 60 | #: lib/prefs/appearance.js:36 61 | msgid "Floating window" 62 | msgstr "" 63 | 64 | #: lib/prefs/appearance.js:38 65 | #, fuzzy 66 | msgid "Split direction hint" 67 | msgstr "Sugerencia de dirección dividida" 68 | 69 | #: lib/prefs/appearance.js:43 70 | msgid "Appearance" 71 | msgstr "Apariencia" 72 | 73 | #: lib/prefs/appearance.js:48 74 | msgid "Gaps" 75 | msgstr "" 76 | 77 | #: lib/prefs/appearance.js:49 78 | msgid "Change the gap size between windows" 79 | msgstr "" 80 | 81 | #: lib/prefs/appearance.js:52 82 | msgid "Gap size" 83 | msgstr "" 84 | 85 | #: lib/prefs/appearance.js:58 86 | #, fuzzy 87 | msgid "Gap size multiplier" 88 | msgstr "Espacios" 89 | 90 | #: lib/prefs/appearance.js:64 91 | msgid "Disable gaps for single window" 92 | msgstr "" 93 | 94 | #: lib/prefs/appearance.js:65 95 | msgid "Disables window gaps when only a single window is present" 96 | msgstr "" 97 | 98 | #: lib/prefs/appearance.js:72 99 | msgid "Style" 100 | msgstr "" 101 | 102 | #: lib/prefs/appearance.js:73 103 | msgid "Change how the shell looks" 104 | msgstr "" 105 | 106 | #: lib/prefs/appearance.js:76 107 | #, fuzzy 108 | msgid "Preview hint" 109 | msgstr "Alternar vista previa de sugerencia" 110 | 111 | #: lib/prefs/appearance.js:77 112 | msgid "Shows where the window will be tiled when you let go of it" 113 | msgstr "" 114 | 115 | #: lib/prefs/appearance.js:83 116 | #, fuzzy 117 | msgid "Border around focused window" 118 | msgstr "Mostrar borde de dirección de división en la ventana enfocada" 119 | 120 | #: lib/prefs/appearance.js:84 121 | msgid "Display a colored border around the focused window" 122 | msgstr "Mostrar un borde de color alrededor de la ventana enfocada" 123 | 124 | #: lib/prefs/appearance.js:89 125 | #, fuzzy 126 | msgid "Window split hint border" 127 | msgstr "Mostrar borde de sugerencia de división de ventana" 128 | 129 | #: lib/prefs/appearance.js:90 130 | msgid "Show split direction border on focused window" 131 | msgstr "Mostrar borde de dirección de división en la ventana enfocada" 132 | 133 | #: lib/prefs/appearance.js:95 134 | #, fuzzy 135 | msgid "Forge in quick settings" 136 | msgstr "Mostrar ajustes rápidos de mosaico" 137 | 138 | #: lib/prefs/appearance.js:96 139 | #, fuzzy 140 | msgid "Toggles the Forge tile in quick settings" 141 | msgstr "Alternar la visualización de Forge en los ajustes rápidos" 142 | 143 | #: lib/prefs/appearance.js:104 144 | msgid "Color" 145 | msgstr "Color" 146 | 147 | #: lib/prefs/appearance.js:105 148 | #, fuzzy 149 | msgid "Changes the focused window's border and preview hint colors" 150 | msgstr "Mostrar borde de dirección de división en la ventana enfocada" 151 | 152 | #: lib/prefs/appearance.js:127 153 | #, fuzzy 154 | msgid "Border size" 155 | msgstr "Tamaño del borde" 156 | 157 | #: lib/prefs/appearance.js:199 158 | #, fuzzy 159 | msgid "Border color" 160 | msgstr "Tamaño del borde" 161 | 162 | #: lib/prefs/keyboard.js:19 163 | msgid "Keyboard" 164 | msgstr "Teclado" 165 | 166 | #: lib/prefs/keyboard.js:22 167 | #, fuzzy 168 | msgid "Drag-and-drop modifier key" 169 | msgstr "Opciones de tecla modificadora para mosaico con arrastrar y soltar" 170 | 171 | #: lib/prefs/keyboard.js:24 172 | #, fuzzy 173 | msgid "" 174 | "Change the modifier key for tiling windows via drag-and-drop. Select 'None' " 175 | "to always tile" 176 | msgstr "" 177 | "Cambia la tecla modificadora para organizar en mosaico las ventanas " 178 | "mediante el ratón/arrastrar y soltar" 179 | 180 | #: lib/prefs/keyboard.js:28 181 | #, fuzzy 182 | msgid "Modifier key" 183 | msgstr "Modificador de mosaico" 184 | 185 | #: lib/prefs/keyboard.js:32 186 | msgid "Super" 187 | msgstr "Super" 188 | 189 | #: lib/prefs/keyboard.js:33 190 | msgid "Ctrl" 191 | msgstr "Ctrl" 192 | 193 | #: lib/prefs/keyboard.js:34 194 | msgid "Alt" 195 | msgstr "Alt" 196 | 197 | #: lib/prefs/keyboard.js:35 198 | msgid "None" 199 | msgstr "Ninguno/a" 200 | 201 | #: lib/prefs/keyboard.js:41 202 | #, fuzzy 203 | msgid "Shortcuts" 204 | msgstr "Actualizar atajos" 205 | 206 | #: lib/prefs/keyboard.js:43 207 | msgid "" 208 | "Change the tiling shortcuts. To clear a shortcut clear the input field. To " 209 | "apply a shortcut press enter. Syntax examples" 211 | msgstr "" 212 | 213 | #: lib/prefs/settings.js:33 214 | msgid "translator-credits" 215 | msgstr "Créditos del traductor/a" 216 | 217 | #: lib/prefs/settings.js:41 218 | msgid "About" 219 | msgstr "Acerca de" 220 | 221 | #: lib/prefs/settings.js:56 222 | #, fuzzy 223 | msgid "Behavior" 224 | msgstr "Comportamiento" 225 | 226 | #: lib/prefs/settings.js:57 227 | msgid "Change how the tiling behaves" 228 | msgstr "" 229 | 230 | #: lib/prefs/settings.js:61 231 | #, fuzzy 232 | msgid "Focus on Hover" 233 | msgstr "Mostrar borde de sugerencia de enfoque" 234 | 235 | #: lib/prefs/settings.js:62 236 | msgid "Window focus follows the pointer" 237 | msgstr "" 238 | 239 | #: lib/prefs/settings.js:68 240 | #, fuzzy 241 | msgid "Move pointer with focused window" 242 | msgstr "Mover el puntero con el enfoque" 243 | 244 | #: lib/prefs/settings.js:69 245 | #, fuzzy 246 | msgid "Moves the pointer when focusing or swapping via keyboard" 247 | msgstr "Mover el puntero al enfocar o intercambiar mediante el teclado" 248 | 249 | #: lib/prefs/settings.js:75 250 | #, fuzzy 251 | msgid "Quarter tiling" 252 | msgstr "Mosaico en cuartos" 253 | 254 | #: lib/prefs/settings.js:76 255 | msgid "Places new windows in a clock-wise fashion" 256 | msgstr "" 257 | 258 | #: lib/prefs/settings.js:82 259 | #, fuzzy 260 | msgid "Stacked tiling" 261 | msgstr "Modo de mosaico apilado" 262 | 263 | #: lib/prefs/settings.js:83 264 | #, fuzzy 265 | msgid "Stacks windows on top of each other while still tiling them" 266 | msgstr "" 267 | "Apila las ventanas unas sobre otras mientras siguen organizadas en mosaico" 268 | 269 | #: lib/prefs/settings.js:89 270 | #, fuzzy 271 | msgid "Tabbed tiling" 272 | msgstr "Modo de mosaico en pestañas" 273 | 274 | #: lib/prefs/settings.js:90 275 | #, fuzzy 276 | msgid "Groups windows as tabs" 277 | msgstr "Agrupa las ventanas en mosaico como pestañas" 278 | 279 | #: lib/prefs/settings.js:96 280 | #, fuzzy 281 | msgid "Auto exit tabbed tiling" 282 | msgstr "Salir automáticamente del modo de mosaico en pestañas" 283 | 284 | #: lib/prefs/settings.js:97 285 | msgid "Exit tabbed tiling mode when only a single tab remains" 286 | msgstr "Salir del modo de mosaico en pestañas cuando solo queda una pestaña" 287 | 288 | #: lib/prefs/settings.js:103 289 | #, fuzzy 290 | msgid "Drag-and-drop behavior" 291 | msgstr "Distribución central predeterminada de arrastrar y soltar" 292 | 293 | #: lib/prefs/settings.js:104 294 | msgid "What to do when dragging one window on top of another" 295 | msgstr "" 296 | 297 | #: lib/prefs/settings.js:109 298 | msgid "Swap" 299 | msgstr "Intercambiar" 300 | 301 | #: lib/prefs/settings.js:110 302 | msgid "Tabbed" 303 | msgstr "En pestañas" 304 | 305 | #: lib/prefs/settings.js:111 306 | msgid "Stacked" 307 | msgstr "Apilado" 308 | 309 | #: lib/prefs/settings.js:115 310 | msgid "Always on Top mode for floating windows" 311 | msgstr "" 312 | 313 | #: lib/prefs/settings.js:116 314 | #, fuzzy 315 | msgid "Makes floating windows appear above tiled windows" 316 | msgstr "Ventanas flotantes siempre encima de las ventanas en mosaico" 317 | 318 | #: lib/prefs/settings.js:124 319 | msgid "Non-tiling workspaces" 320 | msgstr "" 321 | 322 | #: lib/prefs/settings.js:125 323 | msgid "" 324 | "Disables tiling on specified workspaces. Starts from 0, separated by commas" 325 | msgstr "" 326 | 327 | #: lib/prefs/settings.js:128 328 | msgid "Example: 0,1,2" 329 | msgstr "" 330 | 331 | #: lib/prefs/settings.js:136 332 | msgid "Logger" 333 | msgstr "Registro" 334 | 335 | #: lib/prefs/settings.js:139 336 | msgid "Logger Level" 337 | msgstr "Nivel de registro" 338 | 339 | #: lib/prefs/widgets.js:43 340 | msgid "" 341 | "CAUTION: Enabling this setting can lead to bugs or cause the shell to " 342 | "crash" 343 | msgstr "" 344 | "PRECAUCIÓN: Activar esta configuración puede provocar errores o hacer " 345 | "que el entorno se bloquee" 346 | 347 | #: lib/prefs/widgets.js:219 348 | #, fuzzy 349 | msgid "Clear shortcut" 350 | msgstr "Actualizar atajos" 351 | 352 | #: lib/prefs/widgets.js:234 353 | msgid "Reset to default" 354 | msgstr "" 355 | 356 | #~ msgid "Reset" 357 | #~ msgstr "Restablecer" 358 | 359 | #~ msgid "Workspace" 360 | #~ msgstr "Espacio de trabajo" 361 | 362 | #~ msgid "Update Workspace Settings" 363 | #~ msgstr "Actualizar ajustes del espacio de trabajo" 364 | 365 | #~ msgid "" 366 | #~ "Provide workspace indices to skip. E.g. 0,1. Empty text to disable. Enter " 367 | #~ "to accept" 368 | #~ msgstr "" 369 | #~ "Indica los índices de espacios de trabajo a omitir. Ej.: 0,1. Dejar vacío " 370 | #~ "para desactivar. Pulsa Intro para aceptar" 371 | 372 | #~ msgid "Skip Workspace Tiling" 373 | #~ msgstr "Omitir mosaico en espacio de trabajo" 374 | 375 | #, fuzzy 376 | #~ msgid "Show border around focused window" 377 | #~ msgstr "Mostrar borde de dirección de división en la ventana enfocada" 378 | 379 | #~ msgid "Legend" 380 | #~ msgstr "Leyenda" 381 | 382 | #~ msgid "Windows key" 383 | #~ msgstr "Tecla de Windows" 384 | 385 | #~ msgid "Control key" 386 | #~ msgstr "Tecla Control" 387 | 388 | #~ msgid "" 389 | #~ "Delete text to unset. Press Return key to accept. Focus out to ignore." 390 | #~ msgstr "" 391 | #~ "Borra el texto para desactivar. Pulsa la tecla Intro para aceptar. Sal " 392 | #~ "del enfoque para ignorar." 393 | 394 | #~ msgid "Resets" 395 | #~ msgstr "Restablecer" 396 | 397 | #~ msgid "to previous value when invalid" 398 | #~ msgstr "al valor anterior cuando es inválido" 399 | 400 | #, fuzzy 401 | #~ msgid "Border color around tabbed window" 402 | #~ msgstr "Mostrar borde de dirección de división en la ventana enfocada" 403 | 404 | #, fuzzy 405 | #~ msgid "Border color around stacked window" 406 | #~ msgstr "Mostrar borde de dirección de división en la ventana enfocada" 407 | 408 | #, fuzzy 409 | #~ msgid "Border color around floating window" 410 | #~ msgstr "Mostrar borde de dirección de división en la ventana enfocada" 411 | 412 | #, fuzzy 413 | #~ msgid "Change the border colors around focused window" 414 | #~ msgstr "Mostrar borde de dirección de división en la ventana enfocada" 415 | 416 | #, fuzzy 417 | #~ msgid "Stacked focus hint and preview" 418 | #~ msgstr "Sugerencia de enfoque apilado y vista previa" 419 | 420 | #, fuzzy 421 | #~ msgid "Tabbed focus hint and preview" 422 | #~ msgstr "Sugerencia de enfoque en pestañas y vista previa" 423 | 424 | #, fuzzy 425 | #~ msgid "Tiled focus hint and preview" 426 | #~ msgstr "Sugerencia de enfoque en mosaico y vista previa" 427 | 428 | #, fuzzy 429 | #~ msgid "Floated focus hint" 430 | #~ msgstr "Sugerencia de enfoque flotante" 431 | 432 | #~ msgid "Syntax" 433 | #~ msgstr "Sintaxis" 434 | 435 | #~ msgid "Auto Split" 436 | #~ msgstr "División automática" 437 | 438 | #, fuzzy 439 | #~ msgid "Behaviour" 440 | #~ msgstr "Comportamiento" 441 | 442 | #, fuzzy 443 | #~ msgid "Show tiling quick settings" 444 | #~ msgstr "Mostrar ajustes rápidos de mosaico" 445 | 446 | #, fuzzy 447 | #~ msgid "" 448 | #~ "\n" 449 | #~ "Select None to always tile immediately by default" 450 | #~ msgstr "" 451 | #~ "Selecciona Ninguna para organizar en mosaico inmediatamente " 452 | #~ "por defecto" 453 | 454 | #, fuzzy 455 | #~ msgid "Workspaces to skip" 456 | #~ msgstr "Espacio de trabajo" 457 | 458 | #~ msgid "Float Mode Always On Top" 459 | #~ msgstr "Modo flotante siempre visible" 460 | 461 | #~ msgid "Toggle Forge's high-level features" 462 | #~ msgstr "Alternar las funciones de alto nivel de Forge" 463 | 464 | #~ msgid "Preview Hint Toggle" 465 | #~ msgstr "Alternar vista previa de sugerencia" 466 | 467 | #~ msgid "Development in Progress..." 468 | #~ msgstr "Desarrollo en curso..." 469 | -------------------------------------------------------------------------------- /po/nl.po: -------------------------------------------------------------------------------- 1 | # Forge Sample Translation 2 | msgid "" 3 | msgstr "" 4 | "Project-Id-Version: Forge\n" 5 | "Report-Msgid-Bugs-To: \n" 6 | "POT-Creation-Date: 2025-05-10 17:37+0100\n" 7 | "PO-Revision-Date: 2021-12-29 19:04+0100\n" 8 | "Last-Translator: Heimen Stoffels \n" 9 | "Language-Team: \n" 10 | "Language: nl\n" 11 | "MIME-Version: 1.0\n" 12 | "Content-Type: text/plain; charset=UTF-8\n" 13 | "Content-Transfer-Encoding: 8bit\n" 14 | "Plural-Forms: nplurals=2; plural=(n != 1);\n" 15 | "X-Generator: Poedit 3.0\n" 16 | 17 | #: lib/extension/indicator.js:47 lib/prefs/settings.js:54 18 | msgid "Tiling" 19 | msgstr "" 20 | 21 | #: lib/extension/indicator.js:66 lib/prefs/settings.js:23 22 | msgid "Forge" 23 | msgstr "" 24 | 25 | #: lib/extension/indicator.js:66 26 | msgid "Tiling Window Management" 27 | msgstr "" 28 | 29 | #: lib/extension/indicator.js:70 30 | msgid "Gaps Hidden when Single" 31 | msgstr "Ruimtes verbergen bij één geopend venster" 32 | 33 | #: lib/extension/indicator.js:78 34 | #, fuzzy 35 | msgid "Show Focus Hint Border" 36 | msgstr "Focushint bij zwevende vensters" 37 | 38 | #: lib/extension/indicator.js:86 39 | msgid "Move Pointer with the Focus" 40 | msgstr "" 41 | 42 | #: lib/extension/indicator.js:94 lib/extension/window.js:67 43 | msgid "Settings" 44 | msgstr "Voorkeuren" 45 | 46 | #: lib/prefs/appearance.js:30 47 | #, fuzzy 48 | msgid "Tiled window" 49 | msgstr "Tabbladen" 50 | 51 | #: lib/prefs/appearance.js:32 52 | #, fuzzy 53 | msgid "Tabbed window" 54 | msgstr "Tabbladen" 55 | 56 | #: lib/prefs/appearance.js:34 57 | #, fuzzy 58 | msgid "Stacked window" 59 | msgstr "Gestapeld" 60 | 61 | #: lib/prefs/appearance.js:36 62 | msgid "Floating window" 63 | msgstr "" 64 | 65 | #: lib/prefs/appearance.js:38 66 | #, fuzzy 67 | msgid "Split direction hint" 68 | msgstr "Hint bij veranderen van richting" 69 | 70 | #: lib/prefs/appearance.js:43 71 | msgid "Appearance" 72 | msgstr "Vormgeving" 73 | 74 | #: lib/prefs/appearance.js:48 75 | msgid "Gaps" 76 | msgstr "Ruimtes" 77 | 78 | #: lib/prefs/appearance.js:49 79 | msgid "Change the gap size between windows" 80 | msgstr "" 81 | 82 | #: lib/prefs/appearance.js:52 83 | msgid "Gap size" 84 | msgstr "" 85 | 86 | #: lib/prefs/appearance.js:58 87 | #, fuzzy 88 | msgid "Gap size multiplier" 89 | msgstr "Ruimte tussen vensters" 90 | 91 | #: lib/prefs/appearance.js:64 92 | msgid "Disable gaps for single window" 93 | msgstr "" 94 | 95 | #: lib/prefs/appearance.js:65 96 | msgid "Disables window gaps when only a single window is present" 97 | msgstr "" 98 | 99 | #: lib/prefs/appearance.js:72 100 | msgid "Style" 101 | msgstr "" 102 | 103 | #: lib/prefs/appearance.js:73 104 | msgid "Change how the shell looks" 105 | msgstr "" 106 | 107 | #: lib/prefs/appearance.js:76 108 | msgid "Preview hint" 109 | msgstr "" 110 | 111 | #: lib/prefs/appearance.js:77 112 | msgid "Shows where the window will be tiled when you let go of it" 113 | msgstr "" 114 | 115 | #: lib/prefs/appearance.js:83 116 | msgid "Border around focused window" 117 | msgstr "" 118 | 119 | #: lib/prefs/appearance.js:84 120 | msgid "Display a colored border around the focused window" 121 | msgstr "" 122 | 123 | #: lib/prefs/appearance.js:89 124 | #, fuzzy 125 | msgid "Window split hint border" 126 | msgstr "Focushint bij zwevende vensters" 127 | 128 | #: lib/prefs/appearance.js:90 129 | msgid "Show split direction border on focused window" 130 | msgstr "" 131 | 132 | #: lib/prefs/appearance.js:95 133 | #, fuzzy 134 | msgid "Forge in quick settings" 135 | msgstr "Forge-paneelvoorkeuren" 136 | 137 | #: lib/prefs/appearance.js:96 138 | msgid "Toggles the Forge tile in quick settings" 139 | msgstr "" 140 | 141 | #: lib/prefs/appearance.js:104 142 | msgid "Color" 143 | msgstr "Kleur" 144 | 145 | #: lib/prefs/appearance.js:105 146 | msgid "Changes the focused window's border and preview hint colors" 147 | msgstr "" 148 | 149 | #: lib/prefs/appearance.js:127 150 | #, fuzzy 151 | msgid "Border size" 152 | msgstr "Randbreedte" 153 | 154 | #: lib/prefs/appearance.js:199 155 | #, fuzzy 156 | msgid "Border color" 157 | msgstr "Randkleur" 158 | 159 | #: lib/prefs/keyboard.js:19 160 | msgid "Keyboard" 161 | msgstr "Sneltoets" 162 | 163 | #: lib/prefs/keyboard.js:22 164 | #, fuzzy 165 | msgid "Drag-and-drop modifier key" 166 | msgstr "Opties omtrent actietoets voor tegelen middels slepen-en-neerzetten" 167 | 168 | #: lib/prefs/keyboard.js:24 169 | #, fuzzy 170 | msgid "" 171 | "Change the modifier key for tiling windows via drag-and-drop. Select 'None' " 172 | "to always tile" 173 | msgstr "" 174 | "Wijzig de actietoets voor het tegelen van vensters middels slepen-en-" 175 | "neerzetten" 176 | 177 | #: lib/prefs/keyboard.js:28 178 | #, fuzzy 179 | msgid "Modifier key" 180 | msgstr "Actietoetsen" 181 | 182 | #: lib/prefs/keyboard.js:32 183 | msgid "Super" 184 | msgstr "Super" 185 | 186 | #: lib/prefs/keyboard.js:33 187 | msgid "Ctrl" 188 | msgstr "Ctrl" 189 | 190 | #: lib/prefs/keyboard.js:34 191 | msgid "Alt" 192 | msgstr "Alt" 193 | 194 | #: lib/prefs/keyboard.js:35 195 | msgid "None" 196 | msgstr "Geen" 197 | 198 | #: lib/prefs/keyboard.js:41 199 | #, fuzzy 200 | msgid "Shortcuts" 201 | msgstr "Sneltoets" 202 | 203 | #: lib/prefs/keyboard.js:43 204 | msgid "" 205 | "Change the tiling shortcuts. To clear a shortcut clear the input field. To " 206 | "apply a shortcut press enter. Syntax examples" 208 | msgstr "" 209 | 210 | #: lib/prefs/settings.js:33 211 | msgid "translator-credits" 212 | msgstr "" 213 | 214 | #: lib/prefs/settings.js:41 215 | msgid "About" 216 | msgstr "Over" 217 | 218 | #: lib/prefs/settings.js:56 219 | msgid "Behavior" 220 | msgstr "" 221 | 222 | #: lib/prefs/settings.js:57 223 | msgid "Change how the tiling behaves" 224 | msgstr "" 225 | 226 | #: lib/prefs/settings.js:61 227 | #, fuzzy 228 | msgid "Focus on Hover" 229 | msgstr "Focushint bij zwevende vensters" 230 | 231 | #: lib/prefs/settings.js:62 232 | msgid "Window focus follows the pointer" 233 | msgstr "" 234 | 235 | #: lib/prefs/settings.js:68 236 | msgid "Move pointer with focused window" 237 | msgstr "" 238 | 239 | #: lib/prefs/settings.js:69 240 | msgid "Moves the pointer when focusing or swapping via keyboard" 241 | msgstr "" 242 | 243 | #: lib/prefs/settings.js:75 244 | #, fuzzy 245 | msgid "Quarter tiling" 246 | msgstr "Tabbladen" 247 | 248 | #: lib/prefs/settings.js:76 249 | msgid "Places new windows in a clock-wise fashion" 250 | msgstr "" 251 | 252 | #: lib/prefs/settings.js:82 253 | #, fuzzy 254 | msgid "Stacked tiling" 255 | msgstr "Gestapeld" 256 | 257 | #: lib/prefs/settings.js:83 258 | #, fuzzy 259 | msgid "Stacks windows on top of each other while still tiling them" 260 | msgstr "Gestapelde tegelmodus (stapel vensters in de tegelmodus)" 261 | 262 | #: lib/prefs/settings.js:89 263 | #, fuzzy 264 | msgid "Tabbed tiling" 265 | msgstr "Tabbladen" 266 | 267 | #: lib/prefs/settings.js:90 268 | #, fuzzy 269 | msgid "Groups windows as tabs" 270 | msgstr "Tabblad-tegelmodus (groepeer getegelde vensters als tabbladen)" 271 | 272 | #: lib/prefs/settings.js:96 273 | #, fuzzy 274 | msgid "Auto exit tabbed tiling" 275 | msgstr "Tabbladen" 276 | 277 | #: lib/prefs/settings.js:97 278 | msgid "Exit tabbed tiling mode when only a single tab remains" 279 | msgstr "" 280 | 281 | #: lib/prefs/settings.js:103 282 | #, fuzzy 283 | msgid "Drag-and-drop behavior" 284 | msgstr "Standaardindeling: gecentreerd slepen-en-neerzetten" 285 | 286 | #: lib/prefs/settings.js:104 287 | msgid "What to do when dragging one window on top of another" 288 | msgstr "" 289 | 290 | #: lib/prefs/settings.js:109 291 | msgid "Swap" 292 | msgstr "" 293 | 294 | #: lib/prefs/settings.js:110 295 | msgid "Tabbed" 296 | msgstr "Tabbladen" 297 | 298 | #: lib/prefs/settings.js:111 299 | msgid "Stacked" 300 | msgstr "Gestapeld" 301 | 302 | #: lib/prefs/settings.js:115 303 | msgid "Always on Top mode for floating windows" 304 | msgstr "" 305 | 306 | #: lib/prefs/settings.js:116 307 | msgid "Makes floating windows appear above tiled windows" 308 | msgstr "" 309 | 310 | #: lib/prefs/settings.js:124 311 | msgid "Non-tiling workspaces" 312 | msgstr "" 313 | 314 | #: lib/prefs/settings.js:125 315 | msgid "" 316 | "Disables tiling on specified workspaces. Starts from 0, separated by commas" 317 | msgstr "" 318 | 319 | #: lib/prefs/settings.js:128 320 | msgid "Example: 0,1,2" 321 | msgstr "" 322 | 323 | #: lib/prefs/settings.js:136 324 | #, fuzzy 325 | msgid "Logger" 326 | msgstr "Logniveau" 327 | 328 | #: lib/prefs/settings.js:139 329 | msgid "Logger Level" 330 | msgstr "Logniveau" 331 | 332 | #: lib/prefs/widgets.js:43 333 | #, fuzzy 334 | msgid "" 335 | "CAUTION: Enabling this setting can lead to bugs or cause the shell to " 336 | "crash" 337 | msgstr "" 338 | "LET OP: deze voorkeuren kunnen instabiliteit veroorzaken of zelfs de " 339 | "shell laten crashen!" 340 | 341 | #: lib/prefs/widgets.js:219 342 | #, fuzzy 343 | msgid "Clear shortcut" 344 | msgstr "Containersneltoetsen" 345 | 346 | #: lib/prefs/widgets.js:234 347 | msgid "Reset to default" 348 | msgstr "" 349 | 350 | #~ msgid "Reset" 351 | #~ msgstr "Standaardwaarden" 352 | 353 | #~ msgid "Workspace" 354 | #~ msgstr "Werkblad" 355 | 356 | #~ msgid "Update Workspace Settings" 357 | #~ msgstr "Werkbladvoorkeuren bijwerken" 358 | 359 | #~ msgid "" 360 | #~ "Provide workspace indices to skip. E.g. 0,1. Empty text to disable. Enter " 361 | #~ "to accept" 362 | #~ msgstr "" 363 | #~ "Geef op op welke werkbladen er niet dient te worden getegeld, bijv. 0,1. " 364 | #~ "Laat leeg om uit te schakelen. Druk Enter om toe te passen." 365 | 366 | #~ msgid "Skip Workspace Tiling" 367 | #~ msgstr "Niet tegelen op werkblad" 368 | 369 | #~ msgid "Legend" 370 | #~ msgstr "Legenda" 371 | 372 | #, fuzzy 373 | #~ msgid "Windows key" 374 | #~ msgstr "Windows-toets" 375 | 376 | #~ msgid "Control key" 377 | #~ msgstr "Ctrl-toets" 378 | 379 | #~ msgid "" 380 | #~ "Delete text to unset. Press Return key to accept. Focus out to ignore." 381 | #~ msgstr "" 382 | #~ "Wis de tekst om de toets wijzigen. Druk op enter om toe te passen. Klik " 383 | #~ "erbuiten om te negeren." 384 | 385 | #~ msgid "Resets" 386 | #~ msgstr "Herstelt" 387 | 388 | #~ msgid "to previous value when invalid" 389 | #~ msgstr "de standaardwaarde indien ongeldig" 390 | 391 | #, fuzzy 392 | #~ msgid "Stacked focus hint and preview" 393 | #~ msgstr "Hint bij gestapelde tegelfocus en voorvertoning" 394 | 395 | #, fuzzy 396 | #~ msgid "Tabbed focus hint and preview" 397 | #~ msgstr "Hint bij tabblad-tegelfocus en voorvertoning" 398 | 399 | #, fuzzy 400 | #~ msgid "Tiled focus hint and preview" 401 | #~ msgstr "Hint bij tegelfocus en voorvertoning" 402 | 403 | #, fuzzy 404 | #~ msgid "Floated focus hint" 405 | #~ msgstr "Focushint bij zwevende vensters" 406 | 407 | #~ msgid "Syntax" 408 | #~ msgstr "Syntaxis" 409 | 410 | #~ msgid "Tile Modifier" 411 | #~ msgstr "Tegel-actietoets" 412 | 413 | #~ msgid "Gaps Size" 414 | #~ msgstr "Ruimte tussen vensters" 415 | 416 | #, fuzzy 417 | #~ msgid "" 418 | #~ "\n" 419 | #~ "Select None to always tile immediately by default" 420 | #~ msgstr "Kies Geen om altijd direct te tegelen" 421 | 422 | #, fuzzy 423 | #~ msgid "Workspaces to skip" 424 | #~ msgstr "Werkblad" 425 | 426 | #, fuzzy 427 | #~ msgid "Update Shortcuts" 428 | #~ msgstr "Overige sneltoetsen" 429 | 430 | #~ msgid "Development in Progress..." 431 | #~ msgstr "De ontwikkeling is gaande…" 432 | 433 | #~ msgid "Development" 434 | #~ msgstr "Ontwikkeling" 435 | 436 | #~ msgid "Experimental" 437 | #~ msgstr "Experimenteel" 438 | 439 | #~ msgid "Home" 440 | #~ msgstr "Home" 441 | 442 | #~ msgid "Window" 443 | #~ msgstr "Venster" 444 | 445 | #, fuzzy 446 | #~ msgid "Borders" 447 | #~ msgstr "Randbreedte" 448 | 449 | #~ msgid "Palette Mode" 450 | #~ msgstr "Paletmodus" 451 | 452 | #~ msgid "Editor Mode" 453 | #~ msgstr "Bewerkmodus" 454 | 455 | #~ msgid "Apply Changes" 456 | #~ msgstr "Wijzigingen toepassen" 457 | 458 | #~ msgid "Layout" 459 | #~ msgstr "Indeling" 460 | 461 | #~ msgid "Window Shortcuts" 462 | #~ msgstr "Venstersneltoetsen" 463 | 464 | #~ msgid "Workspace Shortcuts" 465 | #~ msgstr "Werkbladsneltoetsen" 466 | 467 | #~ msgid "Focus Shortcuts" 468 | #~ msgstr "Focussneltoetsen" 469 | 470 | #~ msgid "Update Keybindings" 471 | #~ msgstr "Sneltoetsen bijwerken" 472 | 473 | #~ msgid "Action" 474 | #~ msgstr "Actie" 475 | 476 | #~ msgid "Notes" 477 | #~ msgstr "Opmerkingen" 478 | 479 | #~ msgid "Tile Mode" 480 | #~ msgstr "Tegelmodus" 481 | 482 | #~ msgid "Open Preferences" 483 | #~ msgstr "Voorkeuren openen" 484 | 485 | #~ msgid "Gaps Size Increments" 486 | #~ msgstr "Ruimteverhogingen" 487 | -------------------------------------------------------------------------------- /po/pt_BR.po: -------------------------------------------------------------------------------- 1 | # Forge Sample Translation 2 | msgid "" 3 | msgstr "" 4 | "Project-Id-Version: Forge\n" 5 | "Report-Msgid-Bugs-To: \n" 6 | "POT-Creation-Date: 2025-05-10 17:37+0100\n" 7 | "PO-Revision-Date: 2021-09-18 16:25-0400\n" 8 | "Last-Translator: Juarez Rudsatz \n" 9 | "Language-Team: \n" 10 | "Language: pt_BR\n" 11 | "MIME-Version: 1.0\n" 12 | "Content-Type: text/plain; charset=UTF-8\n" 13 | "Content-Transfer-Encoding: 8bit\n" 14 | 15 | #: lib/extension/indicator.js:47 lib/prefs/settings.js:54 16 | msgid "Tiling" 17 | msgstr "Emparelhamento" 18 | 19 | #: lib/extension/indicator.js:66 lib/prefs/settings.js:23 20 | msgid "Forge" 21 | msgstr "Forge" 22 | 23 | #: lib/extension/indicator.js:66 24 | msgid "Tiling Window Management" 25 | msgstr "Gerenciamento de Janelas Emparelhadas" 26 | 27 | #: lib/extension/indicator.js:70 28 | msgid "Gaps Hidden when Single" 29 | msgstr "Espaçamento Omitido quando Sozinho" 30 | 31 | #: lib/extension/indicator.js:78 32 | #, fuzzy 33 | msgid "Show Focus Hint Border" 34 | msgstr "Sinalizador de Foco Flutuante" 35 | 36 | #: lib/extension/indicator.js:86 37 | msgid "Move Pointer with the Focus" 38 | msgstr "Mover o Ponteiro com o Foco" 39 | 40 | #: lib/extension/indicator.js:94 lib/extension/window.js:67 41 | msgid "Settings" 42 | msgstr "Configurações" 43 | 44 | #: lib/prefs/appearance.js:30 45 | #, fuzzy 46 | msgid "Tiled window" 47 | msgstr "Modo em Abas" 48 | 49 | #: lib/prefs/appearance.js:32 50 | #, fuzzy 51 | msgid "Tabbed window" 52 | msgstr "Modo em Abas" 53 | 54 | #: lib/prefs/appearance.js:34 55 | #, fuzzy 56 | msgid "Stacked window" 57 | msgstr "Modo empilhado" 58 | 59 | #: lib/prefs/appearance.js:36 60 | msgid "Floating window" 61 | msgstr "" 62 | 63 | #: lib/prefs/appearance.js:38 64 | #, fuzzy 65 | msgid "Split direction hint" 66 | msgstr "Sinalizador de Separação de Direção" 67 | 68 | #: lib/prefs/appearance.js:43 69 | msgid "Appearance" 70 | msgstr "Aparência" 71 | 72 | #: lib/prefs/appearance.js:48 73 | msgid "Gaps" 74 | msgstr "Espaçamento" 75 | 76 | #: lib/prefs/appearance.js:49 77 | msgid "Change the gap size between windows" 78 | msgstr "" 79 | 80 | #: lib/prefs/appearance.js:52 81 | msgid "Gap size" 82 | msgstr "" 83 | 84 | #: lib/prefs/appearance.js:58 85 | #, fuzzy 86 | msgid "Gap size multiplier" 87 | msgstr "Tamanho do Espaçamento" 88 | 89 | #: lib/prefs/appearance.js:64 90 | msgid "Disable gaps for single window" 91 | msgstr "" 92 | 93 | #: lib/prefs/appearance.js:65 94 | msgid "Disables window gaps when only a single window is present" 95 | msgstr "" 96 | 97 | #: lib/prefs/appearance.js:72 98 | msgid "Style" 99 | msgstr "" 100 | 101 | #: lib/prefs/appearance.js:73 102 | msgid "Change how the shell looks" 103 | msgstr "" 104 | 105 | #: lib/prefs/appearance.js:76 106 | #, fuzzy 107 | msgid "Preview hint" 108 | msgstr "Habilitar Ver Dica" 109 | 110 | #: lib/prefs/appearance.js:77 111 | msgid "Shows where the window will be tiled when you let go of it" 112 | msgstr "" 113 | 114 | #: lib/prefs/appearance.js:83 115 | #, fuzzy 116 | msgid "Border around focused window" 117 | msgstr "Exibir a borda de divisão na janela com foco" 118 | 119 | #: lib/prefs/appearance.js:84 120 | msgid "Display a colored border around the focused window" 121 | msgstr "Exibir uma borda colorida ao redor da janela com foco" 122 | 123 | #: lib/prefs/appearance.js:89 124 | #, fuzzy 125 | msgid "Window split hint border" 126 | msgstr "Sinalizador de Foco Flutuante" 127 | 128 | #: lib/prefs/appearance.js:90 129 | msgid "Show split direction border on focused window" 130 | msgstr "Exibir a borda de divisão na janela com foco" 131 | 132 | #: lib/prefs/appearance.js:95 133 | #, fuzzy 134 | msgid "Forge in quick settings" 135 | msgstr "Exibir configurações rápidas" 136 | 137 | #: lib/prefs/appearance.js:96 138 | #, fuzzy 139 | msgid "Toggles the Forge tile in quick settings" 140 | msgstr "Habilitar a exibição do Forge nas configurações rápidas" 141 | 142 | #: lib/prefs/appearance.js:104 143 | msgid "Color" 144 | msgstr "Cor" 145 | 146 | #: lib/prefs/appearance.js:105 147 | #, fuzzy 148 | msgid "Changes the focused window's border and preview hint colors" 149 | msgstr "Exibir a borda de divisão na janela com foco" 150 | 151 | #: lib/prefs/appearance.js:127 152 | #, fuzzy 153 | msgid "Border size" 154 | msgstr "Tamanho de Borda" 155 | 156 | #: lib/prefs/appearance.js:199 157 | #, fuzzy 158 | msgid "Border color" 159 | msgstr "Cor da Borda" 160 | 161 | #: lib/prefs/keyboard.js:19 162 | msgid "Keyboard" 163 | msgstr "Teclado" 164 | 165 | #: lib/prefs/keyboard.js:22 166 | #, fuzzy 167 | msgid "Drag-and-drop modifier key" 168 | msgstr "Opções de Teclas Modificadoras de Arrastar/Soltar" 169 | 170 | #: lib/prefs/keyboard.js:24 171 | #, fuzzy 172 | msgid "" 173 | "Change the modifier key for tiling windows via drag-and-drop. Select 'None' " 174 | "to always tile" 175 | msgstr "" 176 | "Altere o modificador para janelas emparelhadas através do mouse e " 177 | "arrastar/soltar" 178 | 179 | #: lib/prefs/keyboard.js:28 180 | #, fuzzy 181 | msgid "Modifier key" 182 | msgstr "Teclas Modificadoras" 183 | 184 | #: lib/prefs/keyboard.js:32 185 | msgid "Super" 186 | msgstr "Super" 187 | 188 | #: lib/prefs/keyboard.js:33 189 | msgid "Ctrl" 190 | msgstr "Ctrl" 191 | 192 | #: lib/prefs/keyboard.js:34 193 | msgid "Alt" 194 | msgstr "Alt" 195 | 196 | #: lib/prefs/keyboard.js:35 197 | msgid "None" 198 | msgstr "Nenhum" 199 | 200 | #: lib/prefs/keyboard.js:41 201 | #, fuzzy 202 | msgid "Shortcuts" 203 | msgstr "Atalho" 204 | 205 | #: lib/prefs/keyboard.js:43 206 | msgid "" 207 | "Change the tiling shortcuts. To clear a shortcut clear the input field. To " 208 | "apply a shortcut press enter. Syntax examples" 210 | msgstr "" 211 | 212 | #: lib/prefs/settings.js:33 213 | msgid "translator-credits" 214 | msgstr "" 215 | 216 | #: lib/prefs/settings.js:41 217 | msgid "About" 218 | msgstr "Sobre" 219 | 220 | #: lib/prefs/settings.js:56 221 | #, fuzzy 222 | msgid "Behavior" 223 | msgstr "Comportamento" 224 | 225 | #: lib/prefs/settings.js:57 226 | msgid "Change how the tiling behaves" 227 | msgstr "" 228 | 229 | #: lib/prefs/settings.js:61 230 | #, fuzzy 231 | msgid "Focus on Hover" 232 | msgstr "Sinalizador de Foco Flutuante" 233 | 234 | #: lib/prefs/settings.js:62 235 | msgid "Window focus follows the pointer" 236 | msgstr "" 237 | 238 | #: lib/prefs/settings.js:68 239 | #, fuzzy 240 | msgid "Move pointer with focused window" 241 | msgstr "Mover o Ponteiro para Janela com Foco" 242 | 243 | #: lib/prefs/settings.js:69 244 | #, fuzzy 245 | msgid "Moves the pointer when focusing or swapping via keyboard" 246 | msgstr "Mover o Ponteiro ao focar ou transpor janelas via teclado" 247 | 248 | #: lib/prefs/settings.js:75 249 | #, fuzzy 250 | msgid "Quarter tiling" 251 | msgstr "Divisão Automática (Metade)" 252 | 253 | #: lib/prefs/settings.js:76 254 | msgid "Places new windows in a clock-wise fashion" 255 | msgstr "" 256 | 257 | #: lib/prefs/settings.js:82 258 | #, fuzzy 259 | msgid "Stacked tiling" 260 | msgstr "Modo empilhado" 261 | 262 | #: lib/prefs/settings.js:83 263 | #, fuzzy 264 | msgid "Stacks windows on top of each other while still tiling them" 265 | msgstr "" 266 | "Modo de Emparelhamento Empilhado (Empilhar janelas um em cima da outra mesmo " 267 | "ainda sendo emparelhadas)" 268 | 269 | #: lib/prefs/settings.js:89 270 | #, fuzzy 271 | msgid "Tabbed tiling" 272 | msgstr "Modo em Abas" 273 | 274 | #: lib/prefs/settings.js:90 275 | #, fuzzy 276 | msgid "Groups windows as tabs" 277 | msgstr "Modo de Emparelhamento em Abas (Agrupar janelas emparelhadas em abas)" 278 | 279 | #: lib/prefs/settings.js:96 280 | #, fuzzy 281 | msgid "Auto exit tabbed tiling" 282 | msgstr "Modo em Abas" 283 | 284 | #: lib/prefs/settings.js:97 285 | msgid "Exit tabbed tiling mode when only a single tab remains" 286 | msgstr "" 287 | 288 | #: lib/prefs/settings.js:103 289 | #, fuzzy 290 | msgid "Drag-and-drop behavior" 291 | msgstr "Arranjo de Arrastar/Soltar Padrão" 292 | 293 | #: lib/prefs/settings.js:104 294 | msgid "What to do when dragging one window on top of another" 295 | msgstr "" 296 | 297 | #: lib/prefs/settings.js:109 298 | msgid "Swap" 299 | msgstr "" 300 | 301 | #: lib/prefs/settings.js:110 302 | msgid "Tabbed" 303 | msgstr "Em Abas" 304 | 305 | #: lib/prefs/settings.js:111 306 | msgid "Stacked" 307 | msgstr "Empilhado" 308 | 309 | #: lib/prefs/settings.js:115 310 | msgid "Always on Top mode for floating windows" 311 | msgstr "" 312 | 313 | #: lib/prefs/settings.js:116 314 | #, fuzzy 315 | msgid "Makes floating windows appear above tiled windows" 316 | msgstr "" 317 | "Modo Flutuante Sempre no Topo (Janelas flutuantes sempre acima de janelas " 318 | "emparelhadas)" 319 | 320 | #: lib/prefs/settings.js:124 321 | msgid "Non-tiling workspaces" 322 | msgstr "" 323 | 324 | #: lib/prefs/settings.js:125 325 | msgid "" 326 | "Disables tiling on specified workspaces. Starts from 0, separated by commas" 327 | msgstr "" 328 | 329 | #: lib/prefs/settings.js:128 330 | msgid "Example: 0,1,2" 331 | msgstr "" 332 | 333 | #: lib/prefs/settings.js:136 334 | #, fuzzy 335 | msgid "Logger" 336 | msgstr "Nivel de Log" 337 | 338 | #: lib/prefs/settings.js:139 339 | msgid "Logger Level" 340 | msgstr "Nivel de Log" 341 | 342 | #: lib/prefs/widgets.js:43 343 | #, fuzzy 344 | msgid "" 345 | "CAUTION: Enabling this setting can lead to bugs or cause the shell to " 346 | "crash" 347 | msgstr "" 348 | "CUIDADO: Estas configurações, quando ativadas, são problemáticas ou podem " 349 | "causar a queda da interface" 350 | 351 | #: lib/prefs/widgets.js:219 352 | #, fuzzy 353 | msgid "Clear shortcut" 354 | msgstr "Atalhos de Container" 355 | 356 | #: lib/prefs/widgets.js:234 357 | msgid "Reset to default" 358 | msgstr "" 359 | 360 | #~ msgid "Reset" 361 | #~ msgstr "Restaurar" 362 | 363 | #~ msgid "Workspace" 364 | #~ msgstr "Área de Trabalho" 365 | 366 | #~ msgid "Update Workspace Settings" 367 | #~ msgstr "Atualizar Configurações de Área de Trabalho" 368 | 369 | #~ msgid "" 370 | #~ "Provide workspace indices to skip. E.g. 0,1. Empty text to disable. Enter " 371 | #~ "to accept" 372 | #~ msgstr "" 373 | #~ "Informe índices para ignorar. Ex: 0,1. Texto em branco para desabilitar. " 374 | #~ "Enter para aceitar" 375 | 376 | #~ msgid "Skip Workspace Tiling" 377 | #~ msgstr "Ignorar Emparelhamento de Área de Trabalho" 378 | 379 | #, fuzzy 380 | #~ msgid "Show border around focused window" 381 | #~ msgstr "Exibir a borda de divisão na janela com foco" 382 | 383 | #~ msgid "Legend" 384 | #~ msgstr "Legenda" 385 | 386 | #, fuzzy 387 | #~ msgid "Windows key" 388 | #~ msgstr "tecla Windows" 389 | 390 | #~ msgid "Control key" 391 | #~ msgstr "tecla Control" 392 | 393 | #~ msgid "" 394 | #~ "Delete text to unset. Press Return key to accept. Focus out to ignore." 395 | #~ msgstr "" 396 | #~ "Apague o texto para desativar. Pressione a tecla Enter para aceitar. Mude " 397 | #~ "o foco para ignorar." 398 | 399 | #~ msgid "Resets" 400 | #~ msgstr "Restaurar" 401 | 402 | #~ msgid "to previous value when invalid" 403 | #~ msgstr "para o valor anterior quando inválido" 404 | 405 | #, fuzzy 406 | #~ msgid "Border color around tabbed window" 407 | #~ msgstr "Exibir a borda de divisão na janela com foco" 408 | 409 | #, fuzzy 410 | #~ msgid "Border color around stacked window" 411 | #~ msgstr "Exibir a borda de divisão na janela com foco" 412 | 413 | #, fuzzy 414 | #~ msgid "Border color around floating window" 415 | #~ msgstr "Exibir a borda de divisão na janela com foco" 416 | 417 | #, fuzzy 418 | #~ msgid "Change the border colors around focused window" 419 | #~ msgstr "Exibir a borda de divisão na janela com foco" 420 | 421 | #, fuzzy 422 | #~ msgid "Stacked focus hint and preview" 423 | #~ msgstr "Sinalizador de Foco Empilhado e Pré-Visualização" 424 | 425 | #, fuzzy 426 | #~ msgid "Tabbed focus hint and preview" 427 | #~ msgstr "Sinalizador de Foco em Abas e Pré-Visualização" 428 | 429 | #, fuzzy 430 | #~ msgid "Tiled focus hint and preview" 431 | #~ msgstr "Sinalizador de Foco Emparelhado e Pré-Visualização" 432 | 433 | #, fuzzy 434 | #~ msgid "Floated focus hint" 435 | #~ msgstr "Sinalizador de Foco Flutuante" 436 | 437 | #~ msgid "Syntax" 438 | #~ msgstr "Sintaxe" 439 | 440 | #~ msgid "Tile Modifier" 441 | #~ msgstr "Modificador de Emparelhamento" 442 | 443 | #~ msgid "Auto Split" 444 | #~ msgstr "Dividir automaticamente" 445 | 446 | #, fuzzy 447 | #~ msgid "Behaviour" 448 | #~ msgstr "Comportamento" 449 | 450 | #, fuzzy 451 | #~ msgid "Show tiling quick settings" 452 | #~ msgstr "Exibir configurações rápidas" 453 | 454 | #~ msgid "Gaps Size" 455 | #~ msgstr "Tamanho do Espaçamento" 456 | 457 | #, fuzzy 458 | #~ msgid "" 459 | #~ "\n" 460 | #~ "Select None to always tile immediately by default" 461 | #~ msgstr "" 462 | #~ "Selecione Nenhum para sempre emparelhar imediatamente por " 463 | #~ "padrão" 464 | 465 | #, fuzzy 466 | #~ msgid "Workspaces to skip" 467 | #~ msgstr "Área de Trabalho" 468 | 469 | #~ msgid "Float Mode Always On Top" 470 | #~ msgstr "Modo Flutuante sempre no topo" 471 | 472 | #~ msgid "Toggle Forge's high-level features" 473 | #~ msgstr "Habilitar as funcionalidade principais" 474 | 475 | #, fuzzy 476 | #~ msgid "Update Shortcuts" 477 | #~ msgstr "Outros Atalhos" 478 | 479 | #~ msgid "Preview Hint Toggle" 480 | #~ msgstr "Habilitar Ver Dica" 481 | 482 | #~ msgid "Development in Progress..." 483 | #~ msgstr "Desenvolvimento em andamento..." 484 | 485 | #~ msgid "Development" 486 | #~ msgstr "Desenvolvimento" 487 | 488 | #~ msgid "Experimental" 489 | #~ msgstr "Experimental" 490 | 491 | #~ msgid "Home" 492 | #~ msgstr "Início" 493 | 494 | #~ msgid "Window" 495 | #~ msgstr "Janela" 496 | 497 | #, fuzzy 498 | #~ msgid "Borders" 499 | #~ msgstr "Tamanho de Borda" 500 | 501 | #~ msgid "Palette Mode" 502 | #~ msgstr "Modo de Paleta" 503 | 504 | #~ msgid "Editor Mode" 505 | #~ msgstr "Modo de Edição" 506 | 507 | #~ msgid "Apply Changes" 508 | #~ msgstr "Aplicar Mudanças" 509 | 510 | #~ msgid "Layout" 511 | #~ msgstr "Arranjo" 512 | 513 | #~ msgid "Window Shortcuts" 514 | #~ msgstr "Atalhos de Janela" 515 | 516 | #~ msgid "Workspace Shortcuts" 517 | #~ msgstr "Atalhos de Área de Trabalho" 518 | 519 | #~ msgid "Focus Shortcuts" 520 | #~ msgstr "Atalhos de Foco" 521 | 522 | #~ msgid "Update Keybindings" 523 | #~ msgstr "Atualizar Atalhos" 524 | 525 | #~ msgid "Action" 526 | #~ msgstr "Ação" 527 | 528 | #~ msgid "Notes" 529 | #~ msgstr "Nota" 530 | 531 | #~ msgid "Forge Panel Settings" 532 | #~ msgstr "Painel de Configuração do Forge" 533 | 534 | #~ msgid "Tile Mode" 535 | #~ msgstr "Modo de Emparelhamento" 536 | 537 | #~ msgid "Open Preferences" 538 | #~ msgstr "Abrir Preferências" 539 | 540 | #~ msgid "Gaps Size Increments" 541 | #~ msgstr "Incremento no Tamanho do Espaçamento" 542 | -------------------------------------------------------------------------------- /prefs.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the Forge GNOME extension 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | * 17 | */ 18 | 19 | // Gnome imports 20 | import Gdk from "gi://Gdk"; 21 | import Gtk from "gi://Gtk"; 22 | 23 | import { ExtensionPreferences } from "resource:///org/gnome/Shell/Extensions/js/extensions/prefs.js"; 24 | 25 | import { KeyboardPage } from "./lib/prefs/keyboard.js"; 26 | import { AppearancePage } from "./lib/prefs/appearance.js"; 27 | import { SettingsPage } from "./lib/prefs/settings.js"; 28 | 29 | export default class ForgeExtensionPreferences extends ExtensionPreferences { 30 | settings = this.getSettings(); 31 | 32 | kbdSettings = this.getSettings("org.gnome.shell.extensions.forge.keybindings"); 33 | 34 | constructor(...args) { 35 | super(...args); 36 | const iconPath = this.dir.get_child("resources").get_child("icons").get_path(); 37 | const iconTheme = Gtk.IconTheme.get_for_display(Gdk.Display.get_default()); 38 | iconTheme.add_search_path(iconPath); 39 | } 40 | 41 | fillPreferencesWindow(window) { 42 | this.window = window; 43 | window._settings = this.settings; 44 | window._kbdSettings = this.kbdSettings; 45 | window.add(new SettingsPage(this)); 46 | window.add(new AppearancePage(this)); 47 | window.add(new KeyboardPage(this)); 48 | window.search_enabled = true; 49 | window.can_navigate_back = true; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /resources/icons/hicolor/scalable/actions/applications-science-symbolic.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /resources/icons/hicolor/scalable/actions/appointment-soon-symbolic.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /resources/icons/hicolor/scalable/actions/brush-symbolic.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /resources/icons/hicolor/scalable/actions/bug-symbolic.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /resources/icons/hicolor/scalable/actions/code-context-symbolic.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /resources/icons/hicolor/scalable/actions/color-picker-symbolic.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /resources/icons/hicolor/scalable/actions/color-select-symbolic.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /resources/icons/hicolor/scalable/actions/colorfx-symbolic.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /resources/icons/hicolor/scalable/actions/forge-logo-symbolic.svg: -------------------------------------------------------------------------------- 1 | 2 | 14 | 32 | 35 | 36 | 38 | 43 | 44 | 49 | 50 | -------------------------------------------------------------------------------- /resources/icons/hicolor/scalable/actions/go-home-symbolic.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /resources/icons/hicolor/scalable/actions/go-next-symbolic.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /resources/icons/hicolor/scalable/actions/input-keyboard-symbolic.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /resources/icons/hicolor/scalable/actions/larger-brush-symbolic.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /resources/icons/hicolor/scalable/actions/preferences-desktop-apps-symbolic.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /resources/icons/hicolor/scalable/actions/preferences-desktop-keyboard-symbolic.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /resources/icons/hicolor/scalable/actions/preferences-desktop-wallpaper-symbolic.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /resources/icons/hicolor/scalable/actions/settings-symbolic.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /resources/icons/hicolor/scalable/actions/shell-overview-symbolic.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /resources/icons/hicolor/scalable/actions/tab-new-symbolic.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /resources/icons/hicolor/scalable/actions/tool-brush-symbolic.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /resources/icons/hicolor/scalable/actions/tool-rectangle-symbolic.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /resources/icons/hicolor/scalable/actions/utilities-tweak-tool-symbolic.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | image/svg+xml 7 | 8 | Pop Symbolic Icon Theme 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | Pop Symbolic Icon Theme 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /resources/icons/hicolor/scalable/actions/view-grid-symbolic.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /resources/icons/hicolor/scalable/actions/window-symbolic.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /resources/icons/hicolor/scalable/panel/focus-windows-symbolic.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /resources/icons/hicolor/scalable/panel/view-dual-symbolic.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /resources/icons/hicolor/scalable/panel/window-duplicate-symbolic.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /schemas/org.gnome.shell.extensions.forge.gschema.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | true 7 | Show window border on focused window 8 | 9 | 10 | 11 | 3 12 | The focused border's current thickness 13 | 14 | 15 | 16 | 'rgba(236, 94, 94, 1)' 17 | The focused border's current color 18 | 19 | 20 | 21 | true 22 | Show split direction border on focused window 23 | 24 | 25 | 26 | 'rgba(255, 246, 108, 1)' 27 | The focused border's current color 28 | 29 | 30 | 31 | 4 32 | 33 | The size of the gap between windows in the workarea 34 | 35 | 36 | 37 | 38 | 15 39 | 40 | The size of the gap between windows in the workarea 41 | 42 | 43 | 44 | 45 | 1 46 | 47 | The size increment of the gaps. size-increment * gap-size 48 | 49 | 50 | 51 | 52 | false 53 | Hide gap when single window toggle 54 | 55 | 56 | 57 | 'tiling' 58 | Layout modes: stacking, tiling 59 | 60 | 61 | 62 | true 63 | Tiling mode on/off 64 | 65 | 66 | 67 | true 68 | Forge quick settings toggle 69 | 70 | 71 | 72 | '' 73 | Skips tiling on the provided workspace indices 74 | 75 | 76 | 77 | true 78 | Stacked tiling mode on/off 79 | 80 | 81 | 82 | true 83 | Tabbed tiling mode on/off 84 | 85 | 86 | 87 | 88 | 0 89 | 90 | Provide log4j-like logging levels: 91 | 0 - OFF 92 | 1 - FATAL 93 | 2 - ERROR 94 | 3 - WARN 95 | 4 - INFO 96 | 5 - DEBUG 97 | 6 - TRACE 98 | 7 - ALL 99 | 100 | 101 | 102 | false 103 | Enable logging 104 | 105 | 106 | 107 | '' 108 | Timestamp when css is last updated. Also triggers reload of St 109 | 110 | 111 | 112 | 1 113 | 114 | 115 | 116 | 'tabbed' 117 | 118 | The default center layout when dragging/dropping a window. 119 | The values are `tabbed` and `stacked`. 120 | 121 | 122 | 123 | false 124 | Move the pointer when focusing or swapping via kbd 125 | 126 | 127 | false 128 | Focus switches to the window under the pointer. 129 | 130 | 131 | true 132 | Floating windows toggle always-on-top 133 | 134 | 135 | true 136 | Exit tabbed tiling mode when only a single tab remains 137 | 138 | 139 | true 140 | Enable auto split or quarter-tiling based based on smaller side 141 | 142 | 143 | true 144 | Whether to show preview hint DND 145 | 146 | 147 | true 148 | Whether to show the tab decoration or not 149 | 150 | 151 | 152 | 153 | 154 | 155 | x']]]> 156 | Toggle the focused window's border 157 | 158 | 159 | 160 | plus']]]> 161 | Increase the size of gaps between windows/cons 162 | 163 | 164 | 165 | minus']]]> 166 | Decrease the size of gaps between windows/cons 167 | 168 | 169 | 170 | g']]]> 171 | Toggle split layout: horizontal or vertical 172 | 173 | 174 | 175 | z']]]> 176 | Split a container horizontally 177 | 178 | 179 | 180 | v']]]> 181 | Split a container vertically 182 | 183 | 184 | 185 | s']]]> 186 | Toggle stacked layout 187 | 188 | 189 | 190 | t']]]> 191 | Toggle tabbed layout 192 | 193 | 194 | 195 | y']]]> 196 | Toggle showing the tab decoration layout 197 | 198 | 199 | 200 | h']]]> 201 | Swap window left 202 | 203 | 204 | 205 | j']]]> 206 | Swap window down 207 | 208 | 209 | 210 | k']]]> 211 | Swap window up 212 | 213 | 214 | 215 | l']]]> 216 | Swap window right 217 | 218 | 219 | 220 | h']]]> 221 | Move window left 222 | 223 | 224 | 225 | j']]]> 226 | Move window down 227 | 228 | 229 | 230 | k']]]> 231 | Move window up 232 | 233 | 234 | 235 | l']]]> 236 | Move window right 237 | 238 | 239 | 240 | h']]]> 241 | Move window left 242 | 243 | 244 | 245 | j']]]> 246 | Move window down 247 | 248 | 249 | 250 | k']]]> 251 | Move window up 252 | 253 | 254 | 255 | l']]]> 256 | Move window right 257 | 258 | 259 | 260 | c']]]> 261 | Toggle window float 262 | 263 | 264 | 265 | c']]]> 266 | 267 | 268 | 269 | 270 | w']]]> 271 | Toggle active workspace tiling 272 | 273 | 274 | 275 | Period']]]> 276 | Open the extension's preferences 277 | 278 | 279 | 280 | w']]]> 281 | Toggle tiling mode on/off 282 | 283 | 284 | 285 | 'None' 286 | 287 | Mod mask to allow window tiling via drag-drop. None means it will tile immediately. 288 | Options are Super, Ctrl, Shift, Alt, and None 289 | 290 | 291 | 292 | 293 | Return']]]> 294 | Swap the last active window with the current one 295 | 296 | 297 | 298 | g']]]> 299 | 300 | 301 | 302 | t']]]> 303 | 304 | 305 | 306 | d']]]> 307 | 308 | 309 | 310 | e']]]> 311 | 312 | 313 | 314 | c']]]> 315 | 316 | 317 | 318 | y']]]> 319 | 320 | 321 | 322 | o']]]> 323 | 324 | 325 | 326 | u']]]> 327 | 328 | 329 | 330 | i']]]> 331 | 332 | 333 | 334 | i']]]> 335 | 336 | 337 | 338 | u']]]> 339 | 340 | 341 | 342 | o']]]> 343 | 344 | 345 | 346 | y']]]> 347 | 348 | 349 | 350 | -------------------------------------------------------------------------------- /stylesheet.css: -------------------------------------------------------------------------------- 1 | .tiled { 2 | color: rgba(236, 94, 94, 1); 3 | opacity: 1; 4 | border-width: 3px; 5 | } 6 | 7 | .split { 8 | color: rgba(255, 246, 108, 1); 9 | opacity: 1; 10 | border-width: 3px; 11 | } 12 | 13 | .stacked { 14 | color: rgba(247, 162, 43, 1); 15 | opacity: 1; 16 | border-width: 3px; 17 | } 18 | 19 | .tabbed { 20 | color: rgba(17, 199, 224, 1); 21 | opacity: 1; 22 | border-width: 3px; 23 | } 24 | 25 | .floated { 26 | color: rgba(180, 167, 214, 1); 27 | border-width: 3px; 28 | opacity: 1; 29 | } 30 | 31 | .window-tiled-border { 32 | border-width: 3px; 33 | border-color: rgba(236, 94, 94, 1); 34 | border-style: solid; 35 | border-radius: 14px; 36 | } 37 | 38 | .window-split-border { 39 | border-width: 3px; 40 | border-color: rgba(255, 246, 108, 1); 41 | border-style: solid; 42 | border-radius: 14px; 43 | } 44 | 45 | .window-split-horizontal { 46 | border-left-width: 0; 47 | border-top-width: 0; 48 | border-bottom-width: 0; 49 | } 50 | 51 | .window-split-vertical { 52 | border-left-width: 0; 53 | border-top-width: 0; 54 | border-right-width: 0; 55 | } 56 | 57 | .window-stacked-border { 58 | border-width: 3px; 59 | border-color: rgba(247, 162, 43, 1); 60 | border-style: solid; 61 | border-radius: 14px; 62 | } 63 | 64 | .window-tabbed-border { 65 | border-width: 3px; 66 | border-color: rgba(17, 199, 224, 1); 67 | border-style: solid; 68 | border-radius: 14px; 69 | } 70 | 71 | .window-tabbed-bg { 72 | border-radius: 8px; 73 | } 74 | 75 | .window-tabbed-tab { 76 | background-color: rgba(54, 47, 45, 1); 77 | border-color: rgba(17, 199, 224, 0.6); 78 | border-width: 1px; 79 | border-radius: 8px; 80 | color: white; 81 | margin: 1px; 82 | box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.2); 83 | } 84 | 85 | .window-tabbed-tab-active { 86 | background-color: rgba(17, 199, 224, 1); 87 | color: black; 88 | box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.2); 89 | } 90 | 91 | .window-tabbed-tab-close { 92 | padding: 3px; 93 | margin: 4px; 94 | border-radius: 16px; 95 | width: 16px; 96 | background-color: #e06666; 97 | } 98 | 99 | .window-tabbed-tab-icon { 100 | margin: 3px; 101 | } 102 | 103 | .window-floated-border { 104 | border-width: 3px; 105 | border-color: rgba(180, 167, 214, 1); 106 | border-style: solid; 107 | border-radius: 14px; 108 | } 109 | 110 | .window-tilepreview-tiled { 111 | border-width: 1px; 112 | border-color: rgba(236, 94, 94, 0.4); 113 | border-style: solid; 114 | border-radius: 14px; 115 | background-color: rgba(236, 94, 94, 0.3); 116 | } 117 | 118 | .window-tilepreview-stacked { 119 | border-width: 1px; 120 | border-color: rgba(247, 162, 43, 0.4); 121 | border-style: solid; 122 | border-radius: 14px; 123 | background-color: rgba(247, 162, 43, 0.3); 124 | } 125 | 126 | .window-tilepreview-swap { 127 | border-width: 1px; 128 | border-color: rgba(162, 247, 43, 0.4); 129 | border-style: solid; 130 | border-radius: 14px; 131 | background-color: rgba(162, 247, 43, 0.4); 132 | } 133 | 134 | .window-tilepreview-tabbed { 135 | border-width: 1px; 136 | border-color: rgba(18, 199, 224, 0.4); 137 | border-style: solid; 138 | border-radius: 14px; 139 | background-color: rgba(17, 199, 224, 0.3); 140 | } 141 | -------------------------------------------------------------------------------- /templates/README.md: -------------------------------------------------------------------------------- 1 | # Templates 2 | 3 | Collection of files for development and runtime 4 | -------------------------------------------------------------------------------- /templates/new-module.js.txt: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the Forge extension for GNOME 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | * 17 | */ 18 | 19 | // Gnome imports 20 | import GLib from 'gi://GLib'; 21 | import GObject from 'gi://GObject'; 22 | import Meta from 'gi://Meta'; 23 | import Shell from 'gi://Shell'; 24 | import St from 'gi://St'; 25 | 26 | // Application imports 27 | import * as Logger from './logger.js'; 28 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "include": [ 3 | "lib/**/*", 4 | "extension.js", 5 | "prefs.js", 6 | "./node_modules/@girs/adw-1", 7 | "./node_modules/@girs/clutter-12", 8 | "./node_modules/@girs/gio-2.0", 9 | "./node_modules/@girs/gjs", 10 | "./node_modules/@girs/glib-2.0", 11 | "./node_modules/@girs/gobject-2.0", 12 | "./node_modules/@girs/gtk-4.0", 13 | "./node_modules/@girs/meta-12", 14 | "./node_modules/@girs/st-12" 15 | ], 16 | "compilerOptions": { 17 | "allowJs": true, 18 | "checkJs": true, 19 | "moduleResolution": "NodeNext", 20 | "lib": ["ES2022"], 21 | "target": "ES2022" 22 | } 23 | } 24 | --------------------------------------------------------------------------------