├── .gitignore ├── .gitlab-ci.yml ├── AUTHORS ├── LICENSES ├── CC0-1.0 └── GPL-3.0 ├── NEWS ├── README.md ├── data ├── meson.build ├── org.gnome.TweaksDevel.svg ├── org.gnome.tweaks-symbolic.svg ├── org.gnome.tweaks.appdata.xml.in ├── org.gnome.tweaks.desktop.in ├── org.gnome.tweaks.gschema.xml ├── org.gnome.tweaks.svg ├── screenshot-appearance.png ├── screenshot-fonts.png ├── screenshot-mouse-touchpad.png ├── screenshot-windows.png ├── shell.css ├── shell.ui └── tweaks.ui ├── gnome-tweaks ├── gnome-tweaks.doap ├── gtweak ├── __init__.py ├── app.py ├── defs.py.in ├── devicemanager.py ├── gsettings.py ├── gshellwrapper.py ├── gtksettings.py ├── meson.build ├── tweakmodel.py ├── tweaks │ ├── __init__.py │ ├── tweak_group_appearance.py │ ├── tweak_group_font.py │ ├── tweak_group_keyboard.py │ ├── tweak_group_mouse.py │ ├── tweak_group_sound.py │ ├── tweak_group_startup.py │ └── tweak_group_windows.py ├── tweakview.py ├── utils.py └── widgets.py ├── logo.png ├── meson-postinstall.py ├── meson.build ├── meson_options.txt ├── org.gnome.tweaks.json └── po ├── LINGUAS ├── POTFILES.in ├── ab.po ├── af.po ├── ar.po ├── as.po ├── be.po ├── bg.po ├── bs.po ├── ca.po ├── ca@valencia.po ├── ckb.po ├── cs.po ├── da.po ├── de.po ├── el.po ├── en_GB.po ├── eo.po ├── es.po ├── eu.po ├── fa.po ├── fi.po ├── fr.po ├── fur.po ├── gl.po ├── he.po ├── hi.po ├── hr.po ├── hu.po ├── id.po ├── ie.po ├── is.po ├── it.po ├── ja.po ├── ka.po ├── kk.po ├── ko.po ├── ky.po ├── lt.po ├── lv.po ├── meson.build ├── mjw.po ├── ml.po ├── ms.po ├── my.po ├── nb.po ├── ne.po ├── nl.po ├── oc.po ├── pa.po ├── pl.po ├── pt.po ├── pt_BR.po ├── ro.po ├── ru.po ├── sk.po ├── sl.po ├── sr.po ├── sr@latin.po ├── sv.po ├── te.po ├── tg.po ├── th.po ├── tr.po ├── uk.po ├── vi.po ├── zh_CN.po ├── zh_HK.po └── zh_TW.po /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | *.pyc 3 | ABOUT-NLS 4 | Makefile 5 | Makefile.in 6 | aclocal.m4 7 | autom4te.cache/ 8 | config.* 9 | configure 10 | gtweak/defs.py 11 | install-sh 12 | missing 13 | py-compile 14 | *.gmo 15 | po/*.header 16 | po/*.sed 17 | po/*.sin 18 | po/.* 19 | po/Makefile.in.in 20 | po/Makevars.template 21 | po/POTFILES 22 | po/Rules-quot 23 | po/stamp-it 24 | compile 25 | m4/ 26 | data/org.gnome.tweaks.appdata.xml 27 | data/org.gnome.tweaks.desktop 28 | -------------------------------------------------------------------------------- /.gitlab-ci.yml: -------------------------------------------------------------------------------- 1 | stages: 2 | - check 3 | - build 4 | - deploy 5 | 6 | include: 7 | - project: 'gnome/citemplates' 8 | file: 'flatpak/flatpak_ci_initiative.yml' 9 | - component: gitlab.gnome.org/GNOME/citemplates/release-service@master 10 | inputs: 11 | dist-job-name: "flatpak_x86_64" 12 | tarball-artifact-path: "${TARBALL_ARTIFACT_PATH}" 13 | 14 | variables: 15 | BUNDLE: "org.gnome.tweaks.flatpak" 16 | MANIFEST_PATH: "org.gnome.tweaks.json" 17 | FLATPAK_MODULE: "gnome-tweaks" 18 | APP_ID: "org.gnome.tweaks" 19 | RUNTIME_REPO: "https://nightly.gnome.org/gnome-nightly.flatpakrepo" 20 | TARBALL_ARTIFACT_PATH: ".flatpak-builder/build/${FLATPAK_MODULE}/_flatpak_build/meson-dist/${CI_PROJECT_NAME}-${CI_COMMIT_TAG}.tar.xz" 21 | 22 | flatpak_x86_64: 23 | extends: .flatpak@x86_64 24 | stage: build 25 | 26 | flatpak_aarch64: 27 | extends: .flatpak@aarch64 28 | stage: build 29 | allow_failure: true 30 | 31 | publish-nightly@x86_64: 32 | extends: '.publish_nightly' 33 | needs: ['flatpak_x86_64'] 34 | 35 | publish-nightly@aarch64: 36 | extends: '.publish_nightly' 37 | needs: ['flatpak_aarch64'] 38 | allow_failure: true 39 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | John Stowers 2 | 3 | ---- 4 | 5 | data/welcome.png was created by Sofie van Schadewijk 6 | (Creative Commons Attribution 3.0 Unported license) 7 | -------------------------------------------------------------------------------- /LICENSES/CC0-1.0: -------------------------------------------------------------------------------- 1 | Creative Commons Legal Code 2 | 3 | CC0 1.0 Universal 4 | 5 | CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE 6 | LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN 7 | ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS 8 | INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES 9 | REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS 10 | PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM 11 | THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED 12 | HEREUNDER. 13 | 14 | Statement of Purpose 15 | 16 | The laws of most jurisdictions throughout the world automatically confer 17 | exclusive Copyright and Related Rights (defined below) upon the creator 18 | and subsequent owner(s) (each and all, an "owner") of an original work of 19 | authorship and/or a database (each, a "Work"). 20 | 21 | Certain owners wish to permanently relinquish those rights to a Work for 22 | the purpose of contributing to a commons of creative, cultural and 23 | scientific works ("Commons") that the public can reliably and without fear 24 | of later claims of infringement build upon, modify, incorporate in other 25 | works, reuse and redistribute as freely as possible in any form whatsoever 26 | and for any purposes, including without limitation commercial purposes. 27 | These owners may contribute to the Commons to promote the ideal of a free 28 | culture and the further production of creative, cultural and scientific 29 | works, or to gain reputation or greater distribution for their Work in 30 | part through the use and efforts of others. 31 | 32 | For these and/or other purposes and motivations, and without any 33 | expectation of additional consideration or compensation, the person 34 | associating CC0 with a Work (the "Affirmer"), to the extent that he or she 35 | is an owner of Copyright and Related Rights in the Work, voluntarily 36 | elects to apply CC0 to the Work and publicly distribute the Work under its 37 | terms, with knowledge of his or her Copyright and Related Rights in the 38 | Work and the meaning and intended legal effect of CC0 on those rights. 39 | 40 | 1. Copyright and Related Rights. A Work made available under CC0 may be 41 | protected by copyright and related or neighboring rights ("Copyright and 42 | Related Rights"). Copyright and Related Rights include, but are not 43 | limited to, the following: 44 | 45 | i. the right to reproduce, adapt, distribute, perform, display, 46 | communicate, and translate a Work; 47 | ii. moral rights retained by the original author(s) and/or performer(s); 48 | iii. publicity and privacy rights pertaining to a person's image or 49 | likeness depicted in a Work; 50 | iv. rights protecting against unfair competition in regards to a Work, 51 | subject to the limitations in paragraph 4(a), below; 52 | v. rights protecting the extraction, dissemination, use and reuse of data 53 | in a Work; 54 | vi. database rights (such as those arising under Directive 96/9/EC of the 55 | European Parliament and of the Council of 11 March 1996 on the legal 56 | protection of databases, and under any national implementation 57 | thereof, including any amended or successor version of such 58 | directive); and 59 | vii. other similar, equivalent or corresponding rights throughout the 60 | world based on applicable law or treaty, and any national 61 | implementations thereof. 62 | 63 | 2. Waiver. To the greatest extent permitted by, but not in contravention 64 | of, applicable law, Affirmer hereby overtly, fully, permanently, 65 | irrevocably and unconditionally waives, abandons, and surrenders all of 66 | Affirmer's Copyright and Related Rights and associated claims and causes 67 | of action, whether now known or unknown (including existing as well as 68 | future claims and causes of action), in the Work (i) in all territories 69 | worldwide, (ii) for the maximum duration provided by applicable law or 70 | treaty (including future time extensions), (iii) in any current or future 71 | medium and for any number of copies, and (iv) for any purpose whatsoever, 72 | including without limitation commercial, advertising or promotional 73 | purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each 74 | member of the public at large and to the detriment of Affirmer's heirs and 75 | successors, fully intending that such Waiver shall not be subject to 76 | revocation, rescission, cancellation, termination, or any other legal or 77 | equitable action to disrupt the quiet enjoyment of the Work by the public 78 | as contemplated by Affirmer's express Statement of Purpose. 79 | 80 | 3. Public License Fallback. Should any part of the Waiver for any reason 81 | be judged legally invalid or ineffective under applicable law, then the 82 | Waiver shall be preserved to the maximum extent permitted taking into 83 | account Affirmer's express Statement of Purpose. In addition, to the 84 | extent the Waiver is so judged Affirmer hereby grants to each affected 85 | person a royalty-free, non transferable, non sublicensable, non exclusive, 86 | irrevocable and unconditional license to exercise Affirmer's Copyright and 87 | Related Rights in the Work (i) in all territories worldwide, (ii) for the 88 | maximum duration provided by applicable law or treaty (including future 89 | time extensions), (iii) in any current or future medium and for any number 90 | of copies, and (iv) for any purpose whatsoever, including without 91 | limitation commercial, advertising or promotional purposes (the 92 | "License"). The License shall be deemed effective as of the date CC0 was 93 | applied by Affirmer to the Work. Should any part of the License for any 94 | reason be judged legally invalid or ineffective under applicable law, such 95 | partial invalidity or ineffectiveness shall not invalidate the remainder 96 | of the License, and in such case Affirmer hereby affirms that he or she 97 | will not (i) exercise any of his or her remaining Copyright and Related 98 | Rights in the Work or (ii) assert any associated claims and causes of 99 | action with respect to the Work, in either case contrary to Affirmer's 100 | express Statement of Purpose. 101 | 102 | 4. Limitations and Disclaimers. 103 | 104 | a. No trademark or patent rights held by Affirmer are waived, abandoned, 105 | surrendered, licensed or otherwise affected by this document. 106 | b. Affirmer offers the Work as-is and makes no representations or 107 | warranties of any kind concerning the Work, express, implied, 108 | statutory or otherwise, including without limitation warranties of 109 | title, merchantability, fitness for a particular purpose, non 110 | infringement, or the absence of latent or other defects, accuracy, or 111 | the present or absence of errors, whether or not discoverable, all to 112 | the greatest extent permissible under applicable law. 113 | c. Affirmer disclaims responsibility for clearing rights of other persons 114 | that may apply to the Work or any use thereof, including without 115 | limitation any person's Copyright and Related Rights in the Work. 116 | Further, Affirmer disclaims responsibility for obtaining any necessary 117 | consents, permissions or other rights required for any use of the 118 | Work. 119 | d. Affirmer understands and acknowledges that Creative Commons is not a 120 | party to this document and has no duty or obligation with respect to 121 | this CC0 or use of the Work. 122 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | GNOME Tweaks 2 | ============================= 3 | 4 | [Repository](https://gitlab.gnome.org/GNOME/gnome-tweaks) 5 | 6 | [Bug Tracker](https://gitlab.gnome.org/GNOME/gnome-tweaks/issues) 7 | 8 | ## Supported Desktops 9 | 10 | Tweaks is designed for GNOME Shell but can be used in other desktops. 11 | 12 | Some few features will be missing when Tweaks is run without gnome-shell. 13 | 14 | ## Build 15 | 16 | The only build-time dependency is [meson](https://mesonbuild.com/). 17 | 18 | meson builddir 19 | ninja -C builddir 20 | ninja -C builddir install 21 | 22 | ### Runtime Dependencies 23 | 24 | - Python 3 25 | - pygobject 26 | - gnome-settings-daemon 27 | - sound-theme-freedesktop 28 | 29 | - GIR files and libraries from: 30 | 31 | - GLib 32 | - GTK 4 33 | - gnome-desktop 34 | - libadwaita 35 | - libnotify 36 | - Pango 37 | - gsettings-desktop-schemas 38 | - libgudev 39 | 40 | - GSettings Schemas from: 41 | - gsettings-desktop-schemas 42 | - gnome-shell 43 | - mutter 44 | 45 | ### Running 46 | 47 | - If you wish to run the application uninstalled, execute; 48 | 49 | ./gnome-tweaks [-p /path/to/jhbuild/prefix/] 50 | 51 | ## License 52 | 53 | This program is free software: you can redistribute it and/or modify it under 54 | the terms of the GNU General Public License as published by the Free Software 55 | Foundation, either version 3 of the License, or (at your option) any later version. 56 | 57 | data/org.gnome.tweaks.appdata.xml.in is licensed under the [Creative Commons 58 | CC0-1.0](https://creativecommons.org/publicdomain/zero/1.0/legalcode) license. 59 | -------------------------------------------------------------------------------- /data/meson.build: -------------------------------------------------------------------------------- 1 | appdata_file = 'org.gnome.tweaks.appdata.xml' 2 | i18n.merge_file( 3 | input: appdata_file + '.in', 4 | output: appdata_file, 5 | po_dir: '../po', 6 | install: true, 7 | install_dir: appdatadir 8 | ) 9 | 10 | i18n.merge_file( 11 | input: configure_file( 12 | input: default_pkgappid + '.desktop.in', 13 | output: pkgappid + '.desktop.temp', 14 | configuration: defs_conf, 15 | install: false 16 | ), 17 | output: pkgappid + '.desktop', 18 | po_dir: '../po', 19 | install: true, 20 | install_dir: desktopdir, 21 | type: 'desktop' 22 | ) 23 | 24 | gui_data = [ 25 | 'shell.ui', 26 | 'tweaks.ui', 27 | 'shell.css' 28 | ] 29 | install_data(gui_data, install_dir: pkgdatadir) 30 | 31 | # Install the settings schema file 32 | install_data( 33 | 'org.gnome.tweaks.gschema.xml', 34 | install_dir: 'share/glib-2.0/schemas' 35 | ) 36 | 37 | install_data (pkgappid + '.svg', 38 | install_dir: join_paths(datadir, 'icons', 'hicolor', 'scalable', 'apps')) 39 | install_data (default_pkgappid + '-symbolic.svg', rename: pkgappid + '-symbolic.svg', 40 | install_dir: join_paths(datadir, 'icons', 'hicolor', 'symbolic', 'apps')) 41 | -------------------------------------------------------------------------------- /data/org.gnome.TweaksDevel.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /data/org.gnome.tweaks-symbolic.svg: -------------------------------------------------------------------------------- 1 | 2 | 16 | 18 | 19 | 21 | image/svg+xml 22 | 24 | 25 | 26 | 27 | 29 | 46 | 49 | 50 | 55 | 61 | 68 | 69 | -------------------------------------------------------------------------------- /data/org.gnome.tweaks.appdata.xml.in: -------------------------------------------------------------------------------- 1 | 2 | 3 | org.gnome.tweaks 4 | CC0-1.0 5 | GPL-3.0+ 6 | GNOME 7 | 8 | The GNOME Project 9 | 10 | The GNOME Project 11 | 12 | contact_at_evanwelsh.com 13 | GNOME Tweaks 14 | Tweak advanced GNOME settings 15 | 16 |

17 | GNOME Tweaks allows adjusting advanced GNOME options. 18 |

19 |

20 | It can manage keyboard mapping customizations, add 21 | startup applications, and set custom window controls 22 | among other settings. 23 |

24 |
25 | 26 | 27 | 28 |
    29 |
  • User interface redesigned with Adwaita and GTK4
  • 30 |
  • Fixed bug preventing translations from loading in parts of the app
  • 31 |
  • Removed tweaks now handled by GNOME Settings
  • 32 |
  • Added tweaks for managing pointing sticks
  • 33 |
  • Bug fixes and performance improvements
  • 34 |
  • Update translations
  • 35 |
36 |
37 |
38 | 39 | 40 |
    41 |
  • Remove settings now found in GNOME Settings
  • 42 |
  • Some interface refreshments ahead of the GTK4 port
  • 43 |
  • Update translations
  • 44 |
45 |
46 |
47 | 48 | 49 |
    50 |
  • Fix GNOME Extensions link
  • 51 |
  • Clarify legacy GTK theme setting
  • 52 |
  • Remove tweaks duplicated in Settings
  • 53 |
  • Remove broken lockscreen wallpaper setting
  • 54 |
  • Update translations
  • 55 |
56 |
57 |
58 | 59 | 60 |

61 | First stable release for GNOME 40. This version is mainly comprised 62 | of bug fixes and changes for GNOME 40. There are also some notable 63 | improvements: 64 |

65 |
    66 |
  • Remove Extensions support (now found in the Extensions app)
  • 67 |
  • Support new font settings for GNOME 40
  • 68 |
  • Fix UI on small screens
  • 69 |
  • Update translations
  • 70 |
71 |
72 |
73 | 74 | 75 |
    76 |
  • Remove Extensions support
  • 77 |
  • Add Flatpak support
  • 78 |
  • Support new font settings
  • 79 |
  • Fix leaflet on small screens
  • 80 |
  • Various other bug fixes
  • 81 |
  • Update translations
  • 82 |
83 |
84 |
85 |
86 | 87 | gnome-tweak-tool.desktop 88 | 89 | 90 | HiDpiIcon 91 | ModernToolkit 92 | 93 | 94 | 95 | https://gitlab.gnome.org/GNOME/gnome-tweaks/raw/HEAD/data/screenshot-windows.png 96 | 97 | 98 | https://gitlab.gnome.org/GNOME/gnome-tweaks/raw/HEAD/data/screenshot-appearance.png 99 | 100 | 101 | https://gitlab.gnome.org/GNOME/gnome-tweaks/raw/HEAD/data/screenshot-fonts.png 102 | 103 | 104 | https://gitlab.gnome.org/GNOME/gnome-tweaks/raw/HEAD/data/screenshot-mouse-touchpad.png 105 | 106 | 107 | gnome-tweaks 108 | org.gnome.tweaks.desktop 109 | 110 | https://gitlab.gnome.org/GNOME/gnome-tweaks 111 | https://l10n.gnome.org/module/gnome-tweaks/ 112 | https://gitlab.gnome.org/GNOME/gnome-tweaks/-/issues 113 | https://www.gnome.org/donate/ 114 | https://gitlab.gnome.org/GNOME/gnome-tweaks 115 |
-------------------------------------------------------------------------------- /data/org.gnome.tweaks.desktop.in: -------------------------------------------------------------------------------- 1 | [Desktop Entry] 2 | Name=Tweaks 3 | Comment=Tweak advanced GNOME settings 4 | # Translators: Do NOT translate or transliterate this text (this is an icon file name)! 5 | Icon=@APPID@ 6 | Exec=gnome-tweaks 7 | Terminal=false 8 | Type=Application 9 | StartupNotify=true 10 | StartupWMClass=gnome-tweaks 11 | Categories=GNOME;GTK;Utility; 12 | # Translators: Search terms to find this application. Do NOT translate or localize the semicolons! The list MUST also end with a semicolon! 13 | Keywords=Settings;Advanced;Preferences;Fonts;Theme;XKB;Keyboard;Typing;Startup; 14 | OnlyShowIn=GNOME;Unity;Pantheon; 15 | -------------------------------------------------------------------------------- /data/org.gnome.tweaks.gschema.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | true 6 | Show Extensions Notice 7 | When first installed the user should be notified that extensions support has moved to a dedicated app, Extensions. 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /data/org.gnome.tweaks.svg: -------------------------------------------------------------------------------- 1 | 2 | 15 | Adwaita Icon Template 17 | 19 | 21 | 25 | 29 | 33 | 37 | 41 | 45 | 46 | 55 | 58 | 67 | 68 | 69 | 71 | 72 | 74 | image/svg+xml 75 | 77 | 78 | 79 | GNOME Design Team 80 | 81 | 82 | 83 | 85 | Adwaita Icon Template 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 113 | 115 | 117 | 119 | 121 | 123 | 125 | 126 | 127 | 128 | 132 | 135 | 138 | 147 | 156 | 157 | 160 | 165 | 175 | 178 | 186 | 193 | 194 | 201 | 202 | 205 | 210 | 220 | 223 | 231 | 238 | 239 | 246 | 247 | 248 | 249 | 250 | -------------------------------------------------------------------------------- /data/screenshot-appearance.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GNOME/gnome-tweaks/a3275acc437f1fca05e76cd417240b9b1fb72e56/data/screenshot-appearance.png -------------------------------------------------------------------------------- /data/screenshot-fonts.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GNOME/gnome-tweaks/a3275acc437f1fca05e76cd417240b9b1fb72e56/data/screenshot-fonts.png -------------------------------------------------------------------------------- /data/screenshot-mouse-touchpad.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GNOME/gnome-tweaks/a3275acc437f1fca05e76cd417240b9b1fb72e56/data/screenshot-mouse-touchpad.png -------------------------------------------------------------------------------- /data/screenshot-windows.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GNOME/gnome-tweaks/a3275acc437f1fca05e76cd417240b9b1fb72e56/data/screenshot-windows.png -------------------------------------------------------------------------------- /data/shell.css: -------------------------------------------------------------------------------- 1 | /* the sidebar */ 2 | .tweak-category { 3 | padding: 10px; 4 | } 5 | 6 | /* the container and tweaks in a group */ 7 | .tweak-group { 8 | background-color: rgba(0, 0, 0, 0); 9 | } 10 | 11 | .tweak { 12 | padding-top: 3px; 13 | background-color: rgba(0, 0, 0, 0); 14 | } 15 | .tweak:hover { 16 | background-color: rgba(0, 0, 0, 0); 17 | } 18 | .tweak.title { 19 | padding-top: 10px; 20 | } 21 | 22 | .tweak-titlebar-left:dir(ltr), 23 | .tweak-titlebar-right:dir(rtl) { 24 | border-top-right-radius: 0; 25 | } 26 | 27 | .tweak-titlebar-right:dir(ltr), 28 | .tweak-titlebar-left:dir(rtl) { 29 | border-top-left-radius: 0; 30 | } 31 | 32 | /* individual tweak theme changes */ 33 | .tweak.title#title-theme { 34 | padding-top: 3px; 35 | } 36 | .tweak#hinting, 37 | .tweak#text-scaling-factor { 38 | padding-top: 20px; 39 | } 40 | 41 | .tweak-group-white, 42 | .tweak-white, 43 | .tweak-white:hover { 44 | background-color: white; 45 | } 46 | 47 | .tweak-startup, 48 | .tweak-startup:hover { 49 | background-color: lighter(shade(@theme_bg_color, 0.9)); 50 | } 51 | 52 | .tweak-group-startup { 53 | background-color: @view_separators; 54 | } 55 | 56 | .tweak-row { 57 | padding: 10px; 58 | } 59 | 60 | .split-row { 61 | padding: 10px; 62 | } 63 | 64 | .split-row > .content { 65 | padding-top: 10px; 66 | padding-bottom: 10px; 67 | } 68 | 69 | -------------------------------------------------------------------------------- /data/shell.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |
5 | 6 | _Reset to Defaults 7 | app.reset 8 | 9 |
10 |
11 | 12 | _About Tweaks 13 | app.about 14 | 15 |
16 |
17 |
18 | -------------------------------------------------------------------------------- /data/tweaks.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 294 | -------------------------------------------------------------------------------- /gnome-tweaks: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # SPDX-License-Identifier: GPL-3.0+ 3 | # License-Filename: LICENSES/GPL-3.0 4 | 5 | import gettext 6 | import logging 7 | import locale 8 | import os.path 9 | import optparse 10 | import signal 11 | import sys 12 | 13 | import gi 14 | gi.require_version("Gtk", "4.0") 15 | gi.require_version("Adw", "1") 16 | gi.require_version("GDesktopEnums", "3.0") 17 | gi.require_version('GUdev', '1.0') 18 | 19 | import gtweak 20 | from gtweak.defs import VERSION, APP_ID, GSETTINGS_SCHEMA_DIR, TWEAK_DIR, DATA_DIR, \ 21 | PKG_DATA_DIR, LOCALE_DIR 22 | 23 | def set_internationalization(domain, locale_dir): 24 | """ 25 | Initialize gettext and locale domains 26 | """ 27 | 28 | try: 29 | locale.bindtextdomain(domain, locale_dir) 30 | locale.textdomain(domain) 31 | except AttributeError as e: 32 | # Python built without gettext support does not have 33 | # bindtextdomain() and textdomain(). 34 | logging.error( 35 | "Could not bind the gettext translation domain. Some translations will not work.", 36 | exc_info=e, 37 | ) 38 | 39 | gettext.bindtextdomain(domain, locale_dir) 40 | gettext.textdomain(domain) 41 | 42 | gettext.install(domain, names=('gettext', 'ngettext')) 43 | 44 | def setup_defs_for_prefix(prefix: str): 45 | """ 46 | Setup the directory definitions given a prefix 47 | """ 48 | 49 | gtweak.DATA_DIR = os.path.join(prefix, "share") 50 | gtweak.LOCALE_DIR = os.path.join(prefix, "share", "locale") 51 | gtweak.GSETTINGS_SCHEMA_DIR = os.path.join(prefix, "share", "glib-2.0", "schemas") 52 | 53 | me = os.path.abspath(os.path.dirname(__file__)) 54 | gtweak.TWEAK_DIR = os.path.join(me, "gtweak", "tweaks") 55 | gtweak.PKG_DATA_DIR = os.path.join(me, "data") 56 | 57 | 58 | def setup_defs(prefix: str | None = None): 59 | """ 60 | Setup the directory definitions 61 | """ 62 | 63 | gtweak.APP_ID = APP_ID 64 | 65 | if prefix: 66 | setup_defs_for_prefix(prefix) 67 | else: 68 | gtweak.GSETTINGS_SCHEMA_DIR = GSETTINGS_SCHEMA_DIR 69 | gtweak.TWEAK_DIR = TWEAK_DIR 70 | gtweak.DATA_DIR = DATA_DIR 71 | gtweak.PKG_DATA_DIR = PKG_DATA_DIR 72 | gtweak.LOCALE_DIR = LOCALE_DIR 73 | 74 | 75 | if __name__ == '__main__': 76 | parser = optparse.OptionParser(version=VERSION) 77 | parser.add_option("-p", "--prefix", 78 | help="Installation prefix (for gsettings schema, themes, etc)", 79 | metavar="[/, /usr]") 80 | parser.add_option("-v", "--verbose", action="store_true", 81 | help="Print the names of settings modified") 82 | parser.add_option("-d", "--debug", action="store_true", 83 | help="Enable debug output") 84 | options, args = parser.parse_args() 85 | 86 | setup_defs(options.prefix or None) 87 | 88 | gtweak.VERBOSE = options.verbose 89 | 90 | if options.debug: 91 | level = logging.DEBUG 92 | else: 93 | level = logging.WARNING 94 | 95 | logging.basicConfig(format="%(levelname)-8s: %(message)s", level=level) 96 | 97 | set_internationalization(gtweak.APP_NAME, LOCALE_DIR) 98 | 99 | from gtweak.app import GnomeTweaks 100 | 101 | app = GnomeTweaks() 102 | signal.signal(signal.SIGINT, signal.SIG_DFL) 103 | exit_status = app.run(None) 104 | sys.exit(exit_status) 105 | -------------------------------------------------------------------------------- /gnome-tweaks.doap: -------------------------------------------------------------------------------- 1 | 6 | 7 | GNOME Tweaks 8 | Customize advanced GNOME options 9 | GNOME Tweaks allows adjusting advanced configuration 10 | settings in GNOME. This includes things like the fonts used in user 11 | interface elements, alternative user interface themes, changes in window 12 | management behavior, GNOME Shell appearance, etc. 13 | 14 | 15 | 16 | 17 | Python 18 | 19 | 20 | 21 | Evan Welsh 22 | 23 | ewlsh 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /gtweak/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2011 John Stowers 2 | # SPDX-License-Identifier: GPL-3.0+ 3 | # License-Filename: LICENSES/GPL-3.0 4 | 5 | VERBOSE = False 6 | APP_NAME = "gnome-tweaks" 7 | APP_ID = "org.gnome.tweaks" -------------------------------------------------------------------------------- /gtweak/app.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2011 John Stowers 2 | # SPDX-License-Identifier: GPL-3.0+ 3 | # License-Filename: LICENSES/GPL-3.0 4 | 5 | from gi.repository import Adw 6 | from gi.repository import Gtk 7 | from gi.repository import Gio 8 | from gi.repository import GLib 9 | 10 | import gtweak 11 | from gtweak.defs import VERSION 12 | from gtweak.tweakmodel import TweakModel 13 | from gtweak.tweakview import Window 14 | from gtweak.utils import SchemaList 15 | from gtweak.gshellwrapper import GnomeShellFactory 16 | 17 | class ExtensionNotice(Gtk.MessageDialog): 18 | def __init__(self, modal, transient_for): 19 | Gtk.Dialog.__init__(self, modal=modal, transient_for=transient_for) 20 | 21 | self.add_button(_("_Continue"), Gtk.ResponseType.NONE) 22 | 23 | self.set_markup("{0}".format(_("Extensions Has Moved"))) 24 | 25 | self.props.secondary_use_markup = True 26 | self.props.secondary_text = "{0}\n\n{1}".format( 27 | # Translators: Placeholder will be replaced with "GNOME Extensions" in active link form 28 | _("Extensions management has been moved to {0}.").format( 29 | 'GNOME Extensions', 30 | ), 31 | # Translators: Placeholder will be replaced with "Flathub" in active link form 32 | _("We recommend downloading GNOME Extensions from {0} if your distribution does not include it.").format( 33 | 'Flathub' 34 | ) 35 | ) 36 | 37 | _application = None 38 | 39 | def get_application(): 40 | return _application 41 | 42 | def get_window(): 43 | return _application.win 44 | 45 | class GnomeTweaks(Adw.Application): 46 | 47 | def __init__(self): 48 | global _application 49 | 50 | _application = self 51 | 52 | GLib.set_application_name(_("GNOME Tweaks")) 53 | super().__init__(application_id=gtweak.APP_ID) 54 | self.win = None 55 | 56 | self._settings = Gio.Settings.new('org.gnome.tweaks') 57 | 58 | def do_activate(self): 59 | if not self.win: 60 | model = TweakModel() 61 | self.win = Window(self, model) 62 | self.win.present() 63 | 64 | if self._settings.get_boolean('show-extensions-notice'): 65 | self.show_extensions_notice() 66 | self._settings.set_boolean('show-extensions-notice', False) 67 | 68 | def do_startup(self): 69 | Adw.Application.do_startup(self) 70 | 71 | self._create_action("quit", self.quit, ["q"]) 72 | self._create_action("about", self.about_cb) 73 | self._create_action("reset", self.reset_cb) 74 | 75 | def reset_cb(self, action, parameter): 76 | def _on_dialog_response(_dialog, response_type): 77 | if response_type == Gtk.ResponseType.OK: 78 | SchemaList.reset() 79 | 80 | _dialog.destroy() 81 | 82 | dialog = Gtk.MessageDialog(transient_for=self.win, 83 | modal=True, 84 | message_type=Gtk.MessageType.QUESTION, 85 | buttons=Gtk.ButtonsType.OK_CANCEL, 86 | text=_("Reset to Defaults"), 87 | secondary_text=_("Reset all tweaks settings to the original default state?")) 88 | 89 | dialog.connect("response", _on_dialog_response) 90 | dialog.present() 91 | 92 | def about_cb(self, action, parameter): 93 | _shell = GnomeShellFactory().get_shell() 94 | if _shell is not None: 95 | if _shell.mode == "user": 96 | about_comment = f'{_("GNOME Shell")} {_shell.version}' 97 | 98 | else: 99 | about_comment = (_("GNOME Shell") + " %s " + _("(%s mode)")) % \ 100 | (_shell.version, _shell.mode) 101 | else: 102 | about_comment = _("GNOME Shell is not running") 103 | 104 | about_comment += f'\n{_("GTK")} {Gtk.get_major_version()}.{Gtk.get_minor_version()}.{Gtk.get_micro_version()}' 105 | 106 | AUTHORS = [ 107 | "John Stowers " 108 | ] 109 | 110 | aboutdialog = Adw.AboutWindow( 111 | application_name=GLib.get_application_name(), 112 | application_icon=gtweak.APP_ID, 113 | comments=about_comment, 114 | copyright="Copyright © 2011 - 2013 John Stowers.", 115 | developer_name="John Stowers", 116 | # TRANSLATORS: Add your name/nickname here (one name per line), 117 | # they will be displayed in the "about" dialog 118 | translator_credits=_("translator-credits"), 119 | developers=AUTHORS, 120 | transient_for=self.win, 121 | version=VERSION, 122 | website="https://wiki.gnome.org/Apps/Tweaks", 123 | issue_url="https://gitlab.gnome.org/GNOME/gnome-tweaks/-/issues", 124 | license_type=Gtk.License.GPL_3_0 125 | ) 126 | 127 | aboutdialog.present() 128 | 129 | def show_extensions_notice(self): 130 | extensionsdialog = ExtensionNotice( 131 | modal=True, 132 | transient_for=self.win 133 | ) 134 | 135 | extensionsdialog.connect("response", lambda _dialog, _: _dialog.destroy()) 136 | extensionsdialog.show() 137 | 138 | def _create_action(self, name, callback, shortcuts=None): 139 | """Add an application action. 140 | 141 | :param name: the name of the action 142 | :param callback: the function to be called when the action is activated 143 | :param shortcuts: 144 | :return: an optional list of accelerators 145 | """ 146 | action = Gio.SimpleAction.new(name, None) 147 | action.connect("activate", callback) 148 | self.add_action(action) 149 | if shortcuts: 150 | self.set_accels_for_action(f"app.{name}", shortcuts) 151 | -------------------------------------------------------------------------------- /gtweak/defs.py.in: -------------------------------------------------------------------------------- 1 | DATA_DIR = "@DATADIR@" 2 | APP_ID = "@APPID@" 3 | PKG_DATA_DIR = "@PKGDATADIR@" 4 | GSETTINGS_SCHEMA_DIR = "@GSETTINGSSCHEMADIR@" 5 | TWEAK_DIR = "@TWEAKDIR@" 6 | VERSION = "@VERSION@" 7 | LOCALE_DIR = "@LOCALEDIR@" 8 | PROFILE = "@PROFILE@" 9 | 10 | IS_DEVEL = PROFILE == "development" 11 | -------------------------------------------------------------------------------- /gtweak/devicemanager.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2024 Evan Welsh 2 | # SPDX-License-Identifier: GPL-3.0+ 3 | # License-Filename: LICENSES/GPL-3.0 4 | 5 | from gi.repository import GUdev 6 | 7 | # Inspired by panels/common/gsd-device-manager.c in GNOME Settings 8 | # https://gitlab.gnome.org/GNOME/gnome-control-center/-/blob/6d2add0e30538692c151e5fa0bd94ae9bece7690/panels/common/gsd-device-manager.c 9 | 10 | UDEV_ID_INPUT_MOUSE = "ID_INPUT_MOUSE" 11 | UDEV_ID_INPUT_KEYBOARD = "ID_INPUT_KEYBOARD" 12 | UDEV_ID_INPUT_TOUCHPAD = "ID_INPUT_TOUCHPAD" 13 | UDEV_ID_INPUT_POINTING_STICK = "ID_INPUT_POINTINGSTICK" 14 | 15 | UDEV_IDS = [ 16 | UDEV_ID_INPUT_MOUSE, 17 | UDEV_ID_INPUT_KEYBOARD, 18 | UDEV_ID_INPUT_TOUCHPAD, 19 | UDEV_ID_INPUT_POINTING_STICK, 20 | ] 21 | 22 | def udev_device_is_evdev(device): 23 | device_file = device.get_device_file() 24 | if device_file is None or "/event" not in device_file: 25 | return False 26 | 27 | return device.get_property_as_boolean("ID_INPUT") 28 | 29 | 30 | def get_input_devices(): 31 | udev_client = GUdev.Client() 32 | devices = udev_client.query_by_subsystem ("input") 33 | 34 | return [device for device in devices if udev_device_is_evdev(device)] 35 | 36 | 37 | def udev_device_get_device_types (device): 38 | types = set() 39 | 40 | for id in UDEV_IDS: 41 | if device.get_property_as_boolean(id): 42 | types.add(id) 43 | 44 | return types 45 | 46 | 47 | def udev_device_id_is_present(id): 48 | for device in get_input_devices(): 49 | if id in udev_device_get_device_types(device): 50 | return True 51 | 52 | return False 53 | 54 | 55 | def pointing_stick_is_present(): 56 | return udev_device_id_is_present(UDEV_ID_INPUT_POINTING_STICK) 57 | 58 | 59 | def touchpad_is_present(): 60 | return udev_device_id_is_present(UDEV_ID_INPUT_TOUCHPAD) -------------------------------------------------------------------------------- /gtweak/gsettings.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2011 John Stowers 2 | # SPDX-License-Identifier: GPL-3.0+ 3 | # License-Filename: LICENSES/GPL-3.0 4 | 5 | import logging 6 | import os.path 7 | import xml.dom.minidom 8 | import gettext 9 | 10 | import gtweak 11 | 12 | from gi.repository import Gio, GLib 13 | 14 | _SCHEMA_CACHE = {} 15 | _GSETTINGS_SCHEMAS = set(Gio.Settings.list_schemas()) 16 | _GSETTINGS_RELOCATABLE_SCHEMAS = set(Gio.Settings.list_relocatable_schemas()) 17 | 18 | 19 | class GSettingsMissingError(Exception): 20 | pass 21 | 22 | 23 | class _GSettingsSchema: 24 | def __init__(self, schema_name, child_name=None, schema_dir=None, schema_filename=None, **options): 25 | if not schema_filename: 26 | schema_filename = schema_name + ".gschema.xml" 27 | if not schema_dir: 28 | schema_dir = gtweak.GSETTINGS_SCHEMA_DIR 29 | for xdg_dir in GLib.get_system_data_dirs(): 30 | dir = os.path.join(xdg_dir, "glib-2.0", "schemas") 31 | if os.path.exists(os.path.join(dir, schema_filename)): 32 | schema_dir = dir 33 | break 34 | 35 | schema_path = os.path.join(schema_dir, schema_filename) 36 | if not os.path.exists(schema_path): 37 | logging.critical("Could not find schema %s" % schema_path) 38 | assert(False) 39 | 40 | self._schema_name = f"{schema_name}.{child_name}" if child_name else schema_name 41 | self._schema = {} 42 | 43 | try: 44 | dom = xml.dom.minidom.parse(schema_path) 45 | global_gettext_domain = dom.documentElement.getAttribute('gettext-domain') 46 | try: 47 | if global_gettext_domain: 48 | # We can't know where the schema owner was installed, let's assume it's 49 | # the same prefix as ours 50 | global_translation = gettext.translation(global_gettext_domain, gtweak.LOCALE_DIR) 51 | else: 52 | global_translation = gettext.NullTranslations() 53 | except IOError: 54 | global_translation = None 55 | logging.debug("No translated schema for %s (domain: %s)" % (self._schema_name, global_gettext_domain)) 56 | for schema in dom.getElementsByTagName("schema"): 57 | gettext_domain = schema.getAttribute('gettext-domain') 58 | try: 59 | if gettext_domain: 60 | translation = gettext.translation(gettext_domain, gtweak.LOCALE_DIR) 61 | else: 62 | translation = global_translation 63 | except IOError: 64 | translation = None 65 | logging.debug("Schema not translated %s (domain: %s)" % (self._schema_name, gettext_domain)) 66 | if self._schema_name == schema.getAttribute("id"): 67 | for key in schema.getElementsByTagName("key"): 68 | name = key.getAttribute("name") 69 | # summary is 'compulsory', description is optional 70 | # …in theory, but we should not barf on bad schemas ever 71 | try: 72 | summary = key.getElementsByTagName("summary")[0].childNodes[0].data 73 | except: 74 | summary = "" 75 | logging.info("Schema missing summary %s (key %s)" % 76 | (os.path.basename(schema_path), name)) 77 | try: 78 | description = key.getElementsByTagName("description")[0].childNodes[0].data 79 | except: 80 | description = "" 81 | 82 | # if missing translations, use the untranslated values 83 | self._schema[name] = dict( 84 | summary=translation.gettext(summary) if translation else summary, 85 | description=translation.gettext(description) if translation else description 86 | ) 87 | 88 | except: 89 | logging.critical("Error parsing schema %s (%s)" % (self._schema_name, schema_path), exc_info=True) 90 | 91 | def __repr__(self): 92 | return "" % self._schema_name 93 | 94 | 95 | class GSettingsFakeSetting: 96 | def __init__(self): 97 | pass 98 | 99 | def get_range(self, *args, **kwargs): 100 | return False, [] 101 | 102 | def get_string(self, *args, **kwargs): 103 | return "" 104 | 105 | def __getitem__(self, key): 106 | return "" 107 | 108 | def __getattr__(self, name): 109 | def noop(*args, **kwargs): 110 | pass 111 | return noop 112 | 113 | 114 | class GSettingsSetting(Gio.Settings): 115 | def __init__(self, schema_name, schema_child_name=None, schema_dir=None, schema_path=None, schema_id=None, **options): 116 | 117 | if schema_dir is None: 118 | if schema_path is None and schema_id is None and schema_name not in _GSETTINGS_SCHEMAS: 119 | raise GSettingsMissingError(schema_name) 120 | 121 | if schema_path is not None and schema_name not in _GSETTINGS_RELOCATABLE_SCHEMAS: 122 | raise GSettingsMissingError(schema_name) 123 | 124 | if schema_path is None and schema_id is None: 125 | Gio.Settings.__init__(self, schema=schema_name) 126 | elif schema_id is not None: 127 | Gio.Settings.__init__(self, schema_id=schema_id) 128 | else: 129 | Gio.Settings.__init__(self, schema=schema_name, path=schema_path) 130 | else: 131 | try: 132 | GioSSS = Gio.SettingsSchemaSource 133 | schema_source = GioSSS.new_from_directory(schema_dir, 134 | GioSSS.get_default(), 135 | False) 136 | schema_obj = schema_source.lookup(schema_name, True) 137 | if not schema_obj: 138 | raise GSettingsMissingError(schema_name) 139 | except GLib.GError as e: 140 | logging.exception("Failed to load schema from %s" % schema_dir, exc_info=e) 141 | 142 | raise GSettingsMissingError(schema_name) 143 | 144 | Gio.Settings.__init__(self, None, settings_schema=schema_obj) 145 | 146 | if schema_name not in _SCHEMA_CACHE: 147 | _SCHEMA_CACHE[schema_name] = _GSettingsSchema(schema_name, child_name=schema_child_name, schema_dir=schema_dir, **options) 148 | logging.debug("Caching gsettings: %s" % _SCHEMA_CACHE[schema_name]) 149 | 150 | self._schema = _SCHEMA_CACHE[schema_name] 151 | 152 | if gtweak.VERBOSE: 153 | self.connect("changed", self._on_changed) 154 | 155 | def _on_changed(self, settings, key_name): 156 | print("Change: %s %s -> %s" % (self.props.schema, key_name, self[key_name])) 157 | 158 | def _setting_check_is_list(self, key): 159 | variant = Gio.Settings.get_value(self, key) 160 | return variant.get_type_string() == "as" 161 | 162 | def schema_get_summary(self, key): 163 | if key not in self._schema._schema: 164 | return None 165 | 166 | return self._schema._schema[key]["summary"] 167 | 168 | def schema_get_description(self, key): 169 | if key not in self._schema._schema: 170 | return None 171 | 172 | return self._schema._schema[key]["description"] 173 | 174 | def schema_get_all(self, key): 175 | return self._schema._schema[key] 176 | 177 | def setting_add_to_list(self, key, value): 178 | """ helper function, ensures value is present in the GSettingsList at key """ 179 | assert self._setting_check_is_list(key) 180 | 181 | vals = self[key] 182 | if value not in vals: 183 | vals.append(value) 184 | self[key] = vals 185 | return True 186 | 187 | def setting_remove_from_list(self, key, value): 188 | """ helper function, removes value in the GSettingsList at key (if present)""" 189 | assert self._setting_check_is_list(key) 190 | 191 | vals = self[key] 192 | try: 193 | vals.remove(value) 194 | self[key] = vals 195 | return True 196 | except ValueError: 197 | # not present 198 | pass 199 | 200 | def setting_is_in_list(self, key, value): 201 | assert self._setting_check_is_list(key) 202 | return value in self[key] 203 | 204 | 205 | if __name__ == "__main__": 206 | gtweak.GSETTINGS_SCHEMA_DIR = "/usr/share/glib-2.0/schemas/" 207 | 208 | key = "draw-background" 209 | s = GSettingsSetting("org.gnome.desktop.background") 210 | print(s.schema_get_summary(key), s.schema_get_description(key)) 211 | 212 | key = "disabled-extensions" 213 | s = GSettingsSetting("org.gnome.shell") 214 | assert s.setting_add_to_list(key, "foo") 215 | assert s.setting_remove_from_list(key, "foo") 216 | assert not s.setting_remove_from_list(key, "foo") 217 | -------------------------------------------------------------------------------- /gtweak/gshellwrapper.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2011 John Stowers 2 | # SPDX-License-Identifier: GPL-3.0+ 3 | # License-Filename: LICENSES/GPL-3.0 4 | 5 | import os.path 6 | import json 7 | import logging 8 | 9 | from gi.repository import Gio 10 | from gi.repository import GLib 11 | 12 | import gtweak.utils 13 | from gtweak.gsettings import GSettingsSetting 14 | 15 | 16 | class _ShellProxy: 17 | def __init__(self): 18 | d = Gio.bus_get_sync(Gio.BusType.SESSION, None) 19 | 20 | self.proxy = Gio.DBusProxy.new_sync( 21 | d, 0, None, 22 | 'org.gnome.Shell', 23 | '/org/gnome/Shell', 24 | 'org.gnome.Shell', 25 | None) 26 | 27 | self.proxy_extensions = Gio.DBusProxy.new_sync( 28 | d, 0, None, 29 | 'org.gnome.Shell', 30 | '/org/gnome/Shell', 31 | 'org.gnome.Shell.Extensions', 32 | None) 33 | 34 | val = self.proxy.get_cached_property("Mode") 35 | if val is not None: 36 | self._mode = val.unpack() 37 | else: 38 | logging.warning("Error getting shell mode") 39 | self._mode = "user" 40 | 41 | val = self.proxy.get_cached_property("ShellVersion") 42 | if val is not None: 43 | self._version = val.unpack() 44 | else: 45 | logging.critical("Error getting shell version") 46 | self._version = "0.0.0" 47 | 48 | @property 49 | def mode(self): 50 | return self._mode 51 | 52 | @property 53 | def version(self): 54 | return self._version 55 | 56 | 57 | class GnomeShell: 58 | 59 | def __init__(self, shellproxy, shellsettings): 60 | self._proxy = shellproxy 61 | self._settings = shellsettings 62 | 63 | def _execute_js(self, js): 64 | result, output = self._proxy.proxy.Eval('(s)', js) 65 | if not result: 66 | raise Exception(output) 67 | return output 68 | 69 | def restart(self): 70 | self._execute_js('global.reexec_self();') 71 | 72 | def reload_theme(self): 73 | self._execute_js('const Main = imports.ui.main; Main.loadTheme();') 74 | 75 | def list_extensions(self): 76 | return self._proxy.proxy_extensions.ListExtensions() 77 | 78 | @property 79 | def mode(self): 80 | return self._proxy.mode 81 | 82 | @property 83 | def version(self): 84 | return self._proxy.version 85 | 86 | 87 | @gtweak.utils.singleton 88 | class GnomeShellFactory: 89 | def __init__(self): 90 | try: 91 | proxy = _ShellProxy() 92 | settings = GSettingsSetting("org.gnome.shell") 93 | 94 | self.shell = GnomeShell(proxy, settings) 95 | 96 | logging.debug("Shell version: %s", str(proxy.version)) 97 | except: 98 | self.shell = None 99 | logging.warn("Shell not installed or running") 100 | 101 | def get_shell(self): 102 | return self.shell 103 | 104 | 105 | if __name__ == "__main__": 106 | gtweak.GSETTINGS_SCHEMA_DIR = "/usr/share/glib-2.0/schemas/" 107 | 108 | logging.basicConfig(format="%(levelname)-8s: %(message)s", level=logging.DEBUG) 109 | 110 | s = GnomeShellFactory().get_shell() 111 | print("Shell Version: %s" % s.version) 112 | print(s.list_extensions()) 113 | 114 | print(s == GnomeShellFactory().get_shell()) 115 | -------------------------------------------------------------------------------- /gtweak/gtksettings.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2012 Cosimo Cecchi 2 | # SPDX-License-Identifier: GPL-3.0+ 3 | # License-Filename: LICENSES/GPL-3.0 4 | 5 | import os.path 6 | import logging 7 | 8 | from gi.repository import GLib 9 | 10 | SETTINGS_GROUP_NAME = "Settings" 11 | 12 | LOG = logging.getLogger(__name__) 13 | 14 | 15 | class GtkSettingsManager: 16 | def __init__(self, version): 17 | self._path = os.path.join(GLib.get_user_config_dir(), 18 | "gtk-" + version, 19 | "settings.ini") 20 | os.makedirs(os.path.dirname(self._path), exist_ok=True) 21 | 22 | def _get_keyfile(self): 23 | keyfile = None 24 | try: 25 | keyfile = GLib.KeyFile() 26 | keyfile.load_from_file(self._path, 0) 27 | except MemoryError: 28 | LOG.critical("You have an old PyGObject, no support fo KeyFiles", exc_info=True) 29 | finally: 30 | return keyfile 31 | 32 | def get_integer(self, key): 33 | keyfile = self._get_keyfile() 34 | try: 35 | result = keyfile.get_integer(SETTINGS_GROUP_NAME, key) 36 | except: 37 | result = 0 38 | 39 | return result 40 | 41 | def set_integer(self, key, value): 42 | keyfile = self._get_keyfile() 43 | keyfile.set_integer(SETTINGS_GROUP_NAME, key, value) 44 | 45 | try: 46 | data = keyfile.to_data() 47 | GLib.file_set_contents(self._path, data[0].encode()) 48 | except: 49 | raise 50 | -------------------------------------------------------------------------------- /gtweak/meson.build: -------------------------------------------------------------------------------- 1 | defs_conf = configuration_data() 2 | defs_conf.set('DATADIR', datadir) 3 | defs_conf.set('GSETTINGSSCHEMADIR', schemadir) 4 | defs_conf.set('LIBEXECDIR', libexecdir) 5 | defs_conf.set('LOCALEDIR', localedir) 6 | defs_conf.set('PKGDATADIR', pkgdatadir) 7 | defs_conf.set('APPID', pkgappid) 8 | defs_conf.set('TWEAKDIR', join_paths(gtweakdir, 'tweaks')) 9 | defs_conf.set('VERSION', meson.project_version()) 10 | defs_conf.set('PROFILE', get_option('profile')) 11 | 12 | configure_file( 13 | input: 'defs.py.in', 14 | output: 'defs.py', 15 | configuration: defs_conf, 16 | install_dir: gtweakdir 17 | ) 18 | 19 | shell_sources = [ 20 | 'app.py', 21 | 'devicemanager.py', 22 | 'gsettings.py', 23 | 'gshellwrapper.py', 24 | 'gtksettings.py', 25 | '__init__.py', 26 | 'tweakmodel.py', 27 | 'tweakview.py', 28 | 'utils.py', 29 | 'widgets.py' 30 | ] 31 | 32 | tweak_sources = [ 33 | 'tweaks/__init__.py', 34 | 'tweaks/tweak_group_appearance.py', 35 | 'tweaks/tweak_group_sound.py', 36 | 'tweaks/tweak_group_font.py', 37 | 'tweaks/tweak_group_mouse.py', 38 | 'tweaks/tweak_group_keyboard.py', 39 | 'tweaks/tweak_group_startup.py', 40 | 'tweaks/tweak_group_windows.py' 41 | ] 42 | 43 | python3.install_sources(shell_sources, subdir: 'gtweak') 44 | python3.install_sources(tweak_sources, subdir: join_paths('gtweak', 'tweaks')) 45 | -------------------------------------------------------------------------------- /gtweak/tweakmodel.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2011 John Stowers 2 | # SPDX-License-Identifier: GPL-3.0+ 3 | # License-Filename: LICENSES/GPL-3.0 4 | 5 | import logging 6 | import os.path 7 | 8 | import gtweak 9 | from gtweak.utils import LogoutNotification, Notification 10 | from gi.repository import Gtk, GLib 11 | 12 | def N_(x): return x 13 | 14 | LOG = logging.getLogger(__name__) 15 | 16 | def string_for_search(s): 17 | return GLib.utf8_casefold(GLib.utf8_normalize(s, -1, GLib.NormalizeMode.ALL), -1) 18 | 19 | 20 | class Tweak(object): 21 | 22 | main_window = None 23 | widget_for_size_group = None 24 | extra_info = "" 25 | 26 | def __init__(self, title, description, **options): 27 | self.title = title or "" 28 | self.description = description or "" 29 | self.uid = options.get("uid", self.__class__.__name__) 30 | self.group_name = options.get("group_name", _("Miscellaneous")) 31 | self.loaded = options.get("loaded", True) 32 | self.widget_sort_hint = None 33 | 34 | self._search_cache = None 35 | 36 | def search_matches(self, txt): 37 | if self._search_cache is None: 38 | self._search_cache = string_for_search(self.title) + " " + \ 39 | string_for_search(self.description) 40 | try: 41 | self._search_cache += " " + string_for_search(self.extra_info) 42 | except: 43 | LOG.warning("Error adding search info", exc_info=True) 44 | return txt in self._search_cache 45 | 46 | def notify_logout(self): 47 | self._logoutnotification = LogoutNotification() 48 | 49 | def notify_information(self, summary, desc=""): 50 | try: 51 | self._notification = Notification(summary, desc) 52 | except: 53 | logging.exception("Failed to send notification", exc_info=True) 54 | logging.info(f"{summary}: {desc}") 55 | 56 | 57 | class TweakGroup(object): 58 | 59 | main_window = None 60 | 61 | def __init__(self, name, title, *tweaks: Tweak, **options): 62 | self.name = name 63 | self.title = title 64 | self.titlebar_widget = None 65 | self.tweaks = [t for t in tweaks if t.loaded] 66 | self.uid = options.get('uid', self.__class__.__name__) 67 | 68 | def add_tweak(self, tweak: Tweak): 69 | if tweak.loaded: 70 | self.tweaks.append(tweak) 71 | return True 72 | return False 73 | 74 | 75 | class TweakModel(Gtk.ListStore): 76 | (COLUMN_NAME, 77 | COLUMN_TWEAK) = list(range(2)) 78 | 79 | def __init__(self): 80 | super(TweakModel, self).__init__(str, object) 81 | self._tweak_dir = gtweak.TWEAK_DIR 82 | assert(os.path.exists(self._tweak_dir)) 83 | 84 | self.set_sort_column_id(self.COLUMN_NAME, Gtk.SortType.ASCENDING) 85 | 86 | # map of tweakgroup.name -> tweakgroup 87 | self._tweak_group_names = {} 88 | self._tweak_group_iters = {} 89 | 90 | @property 91 | def tweaks(self): 92 | return (t for row in self for t in row[TweakModel.COLUMN_TWEAK].tweaks) 93 | 94 | @property 95 | def tweak_groups(self): 96 | return (row[TweakModel.COLUMN_TWEAK] for row in self) 97 | 98 | def add_tweak_group(self, tweakgroup, main_window=None): 99 | if tweakgroup.name in self._tweak_group_names: 100 | LOG.critical("Tweak group named: %s already exists" % tweakgroup.name) 101 | return 102 | 103 | _iter = self.append([gettext(tweakgroup.name), tweakgroup]) 104 | self._tweak_group_names[tweakgroup.name] = tweakgroup 105 | self._tweak_group_iters[tweakgroup.name] = _iter 106 | if main_window: 107 | tweakgroup.main_window = main_window 108 | 109 | def search_matches(self, txt): 110 | groups = [] 111 | 112 | for g in self.tweak_groups: 113 | for t in g.tweaks: 114 | if t.search_matches(txt): 115 | if g.name not in groups: 116 | groups.append(g.name) 117 | 118 | if string_for_search(txt) in string_for_search(g.name) and g.name not in groups: 119 | groups.append(g.name) 120 | 121 | return groups 122 | 123 | def get_tweakgroup_iter(self, name): 124 | return self._tweak_group_iters[name] 125 | -------------------------------------------------------------------------------- /gtweak/tweaks/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2011 John Stowers 2 | # SPDX-License-Identifier: GPL-3.0+ 3 | # License-Filename: LICENSES/GPL-3.0 4 | -------------------------------------------------------------------------------- /gtweak/tweaks/tweak_group_appearance.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2011 John Stowers 2 | # SPDX-License-Identifier: GPL-3.0+ 3 | # License-Filename: LICENSES/GPL-3.0 4 | 5 | import os 6 | import os.path 7 | import logging 8 | import zipfile 9 | import tempfile 10 | import json 11 | 12 | from gi.repository import Gtk 13 | from gi.repository import GLib 14 | from gtweak.tweakmodel import Tweak 15 | 16 | from gtweak.utils import walk_directories, make_combo_list_with_default, extract_zip_file, get_resource_dirs 17 | from gtweak.gshellwrapper import GnomeShellFactory 18 | from gtweak.gtksettings import GtkSettingsManager 19 | from gtweak.widgets import (TweakPreferencesPage, GSettingsTweakComboRow,TweakPreferencesGroup, GSettingsFileChooserButtonTweak, FileChooserButton, build_label_beside_widget) 20 | 21 | 22 | _shell = GnomeShellFactory().get_shell() 23 | _shell_loaded = _shell is not None 24 | 25 | class GtkThemeSwitcher(GSettingsTweakComboRow): 26 | def __init__(self, **options): 27 | self._gtksettings3 = GtkSettingsManager('3.0') 28 | self._gtksettings4 = GtkSettingsManager('4.0') 29 | 30 | GSettingsTweakComboRow.__init__(self, 31 | _("Legacy Applications"), 32 | "org.gnome.desktop.interface", 33 | "gtk-theme", 34 | make_combo_list_with_default(self._get_valid_themes(), "Adwaita"), 35 | **options) 36 | 37 | 38 | def _get_valid_themes(self): 39 | """ Only shows themes that have variations for gtk3""" 40 | gtk_ver = Gtk.MINOR_VERSION 41 | if gtk_ver % 2: # Want even number 42 | gtk_ver += 1 43 | 44 | valid = ['Adwaita', 'HighContrast', 'HighContrastInverse'] 45 | valid += walk_directories(get_resource_dirs("themes"), lambda d: 46 | os.path.exists(os.path.join(d, "gtk-3.0", "gtk.css")) or \ 47 | os.path.exists(os.path.join(d, "gtk-3.{}".format(gtk_ver)))) 48 | return set(valid) 49 | 50 | def _on_combo_changed(self, combo, _): 51 | item = combo.get_selected_item() 52 | if item: 53 | value = item.value 54 | self.settings.set_string(self.key_name, value) 55 | # Turn off Global Dark Theme when theme is changed. 56 | # https://bugzilla.gnome.org/783666 57 | try: 58 | self._gtksettings3.set_integer("gtk-application-prefer-dark-theme", 59 | 0) 60 | self._gtksettings4.set_integer("gtk-application-prefer-dark-theme", 61 | 0) 62 | except: 63 | self.notify_information(_("Error writing setting")) 64 | 65 | 66 | class IconThemeSwitcher(GSettingsTweakComboRow): 67 | def __init__(self, **options): 68 | GSettingsTweakComboRow.__init__(self, 69 | _("Icons"), 70 | "org.gnome.desktop.interface", 71 | "icon-theme", 72 | make_combo_list_with_default(self._get_valid_icon_themes(), "Adwaita"), 73 | **options) 74 | 75 | def _get_valid_icon_themes(self): 76 | valid = walk_directories(get_resource_dirs("icons"), lambda d: 77 | os.path.isdir(d) and \ 78 | os.path.exists(os.path.join(d, "index.theme"))) 79 | return set(valid) 80 | 81 | class CursorThemeSwitcher(GSettingsTweakComboRow): 82 | def __init__(self, **options): 83 | GSettingsTweakComboRow.__init__(self, 84 | _("Cursor"), 85 | "org.gnome.desktop.interface", 86 | "cursor-theme", 87 | make_combo_list_with_default(self._get_valid_cursor_themes(), "Adwaita"), 88 | **options) 89 | 90 | def _get_valid_cursor_themes(self): 91 | valid = walk_directories(get_resource_dirs("icons"), lambda d: 92 | os.path.isdir(d) and \ 93 | os.path.exists(os.path.join(d, "cursors"))) 94 | return set(valid) 95 | 96 | class ShellThemeTweak(GSettingsTweakComboRow): 97 | THEME_EXT_NAME = "user-theme@gnome-shell-extensions.gcampax.github.com" 98 | THEME_GSETTINGS_SCHEMA = "org.gnome.shell.extensions.user-theme" 99 | THEME_GSETTINGS_NAME = "name" 100 | THEME_GSETTINGS_DIR = os.path.join(GLib.get_user_data_dir(), "gnome-shell", "extensions", 101 | THEME_EXT_NAME, "schemas") 102 | LEGACY_THEME_DIR = os.path.join(GLib.get_home_dir(), ".themes") 103 | THEME_DIR = os.path.join(GLib.get_user_data_dir(), "themes") 104 | 105 | def __init__(self): 106 | #check the shell is running and the usertheme extension is present 107 | error = _("Unknown error") 108 | self._shell = _shell 109 | 110 | if self._shell is None: 111 | logging.warning("Shell not running", exc_info=True) 112 | error = _("Shell not running") 113 | else: 114 | try: 115 | extensions = self._shell.list_extensions() 116 | if ShellThemeTweak.THEME_EXT_NAME in extensions and extensions[ShellThemeTweak.THEME_EXT_NAME]["state"] == 1: 117 | error = None 118 | 119 | else: 120 | error = _("Shell user-theme extension not enabled") 121 | except Exception as e: 122 | logging.warning("Could not list shell extensions", exc_info=True) 123 | error = _("Could not list shell extensions") 124 | 125 | if error: 126 | valid = [] 127 | else: 128 | #include both system, and user themes 129 | #note: the default theme lives in /system/data/dir/gnome-shell/theme 130 | # and not themes/, so add it manually later 131 | dirs = [os.path.join(d, "themes") for d in GLib.get_system_data_dirs()] 132 | dirs += [ShellThemeTweak.THEME_DIR] 133 | dirs += [ShellThemeTweak.LEGACY_THEME_DIR] 134 | # add default theme directory since some alternative themes are installed here 135 | dirs += [os.path.join(d, "gnome-shell", "theme") for d in GLib.get_system_data_dirs()] 136 | 137 | valid = walk_directories(dirs, lambda d: 138 | os.path.exists(os.path.join(d, "gnome-shell.css")) or \ 139 | ( 140 | os.path.exists(os.path.join(d, "gnome-shell")) and \ 141 | os.path.exists(os.path.join(d, "gnome-shell", "gnome-shell.css")) 142 | )) 143 | #the default value to reset the shell is an empty string 144 | valid.extend( ("",) ) 145 | valid = set(valid) 146 | 147 | # load the schema from the user installation of User Themes if it exists 148 | schema_dir = ShellThemeTweak.THEME_GSETTINGS_DIR if os.path.exists(ShellThemeTweak.THEME_GSETTINGS_DIR) else None 149 | 150 | # build a combo box with all the valid theme options 151 | GSettingsTweakComboRow.__init__(self, 152 | title=_("Shell"), 153 | subtitle=error if error else None, 154 | schema_name=ShellThemeTweak.THEME_GSETTINGS_SCHEMA, 155 | schema_dir=schema_dir, 156 | key_name=ShellThemeTweak.THEME_GSETTINGS_NAME, 157 | key_options=make_combo_list_with_default(opts=list(valid), default="", default_text=_("Adwaita (default)")), 158 | loaded=_shell_loaded, 159 | ) 160 | 161 | class ShellThemeInstallerTweak(Gtk.Box, Tweak): 162 | def __init__(self, title, description=None, **options): 163 | Gtk.Box.__init__(self, orientation=Gtk.Orientation.HORIZONTAL) 164 | Tweak.__init__(self, title=title, description=description, **options) 165 | 166 | chooser = FileChooserButton( 167 | _("Select a theme"), 168 | ["application/zip"]) 169 | chooser.connect("notify::file-uri", self._on_file_set) 170 | 171 | build_label_beside_widget(title, chooser, hbox=self) 172 | 173 | self.widget_for_size_group = None 174 | 175 | def _on_file_set(self, chooser: FileChooserButton, _pspec): 176 | f = chooser.get_absolute_path() 177 | 178 | if not f: 179 | return 180 | 181 | with zipfile.ZipFile(f, 'r') as z: 182 | try: 183 | fragment = () 184 | theme_name = None 185 | for n in z.namelist(): 186 | if n.endswith("gnome-shell.css"): 187 | fragment = n.split("/")[0:-1] 188 | if n.endswith("gnome-shell/theme.json"): 189 | logging.info("New style theme detected (theme.json)") 190 | #new style theme - extract the name from the json file 191 | tmp = tempfile.mkdtemp() 192 | z.extract(n, tmp) 193 | with open(os.path.join(tmp,n)) as f: 194 | try: 195 | theme_name = json.load(f)["shell-theme"]["name"] 196 | except: 197 | logging.warning("Invalid theme format", exc_info=True) 198 | 199 | if not fragment: 200 | raise Exception("Could not find gnome-shell.css") 201 | 202 | if not theme_name: 203 | logging.info("Old style theme detected (missing theme.json)") 204 | #old style themes name was taken from the zip name 205 | if fragment[0] == "theme" and len(fragment) == 1: 206 | theme_name = os.path.basename(f) 207 | else: 208 | theme_name = fragment[0] 209 | 210 | theme_members_path = "/".join(fragment) 211 | 212 | ok, updated = extract_zip_file( 213 | z, 214 | theme_members_path, 215 | os.path.join(ShellThemeTweak.THEME_DIR, theme_name, "gnome-shell")) 216 | 217 | if ok: 218 | if updated: 219 | self.notify_information(_("%s theme updated successfully") % theme_name) 220 | else: 221 | self.notify_information(_("%s theme installed successfully") % theme_name) 222 | else: 223 | self.notify_information(_("Error installing theme")) 224 | 225 | 226 | except: 227 | # does not look like a valid theme 228 | self.notify_information(_("Invalid theme")) 229 | logging.warning("Error parsing theme zip", exc_info=True) 230 | 231 | # set button back to default state 232 | chooser.props.file_uri = None 233 | 234 | 235 | TWEAK_GROUP = TweakPreferencesPage("appearance", _("Appearance"), 236 | TweakPreferencesGroup( _("Styles"), "title-styles", 237 | CursorThemeSwitcher(), 238 | IconThemeSwitcher(), 239 | ShellThemeTweak(), 240 | GtkThemeSwitcher(), 241 | ), 242 | # TODO: The current installer is brittle and the interaction doesn't make sense 243 | # (you select a file and then it is un-selected with notifications informing 244 | # you if it installed correctly) 245 | # 246 | # ShellThemeInstallerTweak( 247 | # title=_("Install custom shell theme"), 248 | # description=_("Install custom or user themes for GNOME shell"), 249 | # ), 250 | TweakPreferencesGroup( 251 | _("Background"), "title-backgrounds", 252 | GSettingsFileChooserButtonTweak( 253 | _("Default Image"), 254 | "org.gnome.desktop.background", 255 | "picture-uri", 256 | mimetypes=["application/xml", "image/png", "image/jpeg"], 257 | ), 258 | GSettingsFileChooserButtonTweak( 259 | _("Dark Style Image"), 260 | "org.gnome.desktop.background", 261 | "picture-uri-dark", 262 | mimetypes=["application/xml", "image/png", "image/jpeg"], 263 | ), 264 | GSettingsTweakComboRow( 265 | _("Adjustment"), "org.gnome.desktop.background", "picture-options" 266 | ), 267 | ), 268 | ) 269 | -------------------------------------------------------------------------------- /gtweak/tweaks/tweak_group_font.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2011 John Stowers 2 | # SPDX-License-Identifier: GPL-3.0+ 3 | # License-Filename: LICENSES/GPL-3.0 4 | 5 | 6 | 7 | from gtweak.widgets import TweakPreferencesPage, GSettingsTweakSpinRow, GSettingsTweakFontRow, TweakPreferencesGroup, TweaksCheckGroupActionRow 8 | 9 | class FontHintingTweak(TweaksCheckGroupActionRow): 10 | 11 | def __init__(self, **options): 12 | TweaksCheckGroupActionRow.__init__(self, 13 | title=_("Hinting"), 14 | setting="org.gnome.desktop.interface", 15 | key_name="font-hinting" 16 | ) 17 | 18 | self.add_row(title=_("Full"), key_name="full") 19 | self.add_row(title=_("Medium"), key_name="medium") 20 | self.add_row(title=_("Slight"), key_name="slight") 21 | self.add_row(title=_("None"), key_name="none") 22 | 23 | class FontAliasingTweak(TweaksCheckGroupActionRow): 24 | 25 | def __init__(self, **options): 26 | TweaksCheckGroupActionRow.__init__(self, 27 | title=_("Antialiasing"), 28 | setting="org.gnome.desktop.interface", 29 | key_name="font-antialiasing" 30 | ) 31 | 32 | self.add_row(title=_("Subpixel (for LCD screens)"), key_name="rgba") 33 | self.add_row(title=_("Standard (grayscale)"), key_name="grayscale") 34 | self.add_row(title=_("None"), key_name="none") 35 | 36 | 37 | TWEAK_GROUP = TweakPreferencesPage("fonts", _("Fonts"), 38 | TweakPreferencesGroup( 39 | _("Preferred Fonts"), "preferred-fonts", 40 | GSettingsTweakFontRow(_("Interface Text"),"org.gnome.desktop.interface", "font-name"), 41 | GSettingsTweakFontRow(_("Document Text"), "org.gnome.desktop.interface", "document-font-name"), 42 | GSettingsTweakFontRow(_("Monospace Text"), "org.gnome.desktop.interface", "monospace-font-name"), 43 | ), 44 | TweakPreferencesGroup( 45 | _("Rendering"), "font-rendering", 46 | FontHintingTweak(), 47 | FontAliasingTweak(), 48 | ), 49 | TweakPreferencesGroup( _("Size"), "font-size", 50 | GSettingsTweakSpinRow(_("Scaling Factor"), 51 | "org.gnome.desktop.interface", "text-scaling-factor", 52 | adjustment_step=0.01, digits=2), 53 | ), 54 | ) 55 | 56 | -------------------------------------------------------------------------------- /gtweak/tweaks/tweak_group_keyboard.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2011 John Stowers 2 | # SPDX-License-Identifier: GPL-3.0+ 3 | # License-Filename: LICENSES/GPL-3.0 4 | 5 | import gi 6 | gi.require_version("GnomeDesktop", "4.0") 7 | from gi.repository import Gtk, GnomeDesktop, Gtk 8 | 9 | from gtweak.gshellwrapper import GnomeShellFactory 10 | from gtweak.widgets import TweakPreferencesPage, GSettingsTweakSwitchRow, GSettingsSwitchTweakValue, _GSettingsTweak, TweakPreferencesGroup, build_label_beside_widget, Tweak 11 | from gtweak.tweakmodel import Tweak, TweakGroup 12 | from gtweak.gsettings import GSettingsSetting, GSettingsMissingError 13 | 14 | 15 | 16 | _shell = GnomeShellFactory().get_shell() 17 | _shell_loaded = _shell is not None 18 | 19 | 20 | class _XkbOption(Gtk.Expander, Tweak): 21 | def __init__(self, group_id, parent_settings, xkb_info, **options): 22 | try: 23 | desc = xkb_info.description_for_group(group_id) 24 | except AttributeError: 25 | desc = group_id 26 | Gtk.Expander.__init__(self) 27 | Tweak.__init__(self, desc, desc, **options) 28 | 29 | self.set_label(self.title) 30 | vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) 31 | vbox.set_margin_start(15) 32 | self.set_child(vbox) 33 | 34 | self._multiple_selection = group_id not in { 'keypad', 'kpdl', 'caps', 'altwin', 'nbsp', 'esperanto' } 35 | self._group_id = group_id 36 | self._parent_settings = parent_settings 37 | self._xkb_info = xkb_info 38 | self._possible_values = [] 39 | 40 | model_values = [] 41 | if not self._multiple_selection: 42 | model_values.append((None, _("Default"))) 43 | 44 | for option_id in self._xkb_info.get_options_for_group(group_id): 45 | desc = self._xkb_info.description_for_option(group_id, option_id) 46 | model_values.append((option_id, desc)) 47 | self._possible_values.append(option_id) 48 | 49 | def values_cmp_py3_wrap(f): 50 | ''' https://docs.python.org/3/howto/sorting.html#the-old-way-using-the-cmp-parameter ''' 51 | class C: 52 | def __init__(self, obj, *args): 53 | self.obj = obj 54 | def __lt__(self, other): 55 | return f(self.obj, other.obj) < 0 56 | def __gt__(self, other): 57 | return f(self.obj, other.obj) > 0 58 | def __eq__(self, other): 59 | return f(self.obj, other.obj) == 0 60 | def __le__(self, other): 61 | return f(self.obj, other.obj) <= 0 62 | def __ge__(self, other): 63 | return f(self.obj, other.obj) >= 0 64 | def __ne__(self, other): 65 | return f(self.obj, other.obj) != 0 66 | return C 67 | 68 | def values_cmp(xxx_todo_changeme, xxx_todo_changeme1): 69 | (av, ad) = xxx_todo_changeme 70 | (bv, bd) = xxx_todo_changeme1 71 | if not av: 72 | return -1 73 | elif not bv: 74 | return 1 75 | else: 76 | return (ad > bd) - (ad < bd) 77 | model_values.sort(key=values_cmp_py3_wrap(values_cmp)) 78 | 79 | self._widgets = dict() 80 | for (val, name) in model_values: 81 | w = Gtk.CheckButton.new_with_label(name) 82 | if not self._multiple_selection: 83 | w.set_group(self._widgets.get(None)) 84 | 85 | self._widgets[val] = w 86 | vbox.append(w) 87 | w._changed_id = w.connect('toggled', self._on_toggled) 88 | w._val = val 89 | 90 | self.widget_for_size_group = None 91 | self.reload() 92 | 93 | def reload(self): 94 | self._values = [] 95 | for v in self._parent_settings.get_strv(TypingTweakGroup.XKB_GSETTINGS_NAME): 96 | if (v in self._possible_values): 97 | self._values.append(v) 98 | 99 | self._update_checks() 100 | 101 | def _update_checks(self): 102 | if len(self._values) > 0: 103 | self.set_label(''+self.title+'') 104 | self.set_use_markup(True) 105 | else: 106 | self.set_label(self.title) 107 | 108 | def _set_active(w, active): 109 | w.disconnect(w._changed_id) 110 | w.set_active(active) 111 | w._changed_id = w.connect('toggled', self._on_toggled) 112 | 113 | if not self._multiple_selection: 114 | if len(self._values) > 0: 115 | w = self._widgets.get(self._values[0]) 116 | if w: 117 | _set_active(w, True) 118 | else: 119 | for w in list(self._widgets.values()): 120 | if w._val in self._values: 121 | _set_active(w, True) 122 | else: 123 | _set_active(w, False) 124 | 125 | def _on_toggled(self, w): 126 | active = w.get_active() 127 | if not self._multiple_selection and active: 128 | for v in self._values: 129 | self._parent_settings.setting_remove_from_list(TypingTweakGroup.XKB_GSETTINGS_NAME, v) 130 | 131 | if w._val in self._values and not active: 132 | self._parent_settings.setting_remove_from_list(TypingTweakGroup.XKB_GSETTINGS_NAME, w._val) 133 | elif active and not w._val in self._values and w._val: 134 | self._parent_settings.setting_add_to_list(TypingTweakGroup.XKB_GSETTINGS_NAME, w._val) 135 | 136 | class TypingTweakGroup(Gtk.Box): 137 | 138 | XKB_GSETTINGS_SCHEMA = "org.gnome.desktop.input-sources" 139 | XKB_GSETTINGS_NAME = "xkb-options" 140 | # grp_led is unsupported 141 | XKB_OPTIONS_BLACKLIST = {"grp_led", "Compose key"} 142 | 143 | def __init__(self): 144 | Gtk.Box.__init__(self, orientation=Gtk.Orientation.VERTICAL, spacing=3) 145 | self._option_objects = [] 146 | ok = False 147 | try: 148 | self._kbdsettings = GSettingsSetting(self.XKB_GSETTINGS_SCHEMA) 149 | self._kdb_settings_id = self._kbdsettings.connect("changed::"+self.XKB_GSETTINGS_NAME, self._on_changed) 150 | self._xkb_info = GnomeDesktop.XkbInfo() 151 | ok = True 152 | self.loaded = True 153 | except GSettingsMissingError: 154 | logging.info("Typing missing schema %s" % self.XKB_GSETTINGS_SCHEMA) 155 | self.loaded = False 156 | except AttributeError: 157 | logging.warning("Typing missing GnomeDesktop.gir with Xkb support") 158 | self.loaded = False 159 | finally: 160 | if ok: 161 | for opt in set(self._xkb_info.get_all_option_groups()) - self.XKB_OPTIONS_BLACKLIST: 162 | obj = _XkbOption(opt, self._kbdsettings, self._xkb_info) 163 | self._option_objects.append(obj) 164 | self._option_objects.sort(key=lambda item_desc: item_desc.title) 165 | for item in self._option_objects: 166 | self.append(item) 167 | TweakGroup.__init__(self, _("Typing"), *self._option_objects) 168 | 169 | self.connect("destroy", self._on_destroy) 170 | 171 | def _on_changed(self, *args): 172 | for obj in self._option_objects: 173 | obj.reload() 174 | 175 | def _on_destroy(self, event): 176 | if (self._kdb_settings_id): 177 | self._kbdsettings.disconnect(self._kdb_settings_id) 178 | 179 | 180 | class KeyThemeSwitcher(GSettingsSwitchTweakValue): 181 | def __init__(self, **options): 182 | GSettingsSwitchTweakValue.__init__(self, 183 | _("Emacs Input"), 184 | "org.gnome.desktop.interface", 185 | "gtk-key-theme", 186 | desc=_("Overrides shortcuts to use keybindings from the Emacs editor."), 187 | **options) 188 | 189 | def get_active(self): 190 | return "Emacs" in self.settings.get_string(self.key_name) 191 | 192 | def set_active(self, v): 193 | if v: 194 | self.settings.set_string(self.key_name, "Emacs") 195 | else: 196 | self.settings.set_string(self.key_name, "Default") 197 | 198 | 199 | class OverviewShortcutTweak(Gtk.Box, _GSettingsTweak): 200 | 201 | def __init__(self, **options): 202 | name = _("Overview Shortcut") 203 | Gtk.Box.__init__(self, orientation=Gtk.Orientation.HORIZONTAL, spacing=0) 204 | _GSettingsTweak.__init__(self, name, "org.gnome.mutter", "overlay-key", loaded=_shell_loaded, **options) 205 | 206 | box_btn = Gtk.Box() 207 | box_btn.set_homogeneous(True) 208 | box_btn.add_css_class("linked") 209 | 210 | btn1 = Gtk.ToggleButton.new_with_label(_("Left Super")) 211 | btn2 = Gtk.ToggleButton.new_with_label(_("Right Super")) 212 | btn2.set_group(btn1) 213 | 214 | if self.settings.get_string(self.key_name) == "Super_R": 215 | btn2.set_active(True) 216 | elif self.settings.get_string(self.key_name) == "Super_L": 217 | btn1.set_active(True) 218 | 219 | btn1.connect("toggled", self.on_button_toggled, "Super_L") 220 | btn2.connect("toggled", self.on_button_toggled, "Super_R") 221 | 222 | box_btn.append(btn1) 223 | box_btn.append(btn2) 224 | build_label_beside_widget(name, box_btn, hbox=self) 225 | 226 | def on_button_toggled(self, button, key): 227 | self.settings[self.key_name] = key 228 | 229 | 230 | class AdditionalLayoutButton(Gtk.Box, Tweak): 231 | 232 | def __init__(self): 233 | Gtk.Box.__init__(self, orientation=Gtk.Orientation.VERTICAL, spacing=18, 234 | valign=Gtk.Align.CENTER) 235 | Tweak.__init__(self, "extensions", "") 236 | 237 | btn = Gtk.Button(label=_("Additional Layout Options"), halign=Gtk.Align.END) 238 | btn.connect("clicked", self._on_browse_clicked) 239 | self.append(btn) 240 | 241 | def _on_browse_clicked(self, btn): 242 | dialog = Gtk.Dialog() 243 | dialog.set_title(_("Additional Layout Options")) 244 | dialog.set_transient_for(self.main_window) 245 | dialog.set_modal(True) 246 | dialog.set_size_request(500, 500) 247 | 248 | scrolled_window = Gtk.ScrolledWindow() 249 | scrolled_window.set_margin_top(10) 250 | scrolled_window.set_margin_start(10) 251 | box = TypingTweakGroup() 252 | scrolled_window.set_child(box) 253 | 254 | dialog.set_child(scrolled_window) 255 | dialog.show() 256 | 257 | 258 | TWEAK_GROUP = TweakPreferencesPage("keyboard", _("Keyboard"), 259 | GSettingsTweakSwitchRow(_("Show Extended Input Sources"), 260 | "org.gnome.desktop.input-sources", 261 | "show-all-sources", 262 | desc=_("Increases the choice of input sources in the Settings application."), 263 | logout_required=True,), 264 | TweakPreferencesGroup( 265 | _("Layout"), "keyboard-layout", 266 | 267 | KeyThemeSwitcher(), 268 | OverviewShortcutTweak(), 269 | AdditionalLayoutButton(), 270 | ) 271 | 272 | ) 273 | -------------------------------------------------------------------------------- /gtweak/tweaks/tweak_group_mouse.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2011 John Stowers 2 | # SPDX-License-Identifier: GPL-3.0+ 3 | # License-Filename: LICENSES/GPL-3.0 4 | 5 | from gi.repository import GDesktopEnums 6 | from gtweak.devicemanager import pointing_stick_is_present, touchpad_is_present 7 | 8 | from gtweak.widgets import (GSettingsTweakComboRow, TweakPreferencesPage, GSettingsTweakSwitchRow, GSettingsSwitchTweakValue, TweakPreferencesGroup) 9 | 10 | class KeyThemeSwitcher(GSettingsSwitchTweakValue): 11 | def __init__(self, **options): 12 | GSettingsSwitchTweakValue.__init__(self, 13 | _("Emacs Input"), 14 | "org.gnome.desktop.interface", 15 | "gtk-key-theme", 16 | desc=_("Overrides shortcuts to use keybindings from the Emacs editor."), 17 | **options) 18 | 19 | def get_active(self): 20 | return "Emacs" in self.settings.get_string(self.key_name) 21 | 22 | def set_active(self, v): 23 | if v: 24 | self.settings.set_string(self.key_name, "Emacs") 25 | else: 26 | self.settings.set_string(self.key_name, "Default") 27 | 28 | class ClickMethod(GSettingsSwitchTweakValue): 29 | 30 | def __init__(self, **options): 31 | title = _("Disable Secondary Click") 32 | desc = _("Disables secondary clicks on touchpads which do not have a physical secondary button") 33 | 34 | GSettingsSwitchTweakValue.__init__(self, 35 | title=title, 36 | schema_name="org.gnome.desktop.peripherals", 37 | schema_child_name="touchpad", 38 | schema_id="org.gnome.desktop.peripherals.touchpad", 39 | key_name="click-method", 40 | desc=desc, 41 | **options) 42 | 43 | def get_active(self): 44 | return self.settings.get_enum(self.key_name) == GDesktopEnums.TouchpadClickMethod.NONE 45 | 46 | def set_active(self, v): 47 | if v: 48 | self.settings.set_enum(self.key_name, GDesktopEnums.TouchpadClickMethod.NONE) 49 | else: 50 | self.settings.reset(self.key_name) 51 | 52 | 53 | class PointerAccelProfile(GSettingsSwitchTweakValue): 54 | 55 | def __init__(self, title, description, peripheral_type, **options): 56 | GSettingsSwitchTweakValue.__init__(self, 57 | title=title, 58 | schema_name="org.gnome.desktop.peripherals", 59 | schema_id=f"org.gnome.desktop.peripherals.{peripheral_type}", 60 | schema_child_name=peripheral_type, 61 | key_name="accel-profile", 62 | desc=description, 63 | **options) 64 | 65 | def get_active(self): 66 | return self.settings.get_enum(self.key_name) != GDesktopEnums.PointerAccelProfile.FLAT 67 | 68 | def set_active(self, v): 69 | if not v: 70 | self.settings.set_enum(self.key_name, GDesktopEnums.PointerAccelProfile.FLAT) 71 | else: 72 | self.settings.reset(self.key_name) 73 | 74 | 75 | _tweaks = [ 76 | TweakPreferencesGroup(_("Mouse"), "mouse", 77 | GSettingsTweakSwitchRow(_("Middle Click Paste"), 78 | "org.gnome.desktop.interface", 79 | "gtk-enable-primary-paste"), 80 | ), 81 | ] 82 | 83 | if touchpad_is_present(): 84 | _tweaks += [ 85 | TweakPreferencesGroup(_("Touchpad"), "touchpad", 86 | PointerAccelProfile( 87 | title=_("Touchpad Acceleration"), 88 | description=_("Turning acceleration off can allow faster and more precise movements, but can also make the touchpad more difficult to use."), 89 | peripheral_type="touchpad", 90 | ), 91 | ClickMethod(), 92 | ), 93 | ] 94 | 95 | if pointing_stick_is_present(): 96 | _tweaks += [ 97 | TweakPreferencesGroup(_("Pointing Stick"), "pointing-stick", 98 | PointerAccelProfile( 99 | title=_("Pointing Stick Acceleration"), 100 | description=_("Turning acceleration off can allow faster and more precise movements, but can also make the pointing stick more difficult to use."), 101 | peripheral_type="pointingstick", 102 | ), 103 | GSettingsTweakComboRow( 104 | title=_("Scrolling Method"), 105 | schema_name="org.gnome.desktop.peripherals", 106 | schema_child_name="pointingstick", 107 | schema_id="org.gnome.desktop.peripherals.pointingstick", 108 | key_name="scroll-method", 109 | ), 110 | ), 111 | ] 112 | 113 | TWEAK_GROUP = TweakPreferencesPage("mouse", _("Mouse & Touchpad"), *_tweaks) -------------------------------------------------------------------------------- /gtweak/tweaks/tweak_group_sound.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2011 John Stowers 2 | # SPDX-License-Identifier: GPL-3.0+ 3 | # License-Filename: LICENSES/GPL-3.0 4 | 5 | import os 6 | import os.path 7 | import configparser 8 | 9 | 10 | from gtweak.utils import get_resource_dirs 11 | from gtweak.widgets import TweakPreferencesPage, GSettingsTweakComboRow 12 | 13 | 14 | def get_theme_name(index_path): 15 | """Given an index file path, gets the relevant sound theme's name.""" 16 | config = configparser.ConfigParser() 17 | config.read(index_path) 18 | return config["Sound Theme"]["Name"] 19 | 20 | 21 | def get_sound_themes(): 22 | """Gets the available sound themes as a (theme_directory_name, theme_display_name) tuple list.""" 23 | themes = [] 24 | seen = set() 25 | for location in get_resource_dirs("sounds"): 26 | for item in os.listdir(location): 27 | candidate = os.path.join(location, item) 28 | index_file = os.path.join(candidate, "index.theme") 29 | if os.path.isdir(candidate) and os.path.exists(index_file): 30 | theme_info = (os.path.basename(candidate), get_theme_name(index_file)) 31 | if theme_info[1] not in seen: 32 | themes.append(theme_info) 33 | seen.add(theme_info[1]) 34 | return themes 35 | 36 | sound_themes = get_sound_themes() 37 | 38 | show_sound_tweaks = len(sound_themes) > 0 39 | 40 | TWEAK_GROUP = TweakPreferencesPage( 41 | "sound", 42 | _("Sound"), 43 | GSettingsTweakComboRow( 44 | _("System Sound Theme"), 45 | "org.gnome.desktop.sound", 46 | "theme-name", 47 | sound_themes, 48 | desc=_("Specifies which sound theme to use for sound events."), 49 | ), 50 | ) 51 | 52 | 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /gtweak/tweaks/tweak_group_startup.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2011 John Stowers 2 | # SPDX-License-Identifier: GPL-3.0+ 3 | # License-Filename: LICENSES/GPL-3.0 4 | 5 | import logging 6 | import os.path 7 | import subprocess 8 | from typing import Set, Optional 9 | 10 | from gi.repository import Adw, Gtk, Gdk, Gio, GObject, GLib 11 | 12 | from gtweak.tweakmodel import Tweak, TweakGroup 13 | from gtweak.utils import AutostartManager, AutostartFile 14 | 15 | 16 | def _image_from_gicon(gicon): 17 | image = Gtk.Image.new_from_gicon(gicon) 18 | image.set_icon_size(Gtk.IconSize.LARGE) 19 | return image 20 | 21 | 22 | class _AppChooserRow(Gtk.ListBoxRow): 23 | """The row in the appchooser to show desktop files """ 24 | 25 | def __init__(self, app_info: Gio.AppInfo, is_running: bool, **kwargs): 26 | super().__init__(**kwargs) 27 | self.app_name = app_info.get_name().lower() 28 | self.is_running = is_running 29 | self.app_info = app_info 30 | 31 | vbox = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=10) 32 | vbox.set_margin_top(10) 33 | vbox.set_margin_bottom(10) 34 | vbox.set_margin_start(10) 35 | vbox.set_margin_end(10) 36 | 37 | icon = app_info.get_icon() 38 | if icon: 39 | img = _image_from_gicon(icon) 40 | img.set_hexpand(False) 41 | else: 42 | img = Gtk.Image.new_from_icon_name("application-x-executable") 43 | img.props.icon_size = Gtk.IconSize.LARGE 44 | app_name = app_info.get_name() 45 | lbl = Gtk.Label(label=app_name, hexpand=True, 46 | halign=Gtk.Align.START, wrap=True) 47 | vbox.append(img) 48 | vbox.append(lbl) 49 | 50 | if is_running: 51 | lbl_running = Gtk.Label(label=_("running")) 52 | vbox.append(lbl_running) 53 | self.set_child(vbox) 54 | 55 | 56 | class _AppChooser(Gtk.Dialog): 57 | """Presents a dialog to select a desktop file """ 58 | 59 | def __init__(self, main_window, running_exes, startup_apps): 60 | uhb = Gtk.Settings.get_default().props.gtk_dialogs_use_header 61 | Gtk.Dialog.__init__(self, title=_("Select Application"), use_header_bar=uhb) 62 | 63 | self._running = {} 64 | self._all = {} 65 | 66 | # Build header bar buttons 67 | self.add_button(_("_Close"), Gtk.ResponseType.CANCEL) 68 | self.add_button(_("_Add"), Gtk.ResponseType.OK) 69 | self.set_default_response(Gtk.ResponseType.OK) 70 | 71 | self.entry = Gtk.SearchEntry( 72 | placeholder_text=_("Search Applications…")) 73 | self.entry.set_width_chars(30) 74 | self.entry.props.activates_default = True 75 | self.entry.connect("search-changed", self._on_search_entry_changed) 76 | 77 | self.searchbar = Gtk.SearchBar() 78 | self.searchbar.set_child(self.entry) 79 | self.searchbar.set_hexpand(True) 80 | self.searchbar.set_key_capture_widget(self) 81 | 82 | lb = Gtk.ListBox() 83 | lb.set_activate_on_single_click(False) 84 | lb.set_sort_func(self._list_sort_func, None) 85 | lb.set_filter_func(self._list_filter_func, self.entry) 86 | lb.connect("row-activated", 87 | lambda b, r: self.response(Gtk.ResponseType.OK) if r.get_mapped() else None) 88 | lb.connect("row-selected", self._on_row_selected) 89 | 90 | apps = filter(lambda _app: _app.should_show() and _app.get_id() not in startup_apps, 91 | Gio.app_info_get_all()) 92 | 93 | for app in apps: 94 | running = app.get_executable() in running_exes 95 | if not app.get_name(): 96 | continue 97 | w = _AppChooserRow(app, running) 98 | if w: 99 | lb.append(w) 100 | 101 | sw = Gtk.ScrolledWindow(vexpand=True, margin_top=2, margin_bottom=2, 102 | margin_start=2, margin_end=2) 103 | sw.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC) 104 | sw.set_child(lb) 105 | 106 | header_bar = self.get_header_bar() 107 | if header_bar: 108 | searchbtn = Gtk.ToggleButton(valign=Gtk.Align.CENTER) 109 | searchbtn.set_icon_name("edit-find-symbolic") 110 | header_bar.pack_end(searchbtn) 111 | self._binding = searchbtn.bind_property( 112 | "active", self.searchbar, 113 | "search-mode-enabled", GObject.BindingFlags.BIDIRECTIONAL) 114 | 115 | self.get_content_area().append(self.searchbar) 116 | self.get_content_area().append(sw) 117 | self.set_modal(True) 118 | self.set_transient_for(main_window) 119 | self.set_size_request(320, 300) 120 | 121 | self.listbox = lb 122 | self._setup_shortcut() 123 | 124 | def _setup_shortcut(self): 125 | s_trigger = Gtk.ShortcutTrigger.parse_string("f") 126 | s_action = Gtk.CallbackAction.new(lambda w, a, s: s.set_search_mode(True), self.searchbar) 127 | 128 | if s_trigger and s_action: 129 | shortcut = Gtk.Shortcut(trigger=s_trigger, action=s_action) 130 | self.add_shortcut(shortcut) 131 | 132 | @staticmethod 133 | def _list_sort_func(row_a: _AppChooserRow, row_b: _AppChooserRow, _): 134 | a_isrunning = row_a.is_running 135 | b_isrunning = row_b.is_running 136 | 137 | if a_isrunning and not b_isrunning: 138 | return -1 139 | elif not a_isrunning and b_isrunning: 140 | return 1 141 | else: 142 | aname = row_a.app_name 143 | bname = row_b.app_name 144 | 145 | if aname < bname: 146 | return -1 147 | elif aname > bname: 148 | return 1 149 | else: 150 | return 0 151 | 152 | @staticmethod 153 | def _list_filter_func(row: _AppChooserRow, entry: Gtk.SearchEntry) -> bool: 154 | txt = entry.get_text().lower() 155 | if txt in row.app_name: 156 | return True 157 | return False 158 | 159 | def _on_search_entry_changed(self, editable): 160 | self.listbox.invalidate_filter() 161 | selected = self.listbox.get_selected_row() 162 | if selected and selected.get_mapped(): 163 | self.set_response_sensitive(Gtk.ResponseType.OK, True) 164 | else: 165 | self.set_response_sensitive(Gtk.ResponseType.OK, False) 166 | 167 | def _on_row_selected(self, box, row): 168 | if row and row.get_mapped(): 169 | self.set_response_sensitive(Gtk.ResponseType.OK, True) 170 | else: 171 | self.set_response_sensitive(Gtk.ResponseType.OK, False) 172 | 173 | def get_selected_appinfo(self) -> Optional[Gio.AppInfo]: 174 | row: _AppChooserRow = self.listbox.get_selected_row() 175 | 176 | if row is not None: 177 | return row.app_info 178 | return None 179 | 180 | 181 | class _StartupAppRowTweak(Adw.ActionRow, Tweak): 182 | 183 | def __init__(self, desktop_info: Gio.AppInfo, **options): 184 | Adw.PreferencesRow.__init__(self) 185 | Tweak.__init__(self, desktop_info.get_name(), desktop_info.get_description(), **options) 186 | 187 | icon = desktop_info.get_icon() 188 | if icon: 189 | app_icon = _image_from_gicon(icon) 190 | else: 191 | app_icon = Gtk.Image.new_from_icon_name("image-missing") 192 | app_icon.set_icon_size(Gtk.IconSize.LARGE) 193 | 194 | self.btn = Gtk.Button(icon_name="edit-delete-symbolic") 195 | self.btn.set_tooltip_text(_("Remove")) 196 | 197 | self.btn.add_css_class("flat") 198 | self.btn.set_vexpand(False) 199 | self.btn.set_valign(Gtk.Align.CENTER) 200 | 201 | self.set_title(desktop_info.get_name()) 202 | self.add_prefix(app_icon) 203 | self.add_suffix(self.btn) 204 | self.app_info = desktop_info 205 | 206 | controller_key = Gtk.EventControllerKey() 207 | self.add_controller(controller_key) 208 | controller_key.connect("key-pressed", self._on_key_press_event) 209 | 210 | def _on_key_press_event(self, _, keyval: int, _1, _2): 211 | if keyval in [Gdk.KEY_Delete, Gdk.KEY_KP_Delete, Gdk.KEY_BackSpace]: 212 | self.btn.activate() 213 | return True 214 | return False 215 | 216 | 217 | class AutostartTweakGroup(Adw.PreferencesPage, TweakGroup): 218 | 219 | def __init__(self, *tweaks, **options): 220 | name: str = _("Startup Applications") 221 | desc: str = _("Startup applications are automatically started when you log in.") 222 | Adw.PreferencesPage.__init__(self) 223 | TweakGroup.__init__(self, "startup-applications", name, **options) 224 | 225 | self.tweaks = [Tweak(name, desc, **options)] 226 | 227 | pregroup = Adw.PreferencesGroup() 228 | # Preferencee Group Header 229 | pregroup.set_title(name) 230 | pregroup.set_description(desc) 231 | 232 | self.btn_add_startup = Gtk.Button(valign=Gtk.Align.CENTER) 233 | self.btn_add_startup.set_icon_name("list-add-symbolic") 234 | self.btn_add_startup.add_css_class("flat") 235 | pregroup.set_header_suffix(self.btn_add_startup) 236 | 237 | # Body 238 | self.stack = Gtk.Stack() 239 | self.status_page = Adw.StatusPage() 240 | self.pg_startup_apps = Adw.PreferencesGroup() 241 | self._setup_stack_view() 242 | 243 | self._startup_dapps = self._get_startup_desktop_files() 244 | self._setup_startup_app_row() 245 | 246 | self.__init_connections() 247 | pregroup.add(self.stack) 248 | self.add(pregroup) 249 | self._set_visible_page() 250 | 251 | def _setup_stack_view(self): 252 | self.stack.set_vexpand(True) 253 | self.stack.set_hhomogeneous(True) 254 | self.stack.set_transition_type(Gtk.StackTransitionType.CROSSFADE) 255 | 256 | # Empty Page 257 | self.status_page.set_icon_name("application-x-executable-symbolic") 258 | self.status_page.set_title(_("No Startup Applications")) 259 | self.status_page.set_description(_("Add a startup application")) 260 | self.status_page.add_css_class("dim-label") 261 | 262 | self.stack.add_child(self.status_page) 263 | self.stack.add_child(self.pg_startup_apps) 264 | 265 | def _setup_startup_app_row(self): 266 | """ Add a row for each autostart applications existing""" 267 | 268 | dfiles = self._startup_dapps 269 | for dfile in dfiles: 270 | app_row = _StartupAppRowTweak(dfile) 271 | app_row.btn.connect("clicked", self._on_remove_clicked, app_row) 272 | self.pg_startup_apps.add(app_row) 273 | self.tweaks.append(app_row) 274 | 275 | def _set_visible_page(self): 276 | if len(self._startup_dapps) > 0: 277 | self.stack.set_visible_child(self.pg_startup_apps) 278 | else: 279 | self.stack.set_visible_child(self.status_page) 280 | 281 | def _on_add_clicked(self, _: Gtk.Button): 282 | def _on_response_appchooser(chooser: _AppChooser, response_id: int): 283 | if response_id == Gtk.ResponseType.OK: 284 | appinfo = chooser.get_selected_appinfo() 285 | 286 | if appinfo: 287 | AutostartFile(appinfo).update_start_at_login(True) 288 | arow_app_row = _StartupAppRowTweak(appinfo) 289 | arow_app_row.btn.connect("clicked", self._on_remove_clicked, arow_app_row) 290 | 291 | self.pg_startup_apps.add(arow_app_row) 292 | self._startup_dapps.add(appinfo) 293 | self._set_visible_page() 294 | chooser.destroy() 295 | 296 | startup_app_ids = tuple(map(lambda x: x.get_id(), self._startup_dapps)) 297 | 298 | Gio.Application.get_default().mark_busy() 299 | a = _AppChooser(self.main_window, self._get_running_executables(), startup_app_ids) 300 | a.connect("response", _on_response_appchooser) 301 | Gio.Application.get_default().unmark_busy() 302 | a.present() 303 | 304 | def _on_remove_clicked(self, btn, app_row: _StartupAppRowTweak): 305 | app_info = app_row.app_info 306 | AutostartFile(app_info).update_start_at_login(False) 307 | 308 | self.pg_startup_apps.remove(app_row) 309 | self._startup_dapps.remove(app_info) 310 | self._set_visible_page() 311 | 312 | def __init_connections(self): 313 | self.btn_add_startup.connect("clicked", self._on_add_clicked) 314 | 315 | @staticmethod 316 | def _get_startup_desktop_files(): 317 | asm = AutostartManager() 318 | autostart_files = asm.get_user_autostart_files() 319 | dfiles = set() 320 | 321 | for file in autostart_files: 322 | try: 323 | dappinfo = Gio.DesktopAppInfo.new_from_filename(file) 324 | except TypeError: 325 | logging.warning(f"Error loading desktop file: {file}") 326 | else: 327 | if not AutostartFile(dappinfo).is_start_at_login_enabled(): 328 | continue 329 | dfiles.add(dappinfo) 330 | 331 | return dfiles 332 | 333 | @staticmethod 334 | def _get_running_executables() -> Set[str]: 335 | exes = set() 336 | cmd = subprocess.Popen([ 337 | 'ps', '-e', '-w', '-w', '-U', 338 | str(os.getuid()), '-o', 'cmd'], 339 | stdout=subprocess.PIPE) 340 | out = cmd.communicate()[0] 341 | for process in out.decode('utf8').split('\n'): 342 | exe = process.split(' ')[0] 343 | if exe and exe[0] != '[': # kernel process 344 | exes.add(os.path.basename(exe)) 345 | 346 | return exes 347 | 348 | TWEAK_GROUP = AutostartTweakGroup() 349 | -------------------------------------------------------------------------------- /gtweak/tweaks/tweak_group_windows.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2011 John Stowers 2 | # SPDX-License-Identifier: GPL-3.0+ 3 | # License-Filename: LICENSES/GPL-3.0 4 | 5 | from gtweak.widgets import UI_BOX_HORIZONTAL_SPACING, UI_BOX_SPACING, _GSettingsTweak, GSettingsSwitchTweakValue, TweakPreferencesPage, GSettingsTweakComboRow, GSettingsTweakSwitchRow, TweakPreferencesGroup, TweaksCheckGroupActionRow, build_label_beside_widget 6 | 7 | from gi.repository import Gtk 8 | 9 | 10 | class Focus(TweaksCheckGroupActionRow): 11 | 12 | def __init__(self): 13 | name: str = _("Window Focus") 14 | desc: str = _("Click to Focus") 15 | TweaksCheckGroupActionRow.__init__(self, title=name, subtitle=desc, setting="org.gnome.desktop.wm.preferences", key_name="focus-mode", name="focus") 16 | 17 | self.add_row( 18 | key_name="click", title=_("Click to Focus"), 19 | subtitle=_( 20 | "Windows are focused when they are clicked.")) 21 | 22 | self.add_row( 23 | key_name="sloppy", title=_("Focus on Hover"), 24 | subtitle=_( 25 | "Window is focused when hovered with the pointer. Windows remain focused when the desktop is hovered.")) 26 | 27 | self.add_row( 28 | key_name="mouse", title=_("Focus Follows Mouse"), 29 | subtitle=_( 30 | "Window is focused when hovered with the pointer. Hovering the desktop removes focus " 31 | "from the previous window.")) 32 | 33 | 34 | depends_how = lambda x,kn: x.get_string(kn) in ("mouse", "sloppy") 35 | 36 | class ShowWindowButtons(GSettingsSwitchTweakValue): 37 | 38 | def __init__(self, name, value, **options): 39 | self.value = value 40 | GSettingsSwitchTweakValue.__init__(self, 41 | name, 42 | "org.gnome.desktop.wm.preferences", 43 | "button-layout", 44 | **options) 45 | def get_active(self): 46 | return self.value in self.settings.get_string(self.key_name) 47 | 48 | def set_active(self, v): 49 | val = self.settings.get_string(self.key_name) 50 | (left, colon, right) = val.partition(":") 51 | 52 | if "close" in right: 53 | rsplit = right.split(",") 54 | rsplit = [x for x in rsplit if x in ['appmenu', 'minimize', 'maximize', 'close']] 55 | 56 | if v: 57 | rsplit.append(self.value) 58 | else: 59 | rsplit.remove(self.value) 60 | 61 | rsplit.sort(key=lambda x: ["appmenu", "minimize", "maximize", "close"].index(x)) 62 | 63 | self.settings.set_string(self.key_name, left + colon + ",".join(rsplit)) 64 | 65 | else: 66 | rsplit = left.split(",") 67 | rsplit = [x for x in rsplit if x in ['appmenu', 'minimize', 'maximize', 'close']] 68 | 69 | if v: 70 | rsplit.append(self.value) 71 | else: 72 | rsplit.remove(self.value) 73 | 74 | rsplit.sort(key=lambda x: ["close", "minimize", "maximize", "appmenu"].index(x)) 75 | 76 | self.settings.set_string(self.key_name, ",".join(rsplit) + colon + right) 77 | 78 | class PlaceWindowButtons(Gtk.Box, _GSettingsTweak): 79 | 80 | def __init__(self, **options): 81 | name = _("Placement") 82 | Gtk.Box.__init__(self, orientation=Gtk.Orientation.HORIZONTAL, margin_start=UI_BOX_HORIZONTAL_SPACING, margin_end=UI_BOX_HORIZONTAL_SPACING, 83 | margin_top=UI_BOX_SPACING, margin_bottom=UI_BOX_SPACING,spacing=0) 84 | 85 | _GSettingsTweak.__init__(self, 86 | name, 87 | "org.gnome.desktop.wm.preferences", 88 | "button-layout", 89 | **options) 90 | 91 | box_btn = Gtk.Box() 92 | box_btn.set_homogeneous(True) 93 | box_btn.add_css_class("linked") 94 | 95 | # Translators: For RTL languages, this is the "Right" direction since the 96 | # interface is flipped 97 | btn1 = Gtk.ToggleButton.new_with_label(_("Left")) 98 | # Translators: For RTL languages, this is the "Left" direction since the 99 | # interface is flipped 100 | btn2 = Gtk.ToggleButton.new_with_label(_("Right")) 101 | btn2.set_group(btn1) 102 | 103 | val = self.settings.get_string(self.key_name) 104 | (left, colon, right) = val.partition(":") 105 | if "close" in right: 106 | btn2.set_active(True) 107 | elif "close" in left: 108 | btn1.set_active(True) 109 | btn2.connect("toggled", self.on_button_toggled) 110 | 111 | box_btn.append(btn1) 112 | box_btn.append(btn2) 113 | 114 | build_label_beside_widget(name, box_btn, hbox=self) 115 | 116 | def on_button_toggled(self, v): 117 | val = self.settings.get_string(self.key_name) 118 | (left, colon, right) = val.partition(":") 119 | 120 | if "close" in left: 121 | rsplit = left.split(",") 122 | rsplit = [x for x in rsplit if x in ['appmenu', 'minimize', 'maximize', 'close']] 123 | rsplit.sort(key=lambda x: ["appmenu", "minimize", "maximize", "close"].index(x)) 124 | self.settings.set_string(self.key_name, right + colon + ",".join(rsplit)) 125 | else: 126 | rsplit = right.split(",") 127 | rsplit = [x for x in rsplit if x in ['appmenu', 'minimize', 'maximize', 'close']] 128 | rsplit.sort(key=lambda x: ["close", "minimize", "maximize", "appmenu"].index(x)) 129 | self.settings.set_string(self.key_name, ",".join(rsplit) + colon + left) 130 | 131 | 132 | TWEAK_GROUP = TweakPreferencesPage( 133 | "window-management", _("Windows"), 134 | TweakPreferencesGroup( 135 | _("Titlebar Actions"), "title-titlebar-actions", 136 | GSettingsTweakComboRow(_("Double-Click"),"org.gnome.desktop.wm.preferences", "action-double-click-titlebar"), 137 | GSettingsTweakComboRow(_("Middle-Click"),"org.gnome.desktop.wm.preferences", "action-middle-click-titlebar"), 138 | GSettingsTweakComboRow(_("Secondary-Click"),"org.gnome.desktop.wm.preferences", "action-right-click-titlebar"), 139 | ), 140 | TweakPreferencesGroup(_("Titlebar Buttons"), "title-theme", 141 | ShowWindowButtons(_("Maximize"), "maximize"), 142 | ShowWindowButtons(_("Minimize"), "minimize"), 143 | PlaceWindowButtons() 144 | ), 145 | TweakPreferencesGroup( 146 | _("Click Actions"), "title-window-behavior", 147 | GSettingsTweakSwitchRow(_("Attach Modal Dialogs"),"org.gnome.mutter", "attach-modal-dialogs", 148 | desc=_("When on, modal dialog windows are attached to their parent windows, and cannot be moved.")), 149 | GSettingsTweakSwitchRow(_("Center New Windows"),"org.gnome.mutter", "center-new-windows"), 150 | GSettingsTweakComboRow(_("Window Action Key"), 151 | "org.gnome.desktop.wm.preferences", 152 | "mouse-button-modifier", 153 | [("disabled", _("Disabled")), ("", "Alt"), ("", "Super")]), 154 | 155 | 156 | GSettingsTweakSwitchRow(_("Resize with Secondary-Click"),"org.gnome.desktop.wm.preferences", "resize-with-right-button"), 157 | ), 158 | 159 | TweakPreferencesGroup(_("Window Focus"), "window-focus", 160 | Focus(), 161 | GSettingsTweakSwitchRow(_("Raise Windows When Focused"),"org.gnome.desktop.wm.preferences", "auto-raise", depends_on=Focus(), depends_how=depends_how)) 162 | ) 163 | 164 | -------------------------------------------------------------------------------- /gtweak/tweakview.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2011 John Stowers 2 | # SPDX-License-Identifier: GPL-3.0+ 3 | # License-Filename: LICENSES/GPL-3.0 4 | 5 | import os.path 6 | from typing import List 7 | 8 | from gi.repository import Adw, Gtk, Gdk, GObject 9 | import gtweak 10 | import gtweak.widgets 11 | import gtweak.tweakmodel 12 | from gtweak.tweakmodel import TweakModel, string_for_search 13 | 14 | from gtweak.tweaks.tweak_group_appearance import TWEAK_GROUP as AppearanceTweaks 15 | from gtweak.tweaks.tweak_group_font import TWEAK_GROUP as FontTweaks 16 | from gtweak.tweaks.tweak_group_mouse import TWEAK_GROUP as MouseTweaks 17 | from gtweak.tweaks.tweak_group_keyboard import TWEAK_GROUP as KeyboardTweaks 18 | from gtweak.tweaks.tweak_group_sound import TWEAK_GROUP as SoundTweaks, show_sound_tweaks 19 | from gtweak.tweaks.tweak_group_windows import TWEAK_GROUP as WindowTweaks 20 | from gtweak.tweaks.tweak_group_startup import TWEAK_GROUP as StartupApplicationTweaks 21 | 22 | tweaks = [ 23 | MouseTweaks, 24 | KeyboardTweaks, 25 | FontTweaks, 26 | AppearanceTweaks, 27 | WindowTweaks, 28 | StartupApplicationTweaks, 29 | SoundTweaks 30 | ] 31 | 32 | 33 | @Gtk.Template(filename=os.path.join(gtweak.PKG_DATA_DIR, "tweaks.ui")) 34 | class Window(Adw.ApplicationWindow): 35 | __gtype_name__ = "GTweakWindow" 36 | 37 | main_box = Gtk.Template.Child() 38 | listbox = Gtk.Template.Child() 39 | right_box = Gtk.Template.Child() 40 | header = Gtk.Template.Child() 41 | main_content_scroll = Gtk.Template.Child() 42 | main_leaflet = Gtk.Template.Child() 43 | main_stack = Gtk.Template.Child() 44 | left_box = Gtk.Template.Child() 45 | searchbar = Gtk.Template.Child() 46 | entry = Gtk.Template.Child() 47 | list_box_row_sound = Gtk.Template.Child() 48 | 49 | def __init__(self, app: Adw.Application, model: TweakModel): 50 | super().__init__(application=app, show_menubar=False) 51 | self.set_default_size(980, 640) 52 | self.set_size_request(-1, 300) 53 | self.set_icon_name(gtweak.APP_ID) 54 | 55 | for tweak in tweaks: 56 | model.add_tweak_group(tweak, self) 57 | if tweak.name: 58 | self.main_stack.add_named(tweak, tweak.name) 59 | 60 | self._setup_header() 61 | self._setup_sidebar() 62 | self._setup_mainbox() 63 | self._setup_sizegroups() 64 | 65 | self._load_css() 66 | self._model = model 67 | 68 | self._setup_shortcut() 69 | self.searchbar.set_key_capture_widget(self) 70 | 71 | 72 | def _setup_header(self): 73 | self.hsize_group = Gtk.SizeGroup(mode=Gtk.SizeGroupMode.HORIZONTAL) 74 | self.menu_btn = Gtk.MenuButton() 75 | 76 | header = self.header 77 | header.set_transition_type(Adw.LeafletTransitionType.SLIDE) 78 | 79 | left_header = Adw.HeaderBar() 80 | self._left_header = left_header 81 | left_header.set_show_end_title_buttons(False) 82 | left_header.add_css_class("titlebar") 83 | left_header.add_css_class("tweak-titlebar-left") 84 | 85 | self.search_btn = Gtk.ToggleButton() # Search Button 86 | self.search_btn.set_icon_name("edit-find-symbolic") 87 | self.search_btn.bind_property("active", self.searchbar, "search-mode-enabled", 88 | GObject.BindingFlags.BIDIRECTIONAL) 89 | self.search_btn.set_valign(Gtk.Align.CENTER) 90 | self.search_btn.add_css_class("image-button") 91 | left_header.pack_start(self.search_btn) 92 | 93 | lbl = Adw.WindowTitle.new(_("Tweaks"), "") # Left label 94 | left_header.set_title_widget(lbl) 95 | 96 | self.builder = Gtk.Builder() 97 | assert(os.path.exists(gtweak.PKG_DATA_DIR)) 98 | filename = os.path.join(gtweak.PKG_DATA_DIR, 'shell.ui') 99 | self.builder.add_from_file(filename) 100 | 101 | appmenu = self.builder.get_object('appmenu') # Left AppMenu 102 | self.menu_btn.set_icon_name("open-menu-symbolic") 103 | self.menu_btn.set_menu_model(appmenu) 104 | left_header.pack_end(self.menu_btn) 105 | 106 | # Right Header 107 | right_header = Adw.HeaderBar(hexpand=True) 108 | self._right_header = right_header 109 | right_header.set_show_start_title_buttons(False) 110 | 111 | right_header.add_css_class("titlebar") 112 | right_header.add_css_class("tweak-titlebar-right") 113 | 114 | self._group_titlebar_widget = None 115 | 116 | self.title = Adw.WindowTitle.new("", "") 117 | right_header.set_title_widget(self.title) 118 | 119 | self.back_button = Gtk.Button.new_from_icon_name("go-previous-symbolic") 120 | self.back_button.connect("clicked", self._on_back_clicked) 121 | self.back_button.props.visible = header.props.folded 122 | right_header.pack_start(self.back_button) 123 | 124 | header.bind_property("folded", self.back_button, "visible", 125 | GObject.BindingFlags.SYNC_CREATE) 126 | header.bind_property("folded", right_header, "show-start-title-buttons") 127 | header.bind_property("folded", left_header, "show-end-title-buttons") 128 | 129 | lbl = Adw.WindowTitle.new(_("Tweaks"), "") 130 | left_header.set_title_widget(lbl) 131 | 132 | self.builder = Gtk.Builder() 133 | assert os.path.exists(gtweak.PKG_DATA_DIR) 134 | filename = os.path.join(gtweak.PKG_DATA_DIR, "shell.ui") 135 | self.builder.add_from_file(filename) 136 | 137 | header.append(left_header) 138 | header.get_page(left_header).set_name("sidebar") 139 | header.append(Gtk.Separator(orientation=Gtk.Orientation.VERTICAL)) 140 | header.append(right_header) 141 | header.get_page(right_header).set_name("content") 142 | 143 | self.hsize_group.add_widget(left_header) 144 | 145 | return header 146 | 147 | def _setup_mainbox(self): 148 | # self.main_box.set_size_request(540, -1) 149 | 150 | # self.main_stack.add_css_class("background") 151 | self.main_stack.set_transition_type(Gtk.StackTransitionType.CROSSFADE) 152 | 153 | self.main_leaflet.bind_property("visible-child-name", self.header, "visible-child-name", GObject.BindingFlags.SYNC_CREATE) 154 | 155 | def _setup_sidebar(self): 156 | self.entry.placeholder_text=_("Search Tweaks…") 157 | if Gtk.check_version(3, 22, 20) is None: 158 | self.entry.set_input_hints(Gtk.InputHints.NO_EMOJI) 159 | self.entry.connect("search-changed", self._on_search) 160 | 161 | self.listbox.connect("row-selected", self._on_select_row) 162 | 163 | if not show_sound_tweaks: 164 | self.listbox.remove(self.list_box_row_sound) 165 | 166 | def _setup_sizegroups(self): 167 | start_pane_size_group = Gtk.SizeGroup(mode=Gtk.SizeGroupMode.HORIZONTAL) 168 | start_pane_size_group.add_widget(self.left_box) 169 | start_pane_size_group.add_widget(self._left_header) 170 | 171 | end_pane_size_group = Gtk.SizeGroup(mode=Gtk.SizeGroupMode.HORIZONTAL) 172 | end_pane_size_group.add_widget(self.right_box) 173 | end_pane_size_group.add_widget(self._right_header) 174 | 175 | def _setup_shortcut(self): 176 | s_trigger = Gtk.ShortcutTrigger.parse_string("f") 177 | s_action = Gtk.CallbackAction.new(lambda w, a, s: s.set_search_mode(True), self.searchbar) 178 | 179 | if s_trigger and s_action: 180 | shortcut = Gtk.Shortcut(trigger=s_trigger, action=s_action) 181 | self.add_shortcut(shortcut) 182 | 183 | def _load_css(self): 184 | if gtweak.defs.IS_DEVEL: 185 | self.add_css_class('devel') 186 | css_provider = Gtk.CssProvider() 187 | css_provider.load_from_path( 188 | os.path.join(gtweak.PKG_DATA_DIR, 'shell.css')) 189 | display = Gdk.Display.get_default() 190 | self.get_style_context().add_provider_for_display(display, css_provider, 191 | Gtk.STYLE_PROVIDER_PRIORITY_USER) 192 | 193 | @staticmethod 194 | def _list_filter_func(row, user_data: List[str]): 195 | name = row.props.tweakname 196 | if name in user_data: 197 | return row 198 | 199 | def _after_key_press(self, widget, event): 200 | if not self.search_btn.get_active() or not self.entry.is_focus(): 201 | if self.entry.im_context_filter_keypress(event): 202 | self.search_btn.set_active(True) 203 | self.entry.grab_focus() 204 | 205 | # Text in entry is selected, deselect it 206 | l = self.entry.get_text_length() 207 | self.entry.select_region(l, l) 208 | return True 209 | 210 | return False 211 | 212 | def _on_list_changed(self, group): 213 | self.listbox.set_filter_func(self._list_filter_func, group) 214 | selected = self.listbox.get_selected_row() 215 | if not selected: 216 | return 217 | selected_tweakname = selected.props.tweakname 218 | if group and selected_tweakname not in group: 219 | index = sorted(self._model._tweak_group_names.keys()).index(group[0]) 220 | row = self.listbox.get_row_at_index(index) 221 | self.listbox.select_row(row) 222 | self.entry.grab_focus() 223 | 224 | def _on_search(self, entry: Gtk.SearchEntry): 225 | txt = string_for_search(entry.get_text()) 226 | group = self._model.search_matches(txt) 227 | self._on_list_changed(group) 228 | 229 | def _on_select_row(self, _, row: Gtk.ListBoxRow): 230 | if row: 231 | group = row.props.tweakname 232 | 233 | for tweak in tweaks: 234 | if tweak.name == group: 235 | 236 | self.main_stack.set_visible_child_name(tweak.name) 237 | self.title.set_title(tweak.title) 238 | self.main_leaflet.set_visible_child_name("content") 239 | 240 | def _on_find_toggled(self, _): 241 | self.searchbar.set_search_mode(not self.searchbar.get_search_mode()) 242 | 243 | def _on_back_clicked(self, *_): 244 | # Clear the page selection when going back to allow 245 | # re-selecting it. 246 | self.listbox.unselect_all() 247 | 248 | self.main_leaflet.set_visible_child_name("sidebar") 249 | 250 | def show_only_tweaks(self, tweaks): 251 | for t in self._model.tweaks: 252 | if not isinstance(t, Gtk.Widget): 253 | continue 254 | if t in tweaks: 255 | t.show() 256 | else: 257 | t.hide() 258 | 259 | -------------------------------------------------------------------------------- /gtweak/utils.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2011 John Stowers 2 | # SPDX-License-Identifier: GPL-3.0+ 3 | # License-Filename: LICENSES/GPL-3.0 4 | 5 | import os.path 6 | import logging 7 | import tempfile 8 | import shutil 9 | import subprocess 10 | import glob 11 | import itertools 12 | import logging 13 | 14 | import gi 15 | gi.require_version("Notify", "0.7") 16 | from gi.repository import GLib 17 | from gi.repository import Gio 18 | from gi.repository import Notify 19 | 20 | import gtweak 21 | from gtweak.gsettings import GSettingsMissingError, GSettingsSetting 22 | 23 | 24 | def singleton(cls): 25 | """ 26 | Singleton decorator that works with GObject derived types. The 'recommended' 27 | python one - http://wiki.python.org/moin/PythonDecoratorLibrary#Singleton 28 | does not (interacts badly with GObjectMeta 29 | """ 30 | instances = {} 31 | 32 | def getinstance(): 33 | if cls not in instances: 34 | instances[cls] = cls() 35 | return instances[cls] 36 | return getinstance 37 | 38 | 39 | def make_combo_list_with_default(opts, default, title=True, default_text=None): 40 | """ 41 | Turns a list of values into a list of value,name (where name is the 42 | display name a user will see in a combo box). If a value is opt is 43 | equal to that supplied in default the display name for that value is 44 | modified to "value (default)" 45 | 46 | @opts: a list of value 47 | @returns: a list of 2-tuples (value, name) 48 | """ 49 | themes = [] 50 | for t in opts: 51 | if t.lower() == "default" and t != default: 52 | # some themes etc are actually called default. Ick. Dont show them if they 53 | # are not the actual default value 54 | continue 55 | 56 | if title and len(t): 57 | name = t[0].upper() + t[1:] 58 | else: 59 | name = t 60 | 61 | if t == default: 62 | # indicates the default theme, e.g Adwaita (default) 63 | name = default_text or _("%s (default)") % name 64 | 65 | themes.append((t, name)) 66 | return themes 67 | 68 | 69 | def walk_directories(dirs, filter_func): 70 | valid = [] 71 | try: 72 | for thdir in dirs: 73 | if os.path.isdir(thdir): 74 | for t in os.listdir(thdir): 75 | if filter_func(os.path.join(thdir, t)): 76 | valid.append(t) 77 | except: 78 | logging.critical("Error parsing directories", exc_info=True) 79 | return valid 80 | 81 | 82 | def extract_zip_file(z, members_path, dest): 83 | """ returns (true_if_extracted_ok, true_if_updated) """ 84 | tmp = tempfile.mkdtemp() 85 | tmpdest = os.path.join(tmp, members_path) 86 | 87 | ok = True 88 | updated = False 89 | try: 90 | if os.path.exists(dest): 91 | shutil.rmtree(dest) 92 | updated = True 93 | z.extractall(tmp) 94 | shutil.copytree(tmpdest, dest) 95 | except OSError: 96 | ok = False 97 | logging.warning("Error extracting zip", exc_info=True) 98 | 99 | if ok: 100 | logging.info("Extracted zip to %s, copied to %s" % (tmpdest, dest)) 101 | 102 | return ok, updated 103 | 104 | 105 | def execute_subprocess(cmd_then_args, block=True): 106 | p = subprocess.Popen( 107 | cmd_then_args, 108 | stdin=subprocess.PIPE, stdout=subprocess.PIPE, close_fds=True, 109 | universal_newlines=True) 110 | if block: 111 | stdout, stderr = p.communicate() 112 | return stdout, stderr, p.returncode 113 | 114 | 115 | def get_resource_dirs(resource): 116 | """Returns a list of all known resource dirs for a given resource. 117 | 118 | :param str resource: 119 | Name of the resource (e.g. "themes") 120 | :return: 121 | A list of resource dirs 122 | """ 123 | dirs = [os.path.join(dir, resource) 124 | for dir in itertools.chain(GLib.get_system_data_dirs(), 125 | (gtweak.DATA_DIR, 126 | GLib.get_user_data_dir()))] 127 | dirs += [os.path.join(os.path.expanduser("~"), ".{}".format(resource))] 128 | 129 | return [dir for dir in dirs if os.path.isdir(dir)] 130 | 131 | 132 | @singleton 133 | class AutostartManager: 134 | 135 | @staticmethod 136 | def get_desktop_files(): 137 | return [a.get_filename() for a in Gio.app_info_get_all()] 138 | 139 | @staticmethod 140 | def get_user_autostart_files(): 141 | return glob.glob( 142 | os.path.join( 143 | GLib.get_user_config_dir(), "autostart", "*.desktop")) 144 | 145 | @staticmethod 146 | def get_system_autostart_files(): 147 | f = [] 148 | for d in GLib.get_system_config_dirs(): 149 | f.extend(glob.glob(os.path.join(d, "autostart", "*.desktop"))) 150 | return f 151 | 152 | 153 | class AutostartFile: 154 | def __init__(self, appinfo, autostart_desktop_filename="", exec_cmd="", extra_exec_args=""): 155 | if appinfo: 156 | self._desktop_file = appinfo.get_filename() 157 | self._autostart_desktop_filename = autostart_desktop_filename or os.path.basename(self._desktop_file) 158 | self._create_file = False 159 | elif autostart_desktop_filename: 160 | self._desktop_file = None 161 | self._autostart_desktop_filename = autostart_desktop_filename 162 | self._create_file = True 163 | else: 164 | raise Exception("Need either an appinfo or a file name") 165 | 166 | self._exec_cmd = exec_cmd 167 | if extra_exec_args: 168 | self._extra_exec_args = " %s\n" % extra_exec_args 169 | else: 170 | self._extra_exec_args = "\n" 171 | 172 | user_autostart_dir = os.path.join(GLib.get_user_config_dir(), "autostart") 173 | os.makedirs(user_autostart_dir, exist_ok=True) 174 | 175 | self._user_autostart_file = os.path.join(user_autostart_dir, self._autostart_desktop_filename) 176 | 177 | if self._desktop_file: 178 | logging.debug("Found desktop file: %s" % self._desktop_file) 179 | logging.debug("User autostart desktop file: %s" % self._user_autostart_file) 180 | 181 | def _create_user_autostart_file(self): 182 | f = open(self._user_autostart_file, "w") 183 | f.write("[Desktop Entry]\nType=Application\nName=%s\nExec=%s\n" % 184 | (self._autostart_desktop_filename[0:-len('.desktop')], self._exec_cmd + self._extra_exec_args)) 185 | f.close() 186 | 187 | def is_start_at_login_enabled(self): 188 | if os.path.exists(self._user_autostart_file): 189 | # prefer user directories first 190 | # if it contains X-GNOME-Autostart-enabled=false then it has 191 | # has been disabled by the user in the session applet, otherwise 192 | # it is enabled 193 | return open(self._user_autostart_file).read().find("X-GNOME-Autostart-enabled=false") == -1 194 | else: 195 | # check the system directories 196 | for f in AutostartManager().get_system_autostart_files(): 197 | if os.path.basename(f) == self._autostart_desktop_filename: 198 | return True 199 | return False 200 | 201 | def update_start_at_login(self, update): 202 | 203 | if os.path.exists(self._user_autostart_file): 204 | logging.info("Removing user autostart file %s" % self._user_autostart_file) 205 | os.remove(self._user_autostart_file) 206 | 207 | if update: 208 | if (not self._desktop_file) or (not os.path.exists(self._desktop_file)): 209 | if self._create_file: 210 | self._create_user_autostart_file() 211 | else: 212 | logging.critical("Could not find desktop file: %s" % self._desktop_file) 213 | return 214 | 215 | logging.info("Adding autostart %s" % self._user_autostart_file) 216 | #copy the original file to the new file, but add the extra exec args 217 | old = open(self._desktop_file, "r") 218 | new = open(self._user_autostart_file, "w") 219 | 220 | for l in old.readlines(): 221 | if l.startswith("Exec="): 222 | if self._exec_cmd: 223 | new.write("Exec=%s\n" % self._exec_cmd) 224 | else: 225 | new.write(l[0:-1]) 226 | new.write(self._extra_exec_args) 227 | else: 228 | new.write(l) 229 | 230 | old.close() 231 | new.close() 232 | 233 | 234 | class SchemaList: 235 | @classmethod 236 | def setup(cls): 237 | cls.__list = [] 238 | 239 | @classmethod 240 | def get(cls): 241 | return cls.__list 242 | 243 | @classmethod 244 | def insert(cls, key_name, schema_id = None, schema_name = None, schema_dir = None): 245 | v = [key_name, (schema_id, schema_name, schema_dir)] 246 | cls.__list.append(v) 247 | 248 | @classmethod 249 | def reset(cls): 250 | for key_name, (schema_id, schema_name, schema_dir) in cls.__list: 251 | try: 252 | s = GSettingsSetting(schema_id=schema_id, schema_dir=schema_dir, schema_name=schema_name) 253 | s.reset(key_name) 254 | except GSettingsMissingError: 255 | logging.warn(f"Could not reset {key_name}, {schema_id or schema_name} is not installed.") 256 | 257 | 258 | SchemaList.setup() 259 | 260 | @singleton 261 | class XSettingsOverrides: 262 | 263 | VARIANT_TYPES = { 264 | 'Gtk/ShellShowsAppMenu': GLib.Variant.new_int32, 265 | 'Gtk/EnablePrimaryPaste': GLib.Variant.new_int32, 266 | 'Gdk/WindowScalingFactor': GLib.Variant.new_int32, 267 | } 268 | 269 | def __init__(self): 270 | # Ensure we don't error out 271 | try: 272 | self._settings = GSettingsSetting(schema='org.gnome.settings-daemon.plugins.xsettings') 273 | except: 274 | self._settings = None 275 | logging.warn("org.gnome.settings-daemon.plugins.xsettings not installed or running") 276 | 277 | if self._settings: 278 | self._variant = self._settings.get_value("overrides") 279 | 280 | def _dup_variant_as_dict(self): 281 | items = {} 282 | for k in list(self._variant.keys()): 283 | try: 284 | # variant override doesnt support .items() 285 | v = self._variant[k] 286 | items[k] = self.VARIANT_TYPES[k](v) 287 | except KeyError: 288 | pass 289 | return items 290 | 291 | def _dup_variant(self): 292 | return GLib.Variant('a{sv}', self._dup_variant_as_dict()) 293 | 294 | def _set_override(self, name, v): 295 | items = self._dup_variant_as_dict() 296 | items[name] = self.VARIANT_TYPES[name](v) 297 | n = GLib.Variant('a{sv}', items) 298 | self._settings.set_value('overrides', n) 299 | self._variant = self._settings.get_value("overrides") 300 | 301 | def _get_override(self, name, default): 302 | try: 303 | return self._variant[name] 304 | except KeyError: 305 | return default 306 | 307 | # while I could store meta type information in the VARIANT_TYPES 308 | # dict, its easiest to do default value handling and missing value 309 | # checks in dedicated functions 310 | def set_shell_shows_app_menu(self, v): 311 | self._set_override('Gtk/ShellShowsAppMenu', int(v)) 312 | 313 | def get_shell_shows_app_menu(self): 314 | return self._get_override('Gtk/ShellShowsAppMenu', True) 315 | 316 | def set_enable_primary_paste(self, v): 317 | self._set_override('Gtk/EnablePrimaryPaste', int(v)) 318 | 319 | def get_enable_primary_paste(self): 320 | return self._get_override('Gtk/EnablePrimaryPaste', True) 321 | 322 | def set_window_scaling_factor(self, v): 323 | self._set_override('Gdk/WindowScalingFactor', int(v)) 324 | 325 | def get_window_scaling_factor(self): 326 | return self._get_override('Gdk/WindowScalingFactor', 1) 327 | 328 | 329 | class Notification: 330 | def __init__(self, summary, body): 331 | if Notify.is_initted() or Notify.init(_("GNOME Tweaks")): 332 | self.notification = Notify.Notification.new( 333 | summary, 334 | body, 335 | 'gnome-tweaks' 336 | ) 337 | self.notification.set_hint( 338 | "desktop-entry", 339 | GLib.Variant('s', gtweak.APP_ID)) 340 | self.notification.show() 341 | else: 342 | raise Exception("Not Supported") 343 | 344 | 345 | @singleton 346 | class LogoutNotification: 347 | def __init__(self): 348 | if Notify.is_initted() or Notify.init(_("GNOME Tweaks")): 349 | self.notification = Notify.Notification.new( 350 | _("Configuration changes require restart"), 351 | _("Your session needs to be restarted for settings to take effect"), 352 | 'gnome-tweaks') 353 | self.notification.add_action( 354 | "restart", 355 | _("Restart Session"), 356 | self._logout, None, None) 357 | self.notification.set_hint( 358 | "desktop-entry", 359 | GLib.Variant('s', gtweak.APP_ID)) 360 | self.notification.show() 361 | else: 362 | raise Exception("Not Supported") 363 | 364 | def _logout(self, btn, action, user_data, unknown): 365 | d = Gio.bus_get_sync(Gio.BusType.SESSION, None) 366 | proxy = Gio.DBusProxy.new_sync( 367 | d, Gio.DBusProxyFlags.NONE, None, 368 | 'org.gnome.SessionManager', 369 | '/org/gnome/SessionManager', 370 | 'org.gnome.SessionManager', 371 | None) 372 | proxy.Logout('(u)', 0) 373 | -------------------------------------------------------------------------------- /logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GNOME/gnome-tweaks/a3275acc437f1fca05e76cd417240b9b1fb72e56/logo.png -------------------------------------------------------------------------------- /meson-postinstall.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import sysconfig 4 | from compileall import compile_dir 5 | from os import environ, path 6 | 7 | prefix = environ.get('MESON_INSTALL_PREFIX', '/usr/local') 8 | destdir = environ.get('DESTDIR', '') 9 | 10 | print('Compiling python bytecode...') 11 | moduledir = sysconfig.get_path('purelib', vars={'base': str(prefix)}) 12 | compile_dir(destdir + path.join(moduledir, 'gtweak'), optimize=2) 13 | -------------------------------------------------------------------------------- /meson.build: -------------------------------------------------------------------------------- 1 | project('gnome-tweaks', 2 | version: '47.alpha', 3 | meson_version: '>= 0.59.0' 4 | ) 5 | 6 | gnome = import('gnome') 7 | i18n = import('i18n') 8 | python = import('python') 9 | python3 = python.find_installation('python3') 10 | 11 | if not python3.found() 12 | error('No valid python3 installation found!') 13 | endif 14 | 15 | # Tweaks requires at least Python 3.10 for certain type annotations 16 | if not python3.language_version().version_compare('>= 3.10') 17 | error('Python 3.10 or newer is required.') 18 | endif 19 | 20 | # Declare runtime dependency versions here to make packaging more obvious 21 | dependency('glib-2.0', version: '>= 2.78.0') 22 | dependency('gobject-introspection-1.0', version: '>= 1.78.0') 23 | dependency('gtk4', version: '>= 4.10.0') 24 | dependency('libadwaita-1', version: '>= 1.4.0') 25 | dependency('pygobject-3.0', version: '>= 3.46.0') 26 | dependency('gudev-1.0', version: '>= 238') 27 | dependency('gsettings-desktop-schemas', version: '>= 46.0') 28 | 29 | prefix = get_option('prefix') 30 | 31 | bindir = join_paths(prefix, get_option('bindir')) 32 | datadir = join_paths(prefix, get_option('datadir')) 33 | libexecdir = join_paths(prefix, get_option('libexecdir')) 34 | localedir = join_paths(prefix, get_option('localedir')) 35 | pythondir = join_paths(prefix, python3.get_path('purelib')) 36 | 37 | pkgdatadir = join_paths(datadir, meson.project_name()) 38 | 39 | default_pkgappid = 'org.gnome.tweaks' 40 | pkgappid = default_pkgappid 41 | 42 | appdatadir = join_paths(datadir, 'metainfo') 43 | desktopdir = join_paths(datadir, 'applications') 44 | gtweakdir = python3.get_install_dir(subdir: 'gtweak') 45 | icondir = join_paths(datadir, 'icons', 'hicolor') 46 | schemadir = join_paths(datadir, 'glib-2.0', 'schemas') 47 | 48 | install_data('gnome-tweaks', install_dir: bindir) 49 | 50 | subdir('gtweak') 51 | subdir('data') 52 | subdir('po') 53 | 54 | gnome.post_install( 55 | glib_compile_schemas: true, 56 | gtk_update_icon_cache: true, 57 | update_desktop_database: true, 58 | ) 59 | 60 | meson.add_install_script('meson-postinstall.py') 61 | -------------------------------------------------------------------------------- /meson_options.txt: -------------------------------------------------------------------------------- 1 | option( 2 | 'profile', 3 | type: 'combo', 4 | choices: [ 5 | 'default', 6 | 'development' 7 | ], 8 | value: 'default' 9 | ) 10 | -------------------------------------------------------------------------------- /org.gnome.tweaks.json: -------------------------------------------------------------------------------- 1 | { 2 | "app-id" : "org.gnome.tweaks", 3 | "runtime" : "org.gnome.Platform", 4 | "runtime-version" : "master", 5 | "sdk" : "org.gnome.Sdk", 6 | "command" : "gnome-tweaks", 7 | "tags" : [ 8 | "devel" 9 | ], 10 | "desktop-file-name-prefix" : "(Development) ", 11 | "finish-args" : [ 12 | "--device=dri", 13 | "--filesystem=xdg-run/dconf", 14 | "--filesystem=~/.config/dconf:rw", 15 | "--filesystem=xdg-run/gvfsd", 16 | "--talk-name=ca.desrt.dconf", 17 | "--env=DCONF_USER_CONFIG_DIR=.config/dconf", 18 | "--filesystem=host", 19 | "--socket=session-bus", 20 | "--socket=system-bus", 21 | "--share=ipc", 22 | "--socket=wayland", 23 | "--socket=fallback-x11", 24 | "--socket=system-bus", 25 | "--talk-name=org.gnome.Shell" 26 | ], 27 | "cleanup" : [ 28 | "/include", 29 | "/lib/*.la", 30 | "/lib/*.a", 31 | "/lib/pkgconfig", 32 | "/share/gir-1.0" 33 | ], 34 | "modules" : [ 35 | { 36 | "name" : "gnome-desktop", 37 | "buildsystem" : "meson", 38 | "sources" : [ 39 | { 40 | "type" : "git", 41 | "url" : "https://gitlab.gnome.org/GNOME/gnome-desktop.git" 42 | } 43 | ] 44 | }, 45 | { 46 | "name" : "gnome-tweaks", 47 | "buildsystem" : "meson", 48 | "config-opts" : [ 49 | "-Dprofile=development" 50 | ], 51 | "sources" : [ 52 | { 53 | "type" : "git", 54 | "url" : "https://gitlab.gnome.org/GNOME/gnome-tweaks.git" 55 | } 56 | ] 57 | } 58 | ] 59 | } 60 | -------------------------------------------------------------------------------- /po/LINGUAS: -------------------------------------------------------------------------------- 1 | # Please keep this list sorted alphabetically 2 | ab 3 | af 4 | ar 5 | as 6 | be 7 | bg 8 | bs 9 | ca 10 | ca@valencia 11 | ckb 12 | cs 13 | da 14 | de 15 | el 16 | en_GB 17 | eo 18 | es 19 | eu 20 | fa 21 | fi 22 | fr 23 | fur 24 | gl 25 | he 26 | hi 27 | hr 28 | hu 29 | id 30 | ie 31 | is 32 | it 33 | ja 34 | ka 35 | kk 36 | ko 37 | ky 38 | lt 39 | lv 40 | mjw 41 | ml 42 | ms 43 | my 44 | nb 45 | ne 46 | nl 47 | oc 48 | pa 49 | pl 50 | pt 51 | pt_BR 52 | ro 53 | ru 54 | sk 55 | sl 56 | sr 57 | sr@latin 58 | sv 59 | te 60 | tg 61 | th 62 | tr 63 | uk 64 | vi 65 | zh_CN 66 | zh_HK 67 | zh_TW 68 | -------------------------------------------------------------------------------- /po/POTFILES.in: -------------------------------------------------------------------------------- 1 | # List of source files which contain translatable strings. 2 | data/org.gnome.tweaks.appdata.xml.in 3 | data/org.gnome.tweaks.desktop.in 4 | data/shell.ui 5 | data/tweaks.ui 6 | gtweak/app.py 7 | gtweak/tweakmodel.py 8 | gtweak/tweaks/tweak_group_appearance.py 9 | gtweak/tweaks/tweak_group_font.py 10 | gtweak/tweaks/tweak_group_keyboard.py 11 | gtweak/tweaks/tweak_group_mouse.py 12 | gtweak/tweaks/tweak_group_sound.py 13 | gtweak/tweaks/tweak_group_startup.py 14 | gtweak/tweaks/tweak_group_windows.py 15 | gtweak/tweakview.py 16 | gtweak/utils.py 17 | gtweak/widgets.py 18 | -------------------------------------------------------------------------------- /po/ky.po: -------------------------------------------------------------------------------- 1 | # Kirghiz translation for gnome-tweak-tool. 2 | # Copyright (C) 2012 gnome-tweak-tool's authors 3 | # This file is distributed under the same license as the gnome-tweak-tool package. 4 | # 5 | # Chynggyz Jumaliev , 2012. 6 | # Timur Zhamakeev , 2012. 7 | # 8 | msgid "" 9 | msgstr "" 10 | "Project-Id-Version: gnome-tweak-tool master\n" 11 | "Report-Msgid-Bugs-To: http://bugzilla.gnome.org/enter_bug.cgi?product=gnome-" 12 | "tweak-tool&keywords=I18N+L10N&component=general\n" 13 | "POT-Creation-Date: 2012-10-11 20:07+0000\n" 14 | "PO-Revision-Date: 2012-10-18 23:26+0600\n" 15 | "Last-Translator: Timur Zhamakeev \n" 16 | "Language-Team: Kirghiz \n" 17 | "Language: ky\n" 18 | "MIME-Version: 1.0\n" 19 | "Content-Type: text/plain; charset=UTF-8\n" 20 | "Content-Transfer-Encoding: 8bit\n" 21 | "X-Generator: Lokalize 1.4\n" 22 | "Plural-Forms: nplurals=1; plural=0;\n" 23 | 24 | #: ../data/gnome-tweak-tool.desktop.in.h:1 ../data/shell.ui.h:1 25 | msgid "Tweak Tool" 26 | msgstr "Ырастоо аспабы" 27 | 28 | #: ../data/gnome-tweak-tool.desktop.in.h:2 29 | msgid "Tweak advanced GNOME 3 settings" 30 | msgstr "GNOME 3-түн комшумча параметрлерин өзгөртүү" 31 | 32 | #: ../data/shell.ui.h:2 33 | msgid "welcome" 34 | msgstr "кош келиңиз" 35 | 36 | #: ../data/shell.ui.h:3 37 | msgid "tweaks" 38 | msgstr "ырастоолор" 39 | 40 | #: ../gtweak/tweakmodel.py:28 41 | msgid "Fonts" 42 | msgstr "Шрифттер" 43 | 44 | #: ../gtweak/tweakmodel.py:29 45 | msgid "Theme" 46 | msgstr "Тема" 47 | 48 | #: ../gtweak/tweakmodel.py:30 49 | msgid "Desktop" 50 | msgstr "Иш столу" 51 | 52 | #: ../gtweak/tweakmodel.py:31 53 | msgid "Windows" 54 | msgstr "Терезелер" 55 | 56 | #: ../gtweak/tweakmodel.py:32 57 | msgid "Shell" 58 | msgstr "Shell" 59 | 60 | #: ../gtweak/tweakmodel.py:33 61 | msgid "Typing" 62 | msgstr "Терүү" 63 | 64 | #: ../gtweak/tweakmodel.py:34 65 | msgid "Mouse" 66 | msgstr "" 67 | 68 | #. translate this the same as the name of the file manager (nautilus) 69 | #: ../gtweak/tweakmodel.py:37 70 | msgid "Files" 71 | msgstr "Файлдар" 72 | 73 | #: ../gtweak/tweakmodel.py:48 74 | msgid "Miscellaneous" 75 | msgstr "Ар башкалар" 76 | 77 | #: ../gtweak/tweaks/tweak_interface.py:84 ../gtweak/tweaks/tweak_shell.py:117 78 | msgid "Default" 79 | msgstr "Абалкы" 80 | 81 | #: ../gtweak/tweaks/tweak_shell_extensions.py:38 82 | msgid "Extension downloading" 83 | msgstr "Кеңейтме жүктөлүп жатат" 84 | 85 | #: ../gtweak/tweaks/tweak_shell_extensions.py:40 86 | msgid "Error loading extension" 87 | msgstr "Кеңейтменин жүктөө катасы" 88 | 89 | #: ../gtweak/tweaks/tweak_shell_extensions.py:42 90 | msgid "Extension does not support shell version" 91 | msgstr "Кеңейтме Shell'дин версиясын колдобойт" 92 | 93 | #: ../gtweak/tweaks/tweak_shell_extensions.py:44 94 | msgid "Unknown extension error" 95 | msgstr "Кеңейтменин белгисиз катасы" 96 | 97 | #: ../gtweak/tweaks/tweak_shell_extensions.py:75 98 | msgid "The shell must be restarted for changes to take effect" 99 | msgstr "Өзгөрүүлөр күчүнө кириш үчүн Shell'ди кайта жүргүзүш керек" 100 | 101 | #: ../gtweak/tweaks/tweak_shell_extensions.py:76 102 | #: ../gtweak/tweaks/tweak_shell_extensions.py:145 103 | msgid "Restart" 104 | msgstr "Кайта жүргүзүү" 105 | 106 | #: ../gtweak/tweaks/tweak_shell_extensions.py:82 107 | msgid "Install Shell Extension" 108 | msgstr "Shell кеңейтмесин орнотуу" 109 | 110 | #: ../gtweak/tweaks/tweak_shell_extensions.py:86 111 | msgid "Select an extension" 112 | msgstr "Кеңейтемени тандоо" 113 | 114 | #: ../gtweak/tweaks/tweak_shell_extensions.py:91 115 | msgid "Get more extensions" 116 | msgstr "Кошумча кеңейтмелер" 117 | 118 | #: ../gtweak/tweaks/tweak_shell_extensions.py:139 119 | #, python-format 120 | msgid "%s extension updated successfully" 121 | msgstr "%s кеңейтмеси ийгиликтүү жаңыртылды" 122 | 123 | #: ../gtweak/tweaks/tweak_shell_extensions.py:141 124 | #, python-format 125 | msgid "%s extension installed successfully" 126 | msgstr "%s кеңейтмеси ийгиликтүү орнотулду" 127 | 128 | #: ../gtweak/tweaks/tweak_shell_extensions.py:149 129 | msgid "Error installing extension" 130 | msgstr "Кеңейтмени орнотуу катасы" 131 | 132 | #. does not look like a valid theme 133 | #: ../gtweak/tweaks/tweak_shell_extensions.py:154 134 | msgid "Invalid extension" 135 | msgstr "Туура эмес кеңейтме" 136 | 137 | #: ../gtweak/tweaks/tweak_shell_extensions.py:162 138 | msgid "Shell Extensions" 139 | msgstr "Shell кеңейтмелери" 140 | 141 | #: ../gtweak/tweaks/tweak_shell.py:38 142 | msgid "Close Only" 143 | msgstr "Жабуу гана" 144 | 145 | #: ../gtweak/tweaks/tweak_shell.py:39 146 | msgid "Minimize and Close" 147 | msgstr "Кичирейтүү жана жабуу" 148 | 149 | #: ../gtweak/tweaks/tweak_shell.py:40 150 | msgid "Maximize and Close" 151 | msgstr "Чоңойтуу жана жабуу" 152 | 153 | #: ../gtweak/tweaks/tweak_shell.py:41 154 | msgid "All" 155 | msgstr "Баары" 156 | 157 | #: ../gtweak/tweaks/tweak_shell.py:55 158 | msgid "Shell theme" 159 | msgstr "Shell темасы" 160 | 161 | #: ../gtweak/tweaks/tweak_shell.py:55 162 | msgid "Install custom or user themes for gnome-shell" 163 | msgstr "gnome-shell үчүн өз теманы орнотуу" 164 | 165 | #. check the shell is running and the usertheme extension is present 166 | #: ../gtweak/tweaks/tweak_shell.py:58 167 | msgid "Unknown error" 168 | msgstr "Белгисиз ката" 169 | 170 | #: ../gtweak/tweaks/tweak_shell.py:63 171 | msgid "Shell not running" 172 | msgstr "Shell жүргүзүлгөн жок" 173 | 174 | #: ../gtweak/tweaks/tweak_shell.py:84 175 | msgid "Shell user-theme extension incorrectly installed" 176 | msgstr "«user-theme» кеңейтмеси туура эмес орнотулуптур" 177 | 178 | #: ../gtweak/tweaks/tweak_shell.py:87 179 | msgid "Shell user-theme extension not enabled" 180 | msgstr "«user-theme» кеңейтмеси күйгүзүлгөн жок" 181 | 182 | #: ../gtweak/tweaks/tweak_shell.py:90 183 | msgid "Could not list shell extensions" 184 | msgstr "Кеңейтме тизмесин алуу оңунан чыкпады" 185 | 186 | #. a filechooser to install new themes 187 | #: ../gtweak/tweaks/tweak_shell.py:122 188 | msgid "Select a theme" 189 | msgstr "Теманы тандаңыз" 190 | 191 | #: ../gtweak/tweaks/tweak_shell.py:171 192 | #, python-format 193 | msgid "%s theme updated successfully" 194 | msgstr "%s темасы ийгиликтүү жаңыртылды" 195 | 196 | #: ../gtweak/tweaks/tweak_shell.py:173 197 | #, python-format 198 | msgid "%s theme installed successfully" 199 | msgstr "%s темасы ийгиликтүү орнотулду" 200 | 201 | #: ../gtweak/tweaks/tweak_shell.py:181 202 | msgid "Error installing theme" 203 | msgstr "Теманы орнотуу катасы" 204 | 205 | #. does not look like a valid theme 206 | #: ../gtweak/tweaks/tweak_shell.py:186 207 | msgid "Invalid theme" 208 | msgstr "Туура эмес тема" 209 | 210 | #: ../gtweak/tweaks/tweak_shell.py:205 211 | msgid "Dynamic workspaces" 212 | msgstr "Динамикалык иш орундары" 213 | 214 | #: ../gtweak/tweaks/tweak_shell.py:205 215 | msgid "Disable gnome-shell dynamic workspace management, use static workspaces" 216 | msgstr "" 217 | "gnome-shell иш орундары үчүн динамикалык башкарууну колдонбоо, статикалыкты " 218 | "колдонуу" 219 | 220 | #: ../gtweak/tweaks/tweak_typing.py:38 ../gtweak/tweaks/tweak_windows.py:48 221 | msgid "Disabled" 222 | msgstr "Өчүк" 223 | 224 | #. indicates the default theme, e.g Adwaita (default) 225 | #: ../gtweak/utils.py:67 226 | #, python-format 227 | msgid "%s (default)" 228 | msgstr "%s (абалкы)" 229 | 230 | #: ../gtweak/widgets.py:318 231 | msgid "Enable dark theme for all applications" 232 | msgstr "Бүт тиркемелерге күңүрт теманы колдонуу" 233 | 234 | #: ../gtweak/widgets.py:319 235 | msgid "Enable the dark theme hint for all the applications in the session" 236 | msgstr "Сеанстын бүт тиркемелерине күңүрт теманы колдонуу" 237 | 238 | #: ../gtweak/widgets.py:337 239 | msgid "Error writing setting" 240 | msgstr "Параметрлерди жазуу катасы" 241 | 242 | -------------------------------------------------------------------------------- /po/meson.build: -------------------------------------------------------------------------------- 1 | i18n.gettext('gnome-tweaks', preset: 'glib') 2 | -------------------------------------------------------------------------------- /po/my.po: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2012 gnome-tweak-tool's COPYRIGHT HOLDER 2 | # This file is distributed under the same license as the gnome-tweak-tool package. 3 | # Shwun Mi , 2012. 4 | # 5 | msgid "" 6 | msgstr "" 7 | "Project-Id-Version: gnome-tweak-tool gnome-3-4\n" 8 | "Report-Msgid-Bugs-To: http://bugzilla.gnome.org/enter_bug.cgi?product=gnome-tweak-tool&keywords=I18N+L10N&component=general\n" 9 | "POT-Creation-Date: 2012-09-07 15:46+0000\n" 10 | "PO-Revision-Date: 2012-10-05 01:19+0530\n" 11 | "Last-Translator: Russell Kyaw\n" 12 | "Language-Team: MM L10N Team \n" 13 | "Language: my\n" 14 | "MIME-Version: 1.0\n" 15 | "Content-Type: text/plain; charset=UTF-8\n" 16 | "Content-Transfer-Encoding: 8bit\n" 17 | 18 | #: ../data/shell.ui.h:1 19 | #: ../data/gnome-tweak-tool.desktop.in.h:1 20 | msgid "Advanced Settings" 21 | msgstr "အဆင့်မြင့် ချိန်ညှိချက်များ" 22 | 23 | #: ../data/shell.ui.h:2 24 | msgid "welcome" 25 | msgstr "ကြိုဆိုပါ၏" 26 | 27 | #: ../data/shell.ui.h:3 28 | msgid "tweaks" 29 | msgstr "Tweak များ" 30 | 31 | #: s../data/gnome-tweak-tool.desktop.in.h:2 32 | msgid "Tweak advanced GNOME 3 settings" 33 | msgstr "Tweak အဆင့်မြင့် GNOME 3 ချိန်ညှိချက်များ" 34 | 35 | #: ../gtweak/tweaks/tweak_interface.py:33 36 | msgid "GTK+ theme" 37 | msgstr "GTK+ သွင်ပြင်" 38 | 39 | #: ../gtweak/tweaks/tweak_interface.py:51 40 | msgid "Icon theme" 41 | msgstr "အိုင်ကွန်ပုံ သွင်ပြင်" 42 | 43 | #: ../gtweak/tweaks/tweak_interface.py:86 44 | #: ../gtweak/tweaks/tweak_shell.py:115 45 | msgid "Default" 46 | msgstr "ပုံမှန်" 47 | 48 | #: ../gtweak/tweaks/tweak_interface.py:87 49 | msgid "Keybinding theme" 50 | msgstr "စုပေါင်း ကီးသွင်ပြင်" 51 | 52 | #. check the shell is running and the usertheme extension is present 53 | #: ../gtweak/tweaks/tweak_shell.py:57 54 | msgid "Unknown error" 55 | msgstr "အမည်မသိ ချို့ယွင်းချက်" 56 | 57 | #: ../gtweak/tweaks/tweak_shell.py:62 58 | msgid "Shell not running" 59 | msgstr "Shell အလုပ်မလုပ်ပါ" 60 | 61 | #: ../gtweak/tweaks/tweak_shell.py:83 62 | msgid "Shell user-theme extension incorrectly installed" 63 | msgstr "Shell သုံးစွဲသူ-သွင်ပြင် ဆင့်ပွားများ ထည့်သွင်းမှု မှားယွင်းသည်" 64 | 65 | #: ../gtweak/tweaks/tweak_shell.py:86 66 | msgid "Shell user-theme extension not enabled" 67 | msgstr "Shell သုံးစွဲသူ-သွင်ပြင် ဆင့်ပွားများ ဖွင့်မထားပါ" 68 | 69 | #: ../gtweak/tweaks/tweak_shell.py:89 70 | msgid "Could not list shell extensions" 71 | msgstr "Shell ဆင့်ပွားများ စာရင်း မပြုစုနိုင်ပါ" 72 | 73 | #. a filechooser to install new themes 74 | #: ../gtweak/tweaks/tweak_shell.py:120 75 | msgid "Select a theme" 76 | msgstr "သွင်ပြင် တစ်ခု ရွေးပါ" 77 | 78 | #: ../gtweak/tweaks/tweak_shell.py:167 79 | #, python-format 80 | msgid "%s theme updated successfully" 81 | msgstr "သွင်ပြင် %s ကို အောင်မြင်စွာ မွမ်းမံလိုက်ပြီ" 82 | 83 | #: ../gtweak/tweaks/tweak_shell.py:169 84 | #, python-format 85 | msgid "%s theme installed successfully" 86 | msgstr "သွင်ပြင် %s ကို အောင်မြင်စွာ ထည့်သွင်းလိုက်ပြီ" 87 | 88 | #: ../gtweak/tweaks/tweak_shell.py:177 89 | msgid "Error installing theme" 90 | msgstr "သွင်ပြင် ထည့်သွင်းမှု ချို့ယွင်းချက်" 91 | 92 | #. does not look like a valid theme 93 | #: ../gtweak/tweaks/tweak_shell.py:182 94 | msgid "Invalid theme" 95 | msgstr "ချို့ယွင်းသော သွင်ပြင်" 96 | 97 | #: ../gtweak/tweaks/tweak_shell.py:231 98 | msgid "Shell" 99 | msgstr "Shell" 100 | 101 | #: ../gtweak/tweaks/tweak_shell_extensions.py:35 102 | msgid "Extension downloading" 103 | msgstr "ဆင့်ပွား ဒေါင်းလုဒ် ဆွဲနေသည်" 104 | 105 | #: ../gtweak/tweaks/tweak_shell_extensions.py:37 106 | msgid "Error loading extension" 107 | msgstr "ဖွင့်မရသော ဆင့်ပွား" 108 | 109 | #: ../gtweak/tweaks/tweak_shell_extensions.py:39 110 | msgid "Extension does not support shell version" 111 | msgstr "ဆင့်ပွားသည် Shell ဗားရှင်းကို ထောက်ပံ့မှု မပေးပါ" 112 | 113 | #: ../gtweak/tweaks/tweak_shell_extensions.py:41 114 | msgid "Unknown extension error" 115 | msgstr "အမည်မသိ ဆင့်ပွား ချို့ယွင်းချက်" 116 | 117 | #: ../gtweak/tweaks/tweak_shell_extensions.py:46 118 | #, python-format 119 | msgid "%s Extension" 120 | msgstr "ဆင့်ပွား %s" 121 | 122 | #: ../gtweak/tweaks/tweak_shell_extensions.py:59 123 | msgid "The shell must be restarted for changes to take effect" 124 | msgstr "ပြောင်းလဲမှုများ ရုပ်လုံးပေါ်အောင် Shell ကို ပြန်ဖွင့်ရမည်" 125 | 126 | #: ../gtweak/tweaks/tweak_shell_extensions.py:60 127 | #: ../gtweak/tweaks/tweak_shell_extensions.py:125 128 | msgid "Restart" 129 | msgstr "ပြန်ဖွင့်ရန်" 130 | 131 | #: ../gtweak/tweaks/tweak_shell_extensions.py:68 132 | msgid "Install Shell Extension" 133 | msgstr "Shell ဆင့်ပွားများ ထည့်သွင်းရန်" 134 | 135 | #: ../gtweak/tweaks/tweak_shell_extensions.py:72 136 | msgid "Select an extension" 137 | msgstr "ဆင့်ပွား တစ်ခုကို ရွေးရန်" 138 | 139 | #: ../gtweak/tweaks/tweak_shell_extensions.py:119 140 | #, python-format 141 | msgid "%s extension updated successfully" 142 | msgstr "ဆင့်ပွား %s ကို အောင်မြင်စွာ မွမ်းမံလိုက်ပြီ" 143 | 144 | #: ../gtweak/tweaks/tweak_shell_extensions.py:121 145 | #, python-format 146 | msgid "%s extension installed successfully" 147 | msgstr "ဆင့်ပွား %s ကို အောင်မြင်စွာ ထည့်သွင်းလိုက်ပြီ" 148 | 149 | #: ../gtweak/tweaks/tweak_shell_extensions.py:129 150 | msgid "Error installing extension" 151 | msgstr "ဆင့်ပွား ထည့်သွင်းမှု ချို့ယွင်းချက်" 152 | 153 | #. does not look like a valid theme 154 | #: ../gtweak/tweaks/tweak_shell_extensions.py:134 155 | msgid "Invalid extension" 156 | msgstr "ချို့ယွင်းနေသော ဆင့်ပွား" 157 | 158 | #: ../gtweak/tweaks/tweak_shell_extensions.py:142 159 | msgid "Shell Extensions" 160 | msgstr "Shell ဆင့်ပွားများ" 161 | 162 | #: ../gtweak/tweaks/tweak_windows.py:31 163 | msgid "Window theme" 164 | msgstr "ဝင်းဒိုး သွင်ပြင်" 165 | 166 | #. indicates the default theme, e.g Adwaita (default) 167 | #: ../gtweak/utils.py:66 168 | #, python-format 169 | msgid "%s (default)" 170 | msgstr "%s (ပုံမှန်)" 171 | 172 | #: ../gtweak/tweakmodel.py:26 173 | msgid "Fonts" 174 | msgstr "ဖောင့်များ" 175 | 176 | #: ../gtweak/tweakmodel.py:27 177 | msgid "Theme" 178 | msgstr "သွင်ပြင်" 179 | 180 | #: ../gtweak/tweakmodel.py:28 181 | msgid "Desktop" 182 | msgstr "အလုပ်ခုံ" 183 | 184 | #: ../gtweak/tweakmodel.py:29 185 | msgid "Windows" 186 | msgstr "ဝင်းဒိုးများ" 187 | 188 | #. translate this the same as the name of the file manager (nautilus) 189 | #: ../gtweak/tweakmodel.py:32 190 | msgid "Files" 191 | msgstr "ဖိုင်များ" 192 | 193 | #: ../gtweak/tweakmodel.py:40 194 | msgid "Miscellaneous" 195 | msgstr "အမျိုးမျိုး အထွေထွေ" 196 | 197 | -------------------------------------------------------------------------------- /po/te.po: -------------------------------------------------------------------------------- 1 | # Telugu translation for gnome-tweak-tool. 2 | # Copyright (C) 2011 e-telugu organisation 3 | # This file is distributed under the same license as the gnome-tweak-tool package. 4 | # Praveen Illa , 2011. 5 | # 6 | msgid "" 7 | msgstr "" 8 | "Project-Id-Version: gnome-tweak-tool master\n" 9 | "Report-Msgid-Bugs-To: http://bugzilla.gnome.org/enter_bug.cgi?product=gnome-tweak-tool&keywords=I18N+L10N&component=general\n" 10 | "POT-Creation-Date: 2011-11-26 18:29+0000\n" 11 | "PO-Revision-Date: 2011-11-27 00:14+0530\n" 12 | "Last-Translator: Praveen Illa \n" 13 | "Language-Team: Telugu \n" 14 | "Language: te\n" 15 | "MIME-Version: 1.0\n" 16 | "Content-Type: text/plain; charset=UTF-8\n" 17 | "Content-Transfer-Encoding: 8bit\n" 18 | "Plural-Forms: nplurals=2; plural=(n!=1);\n" 19 | "X-Generator: Virtaal 0.7.0\n" 20 | "X-Project-Style: gnome\n" 21 | 22 | #: ../data/shell.ui.h:1 23 | #: ../data/gnome-tweak-tool.desktop.in.h:1 24 | msgid "Advanced Settings" 25 | msgstr "అధునాతన అమరికలు" 26 | 27 | #: ../data/shell.ui.h:2 28 | msgid "tweaks" 29 | msgstr "ట్వీకులు" 30 | 31 | #: ../data/shell.ui.h:3 32 | msgid "welcome" 33 | msgstr "స్వాగతం" 34 | 35 | #: ../data/gnome-tweak-tool.desktop.in.h:2 36 | msgid "Tweak advanced GNOME 3 settings" 37 | msgstr "ట్వీక్ అధునాతన గ్నోమ్ 3 అమరికలు" 38 | 39 | #: ../gtweak/tweaks/tweak_interface.py:33 40 | msgid "GTK+ theme" 41 | msgstr "జిటికె+ అలంకారం" 42 | 43 | #: ../gtweak/tweaks/tweak_interface.py:51 44 | msgid "Icon theme" 45 | msgstr "ప్రతీక అలంకారం" 46 | 47 | #: ../gtweak/tweaks/tweak_interface.py:86 48 | #: ../gtweak/tweaks/tweak_shell.py:110 49 | msgid "Default" 50 | msgstr "అప్రమేయం" 51 | 52 | #: ../gtweak/tweaks/tweak_interface.py:87 53 | msgid "Keybinding theme" 54 | msgstr "కీబైండింగ్ అలంకారం" 55 | 56 | #. check the shell is running and the usertheme extension is present 57 | #: ../gtweak/tweaks/tweak_shell.py:56 58 | msgid "Unknown error" 59 | msgstr "తెలియని దోషం" 60 | 61 | #: ../gtweak/tweaks/tweak_shell.py:61 62 | msgid "Shell not running" 63 | msgstr "షెల్ నడుచుట లేదు" 64 | 65 | #: ../gtweak/tweaks/tweak_shell.py:78 66 | msgid "Shell user-theme extension incorrectly installed" 67 | msgstr "షెల్ వాడుకరి-అలంకార పొడిగింత సరిగా స్థాపించబడలేదు" 68 | 69 | #: ../gtweak/tweaks/tweak_shell.py:81 70 | msgid "Shell user-theme extension not enabled" 71 | msgstr "షెల్ వాడుకరి-అలంకార పొడిగింతను చేతనపరుచలేదు" 72 | 73 | #: ../gtweak/tweaks/tweak_shell.py:84 74 | msgid "Could not list shell extensions" 75 | msgstr "షెల్ పొడిగింతలను జాబితా వలె చూపలేకపోతుంది" 76 | 77 | #. a filechooser to install new themes 78 | #: ../gtweak/tweaks/tweak_shell.py:115 79 | msgid "Select a theme" 80 | msgstr "ఒక థీమును ఎంచుకోండి" 81 | 82 | #: ../gtweak/tweaks/tweak_shell.py:162 83 | #, python-format 84 | msgid "%s theme updated successfully" 85 | msgstr "%s అలంకారము విజయవంతంగా నవీకరించబడింది" 86 | 87 | #: ../gtweak/tweaks/tweak_shell.py:164 88 | #, python-format 89 | msgid "%s theme installed successfully" 90 | msgstr "%s అలంకారము విజయవంతంగా స్థాపించబడింది" 91 | 92 | #: ../gtweak/tweaks/tweak_shell.py:172 93 | msgid "Error installing theme" 94 | msgstr "థీమును స్థాపించుటలో దోషం" 95 | 96 | #. does not look like a valid theme 97 | #: ../gtweak/tweaks/tweak_shell.py:177 98 | msgid "Invalid theme" 99 | msgstr "చెల్లని థీము" 100 | 101 | #: ../gtweak/tweaks/tweak_shell.py:195 102 | msgid "Shell" 103 | msgstr "షెల్" 104 | 105 | #: ../gtweak/tweaks/tweak_shell_extensions.py:35 106 | msgid "Extension downloading" 107 | msgstr "పొడిగింతను దింపుకుంటున్నది" 108 | 109 | #: ../gtweak/tweaks/tweak_shell_extensions.py:37 110 | msgid "Error loading extension" 111 | msgstr "పొడిగింతను నింపుటలో దోషం" 112 | 113 | #: ../gtweak/tweaks/tweak_shell_extensions.py:39 114 | msgid "Extension does not support shell version" 115 | msgstr "పొడిగింత షెల్ రూపాంతరానికి సహకరించుటలేదు" 116 | 117 | #: ../gtweak/tweaks/tweak_shell_extensions.py:41 118 | msgid "Unknown extension error" 119 | msgstr "తెలియని పొడిగింత దోషం" 120 | 121 | #: ../gtweak/tweaks/tweak_shell_extensions.py:46 122 | #, python-format 123 | msgid "%s Extension" 124 | msgstr "%s పొడిగింత" 125 | 126 | #: ../gtweak/tweaks/tweak_shell_extensions.py:59 127 | msgid "The shell must be restarted for changes to take effect" 128 | msgstr "మార్పులు అనువర్తించుటకు షెల్‌ను పునఃప్రారంభించాలి" 129 | 130 | #: ../gtweak/tweaks/tweak_shell_extensions.py:60 131 | #: ../gtweak/tweaks/tweak_shell_extensions.py:125 132 | msgid "Restart" 133 | msgstr "పునఃప్రారంభించు" 134 | 135 | #: ../gtweak/tweaks/tweak_shell_extensions.py:68 136 | msgid "Install Shell Extension" 137 | msgstr "షెల్ పొడిగింతను స్థాపించండి" 138 | 139 | #: ../gtweak/tweaks/tweak_shell_extensions.py:72 140 | msgid "Select an extension" 141 | msgstr "ఒక పొడిగింతను ఎంచుకొను" 142 | 143 | #: ../gtweak/tweaks/tweak_shell_extensions.py:119 144 | #, python-format 145 | msgid "%s extension updated successfully" 146 | msgstr "%s పొడిగింత విజయవంతంగా నవీకరించబడింది" 147 | 148 | #: ../gtweak/tweaks/tweak_shell_extensions.py:121 149 | #, python-format 150 | msgid "%s extension installed successfully" 151 | msgstr "%s పొడిగింత విజయవంతంగా స్థాపించబడింది" 152 | 153 | #: ../gtweak/tweaks/tweak_shell_extensions.py:129 154 | msgid "Error installing extension" 155 | msgstr "పొడిగింతను స్థాపించుటలో దోషం" 156 | 157 | #. does not look like a valid theme 158 | #: ../gtweak/tweaks/tweak_shell_extensions.py:134 159 | msgid "Invalid extension" 160 | msgstr "చెల్లని పొడిగింత" 161 | 162 | #: ../gtweak/tweaks/tweak_shell_extensions.py:142 163 | msgid "Shell Extensions" 164 | msgstr "షెల్ పొడిగింతలు" 165 | 166 | #: ../gtweak/tweaks/tweak_windows.py:31 167 | msgid "Window theme" 168 | msgstr "కిటికీ అలంకారం" 169 | 170 | #. indicates the default theme, e.g Adwaita (default) 171 | #: ../gtweak/utils.py:66 172 | #, python-format 173 | msgid "%s (default)" 174 | msgstr "%s(అప్రమేయం)" 175 | 176 | #: ../gtweak/tweakmodel.py:26 177 | msgid "Fonts" 178 | msgstr "ఫాంట్లు" 179 | 180 | #: ../gtweak/tweakmodel.py:27 181 | msgid "Theme" 182 | msgstr "అలంకారం" 183 | 184 | #: ../gtweak/tweakmodel.py:28 185 | msgid "Desktop" 186 | msgstr "డెస్క్‍టాప్" 187 | 188 | #: ../gtweak/tweakmodel.py:29 189 | msgid "Windows" 190 | msgstr "కిటికీలు" 191 | 192 | #: ../gtweak/tweakmodel.py:37 193 | msgid "Miscellaneous" 194 | msgstr "ఇతరాలు" 195 | 196 | #~ msgid "The shell may need to be restarted to apply the theme" 197 | #~ msgstr "మార్పులు అనువర్తించబడుటకు షెల్‌ను పునఃప్రారంభం అవసరమవ్వవచ్చు" 198 | -------------------------------------------------------------------------------- /po/zh_HK.po: -------------------------------------------------------------------------------- 1 | # Chinese (Hong Kong) translation for gnome-tweak-tool. 2 | # Copyright (C) 2011 gnome-tweak-tool's COPYRIGHT HOLDER 3 | # This file is distributed under the same license as the gnome-tweak-tool package. 4 | # Tryneeds 5 | # Cheng-Chia Tseng , 2011. 6 | # 7 | msgid "" 8 | msgstr "" 9 | "Project-Id-Version: gnome-tweak-tool 3.1.90\n" 10 | "Report-Msgid-Bugs-To: http://bugzilla.gnome.org/enter_bug.cgi?product=gnome-" 11 | "tweak-tool&keywords=I18N+L10N&component=general\n" 12 | "POT-Creation-Date: 2014-01-25 03:18+0000\n" 13 | "PO-Revision-Date: 2014-01-28 20:27+0800\n" 14 | "Last-Translator: Cheng-Chia Tseng \n" 15 | "Language-Team: Chinese (Hong Kong) \n" 16 | "Language: zh_HK\n" 17 | "MIME-Version: 1.0\n" 18 | "Content-Type: text/plain; charset=UTF-8\n" 19 | "Content-Transfer-Encoding: 8bit\n" 20 | "X-Generator: Poedit 1.5.4\n" 21 | 22 | #: ../data/gnome-tweak-tool.appdata.xml.in.h:1 23 | msgid "GNOME Tweak Tool allows adjusting advanced GNOME options." 24 | msgstr "GNOME 調校工具讓你可以調整進階的 GNOME 選項。" 25 | 26 | #: ../data/gnome-tweak-tool.appdata.xml.in.h:2 27 | msgid "" 28 | "It can install and manage themes and extensions, change power settings, " 29 | "manage startup applications, and enable desktop icons among other settings." 30 | msgstr "它可以安裝、管理主題與擴充套件、改變電源設定、管理初始啟動應用程式、啟用桌面圖示及其他設定等。" 31 | 32 | #: ../data/gnome-tweak-tool.desktop.in.h:1 33 | msgid "Tweak Tool" 34 | msgstr "調校工具" 35 | 36 | #: ../data/gnome-tweak-tool.desktop.in.h:2 37 | msgid "Tweak advanced GNOME 3 settings" 38 | msgstr "調校進階 GNOME 3 設定值" 39 | 40 | #: ../data/gnome-tweak-tool.desktop.in.h:3 41 | msgid "" 42 | "Settings;Advanced;Preferences;Extensions;Fonts;Theme;XKB;Keyboard;Typing;" 43 | msgstr "Settings;Advanced;Preferences;Extensions;Fonts;Theme;XKB;Keyboard;Typing;設定值;設定;進階;偏好設定;擴充;擴展;擴充套件;字型;主題;鍵盤;打字;輸入;" 44 | 45 | #: ../data/shell.ui.h:1 46 | msgid "_Reset to Defaults" 47 | msgstr "重設回預設值(_R)" 48 | 49 | #: ../data/shell.ui.h:2 50 | msgid "Disable All Shell Extensions" 51 | msgstr "停用所有 Shell 擴充套件" 52 | 53 | #: ../data/shell.ui.h:3 54 | msgid "_Help" 55 | msgstr "求助(_H)" 56 | 57 | #: ../data/shell.ui.h:4 58 | msgid "_About" 59 | msgstr "關於(_A)" 60 | 61 | #: ../data/shell.ui.h:5 62 | msgid "_Quit" 63 | msgstr "退出(_Q)" 64 | 65 | #: ../gtweak/app.py:76 66 | msgid "Reset to Defaults" 67 | msgstr "重設回預設值" 68 | 69 | #: ../gtweak/app.py:77 70 | msgid "Reset all tweak settings to the original default state?" 71 | msgstr "是否要將所有的調校設定值重設回原始預設狀態?" 72 | 73 | #: ../gtweak/app.py:93 74 | msgid "About GNOME Tweak Tool" 75 | msgstr "關於 GNOME 調校工具" 76 | 77 | #: ../gtweak/app.py:94 78 | msgid "GNOME Tweak Tool" 79 | msgstr "GNOME 調校工具" 80 | 81 | #: ../gtweak/app.py:98 82 | #, python-format 83 | msgid "GNOME Shell v%s (%s mode)" 84 | msgstr "GNOME Shell v%s (%s 模式)" 85 | 86 | #: ../gtweak/app.py:100 87 | msgid "GNOME Shell not running" 88 | msgstr "GNOME Shell 並未執行中" 89 | 90 | #: ../gtweak/app.py:105 91 | msgid "Homepage" 92 | msgstr "首頁" 93 | 94 | #: ../gtweak/tweakmodel.py:28 95 | msgid "Appearance" 96 | msgstr "外觀" 97 | 98 | #: ../gtweak/tweakmodel.py:29 99 | #: ../gtweak/tweaks/tweak_group_shell_extensions.py:304 100 | msgid "Extensions" 101 | msgstr "擴充套件" 102 | 103 | #: ../gtweak/tweakmodel.py:30 104 | msgid "Fonts" 105 | msgstr "字型" 106 | 107 | #: ../gtweak/tweakmodel.py:31 108 | msgid "Power" 109 | msgstr "電源" 110 | 111 | #: ../gtweak/tweakmodel.py:32 ../gtweak/tweaks/tweak_group_startup.py:182 112 | msgid "Startup Applications" 113 | msgstr "初始啟動應用程式" 114 | 115 | #: ../gtweak/tweakmodel.py:33 116 | msgid "Top Bar" 117 | msgstr "頂端列" 118 | 119 | #: ../gtweak/tweakmodel.py:34 120 | msgid "Windows" 121 | msgstr "視窗" 122 | 123 | #: ../gtweak/tweakmodel.py:35 124 | msgid "Workspaces" 125 | msgstr "工作區" 126 | 127 | #: ../gtweak/tweakmodel.py:37 ../gtweak/tweaks/tweak_group_keymouse.py:70 128 | msgid "Mouse" 129 | msgstr "滑鼠" 130 | 131 | #: ../gtweak/tweakmodel.py:38 132 | msgid "Files" 133 | msgstr "檔案" 134 | 135 | #: ../gtweak/tweakmodel.py:52 136 | msgid "Miscellaneous" 137 | msgstr "雜項" 138 | 139 | #: ../gtweak/tweakview.py:93 140 | msgid "Tweaks" 141 | msgstr "調校" 142 | 143 | #: ../gtweak/tweakview.py:109 144 | msgid "Search Tweaks..." 145 | msgstr "搜尋調校..." 146 | 147 | #: ../gtweak/tweaks/tweak_group_desktop.py:22 148 | msgid "Icons on Desktop" 149 | msgstr "放置圖示於桌面" 150 | 151 | #: ../gtweak/tweaks/tweak_group_desktop.py:25 152 | msgid "Desktop" 153 | msgstr "桌面" 154 | 155 | #: ../gtweak/tweaks/tweak_group_desktop.py:27 156 | msgid "Computer" 157 | msgstr "電腦" 158 | 159 | #: ../gtweak/tweaks/tweak_group_desktop.py:28 160 | msgid "Home" 161 | msgstr "家目錄" 162 | 163 | #: ../gtweak/tweaks/tweak_group_desktop.py:29 164 | msgid "Network Servers" 165 | msgstr "網絡伺服器" 166 | 167 | #: ../gtweak/tweaks/tweak_group_desktop.py:30 168 | msgid "Trash" 169 | msgstr "回收筒" 170 | 171 | #: ../gtweak/tweaks/tweak_group_desktop.py:31 172 | msgid "Mounted Volumes" 173 | msgstr "掛載的儲存區" 174 | 175 | #: ../gtweak/tweaks/tweak_group_desktop.py:32 176 | msgid "Background" 177 | msgstr "背景" 178 | 179 | #: ../gtweak/tweaks/tweak_group_desktop.py:33 180 | #: ../gtweak/tweaks/tweak_group_desktop.py:36 181 | msgid "Mode" 182 | msgstr "模式" 183 | 184 | #: ../gtweak/tweaks/tweak_group_desktop.py:34 185 | msgid "Background Location" 186 | msgstr "背景位置" 187 | 188 | #: ../gtweak/tweaks/tweak_group_desktop.py:37 189 | msgid "Lock Screen Location" 190 | msgstr "鎖定畫面位置" 191 | 192 | #: ../gtweak/tweaks/tweak_group_font.py:25 193 | msgid "Window Titles" 194 | msgstr "視窗標題" 195 | 196 | #: ../gtweak/tweaks/tweak_group_font.py:26 197 | msgid "Interface" 198 | msgstr "介面" 199 | 200 | #: ../gtweak/tweaks/tweak_group_font.py:27 201 | msgid "Documents" 202 | msgstr "文件" 203 | 204 | #: ../gtweak/tweaks/tweak_group_font.py:28 205 | msgid "Monospace" 206 | msgstr "等寬字型" 207 | 208 | #: ../gtweak/tweaks/tweak_group_font.py:29 209 | msgid "Hinting" 210 | msgstr "字繪提示" 211 | 212 | #: ../gtweak/tweaks/tweak_group_font.py:31 213 | msgid "Antialiasing" 214 | msgstr "防鋸齒" 215 | 216 | #: ../gtweak/tweaks/tweak_group_font.py:33 217 | msgid "Scaling Factor" 218 | msgstr "縮放系數" 219 | 220 | #: ../gtweak/tweaks/tweak_group_interface.py:60 221 | msgid "Icons" 222 | msgstr "圖示" 223 | 224 | #: ../gtweak/tweaks/tweak_group_interface.py:78 225 | msgid "Cursor" 226 | msgstr "游標" 227 | 228 | #: ../gtweak/tweaks/tweak_group_interface.py:96 229 | msgid "Window" 230 | msgstr "視窗" 231 | 232 | #: ../gtweak/tweaks/tweak_group_interface.py:121 233 | msgid "Shell theme" 234 | msgstr "Shell 主題" 235 | 236 | #: ../gtweak/tweaks/tweak_group_interface.py:121 237 | msgid "Install custom or user themes for gnome-shell" 238 | msgstr "為 gnome-shell 安裝自選主題或使用者主題" 239 | 240 | #. check the shell is running and the usertheme extension is present 241 | #: ../gtweak/tweaks/tweak_group_interface.py:124 242 | msgid "Unknown error" 243 | msgstr "未知錯誤" 244 | 245 | #: ../gtweak/tweaks/tweak_group_interface.py:129 246 | msgid "Shell not running" 247 | msgstr "Shell 不在執行中" 248 | 249 | #: ../gtweak/tweaks/tweak_group_interface.py:151 250 | msgid "Shell user-theme extension incorrectly installed" 251 | msgstr "Shell 使用者主題擴充套件安裝不正確" 252 | 253 | #: ../gtweak/tweaks/tweak_group_interface.py:154 254 | msgid "Shell user-theme extension not enabled" 255 | msgstr "Shell 使用者主題擴充套件未啟用" 256 | 257 | #: ../gtweak/tweaks/tweak_group_interface.py:157 258 | msgid "Could not list shell extensions" 259 | msgstr "無法列出 Shell 擴充套件" 260 | 261 | #: ../gtweak/tweaks/tweak_group_interface.py:186 262 | #: ../gtweak/tweaks/tweak_group_keymouse.py:46 263 | msgid "Default" 264 | msgstr "預設" 265 | 266 | #: ../gtweak/tweaks/tweak_group_interface.py:192 267 | msgid "Select a theme" 268 | msgstr "選取主題" 269 | 270 | #: ../gtweak/tweaks/tweak_group_interface.py:241 271 | #, python-format 272 | msgid "%s theme updated successfully" 273 | msgstr "%s 主題成功更新" 274 | 275 | #: ../gtweak/tweaks/tweak_group_interface.py:243 276 | #, python-format 277 | msgid "%s theme installed successfully" 278 | msgstr "%s 主題成功安裝" 279 | 280 | #: ../gtweak/tweaks/tweak_group_interface.py:251 281 | msgid "Error installing theme" 282 | msgstr "安裝主題時發生錯誤" 283 | 284 | #. does not look like a valid theme 285 | #: ../gtweak/tweaks/tweak_group_interface.py:256 286 | msgid "Invalid theme" 287 | msgstr "無效的主題" 288 | 289 | #. GSettingsSwitchTweak("Buttons Icons","org.gnome.desktop.interface", "buttons-have-icons"), 290 | #. GSettingsSwitchTweak("Menu Icons","org.gnome.desktop.interface", "menus-have-icons"), 291 | #: ../gtweak/tweaks/tweak_group_interface.py:272 292 | msgid "Theme" 293 | msgstr "佈景主題" 294 | 295 | #: ../gtweak/tweaks/tweak_group_xkb.py:46 296 | #: ../gtweak/tweaks/tweak_group_windows.py:62 297 | msgid "Disabled" 298 | msgstr "已停用" 299 | 300 | #: ../gtweak/tweaks/tweak_group_xkb.py:131 301 | msgid "Typing" 302 | msgstr "輸入" 303 | 304 | #: ../gtweak/tweaks/tweak_group_shell_extensions.py:74 305 | msgid "Extension downloading" 306 | msgstr "擴充套件下載中" 307 | 308 | #: ../gtweak/tweaks/tweak_group_shell_extensions.py:76 309 | msgid "Error loading extension" 310 | msgstr "載入擴充套件時發生錯誤" 311 | 312 | #: ../gtweak/tweaks/tweak_group_shell_extensions.py:78 313 | msgid "Extension does not support shell version" 314 | msgstr "擴充套件不支援 Shell 版本" 315 | 316 | #: ../gtweak/tweaks/tweak_group_shell_extensions.py:80 317 | msgid "Unknown extension error" 318 | msgstr "未知的擴充套件錯誤" 319 | 320 | #: ../gtweak/tweaks/tweak_group_shell_extensions.py:105 321 | #: ../gtweak/tweaks/tweak_group_startup.py:131 322 | msgid "Remove" 323 | msgstr "移除" 324 | 325 | #: ../gtweak/tweaks/tweak_group_shell_extensions.py:137 326 | msgid "Uninstall Extension" 327 | msgstr "解除安裝擴充套件" 328 | 329 | #: ../gtweak/tweaks/tweak_group_shell_extensions.py:138 330 | #, python-format 331 | msgid "Do you want to uninstall the '%s' extension?" 332 | msgstr "你是否想要解除「%s」擴充套件的安裝?" 333 | 334 | #: ../gtweak/tweaks/tweak_group_shell_extensions.py:155 335 | msgid "Updating" 336 | msgstr "更新中" 337 | 338 | #: ../gtweak/tweaks/tweak_group_shell_extensions.py:166 339 | msgid "Error" 340 | msgstr "錯誤" 341 | 342 | #: ../gtweak/tweaks/tweak_group_shell_extensions.py:171 343 | msgid "Update" 344 | msgstr "更新" 345 | 346 | #: ../gtweak/tweaks/tweak_group_shell_extensions.py:186 347 | msgid "Install Shell Extension" 348 | msgstr "安裝 Shell 擴充套件" 349 | 350 | #: ../gtweak/tweaks/tweak_group_shell_extensions.py:191 351 | msgid "Select an extension" 352 | msgstr "選取擴充套件" 353 | 354 | #: ../gtweak/tweaks/tweak_group_shell_extensions.py:198 355 | msgid "Get more extensions" 356 | msgstr "取得更多擴充套件" 357 | 358 | #: ../gtweak/tweaks/tweak_group_shell_extensions.py:248 359 | #, python-format 360 | msgid "%s extension updated successfully" 361 | msgstr "%s 擴充套件已成功更新" 362 | 363 | #: ../gtweak/tweaks/tweak_group_shell_extensions.py:250 364 | #, python-format 365 | msgid "%s extension installed successfully" 366 | msgstr "%s 擴充套件已成功安裝" 367 | 368 | #: ../gtweak/tweaks/tweak_group_shell_extensions.py:254 369 | msgid "Restart" 370 | msgstr "重新啟動" 371 | 372 | #: ../gtweak/tweaks/tweak_group_shell_extensions.py:258 373 | msgid "Error installing extension" 374 | msgstr "安裝擴充套件時發生錯誤" 375 | 376 | #. does not look like a valid theme 377 | #: ../gtweak/tweaks/tweak_group_shell_extensions.py:263 378 | msgid "Invalid extension" 379 | msgstr "無效的擴充套件" 380 | 381 | #: ../gtweak/tweaks/tweak_group_shell.py:32 382 | msgid "Show Application Menu" 383 | msgstr "顯示應用程式選單" 384 | 385 | #: ../gtweak/tweaks/tweak_group_shell.py:45 386 | msgid "Workspace Creation" 387 | msgstr "工作區建立" 388 | 389 | #: ../gtweak/tweaks/tweak_group_shell.py:50 390 | msgid "Dynamic" 391 | msgstr "動態" 392 | 393 | #: ../gtweak/tweaks/tweak_group_shell.py:50 394 | msgid "Static" 395 | msgstr "靜態" 396 | 397 | #: ../gtweak/tweaks/tweak_group_shell.py:71 398 | msgid "Clock" 399 | msgstr "時鐘" 400 | 401 | #: ../gtweak/tweaks/tweak_group_shell.py:72 402 | msgid "Show date" 403 | msgstr "顯示日期" 404 | 405 | #: ../gtweak/tweaks/tweak_group_shell.py:73 406 | msgid "Show seconds" 407 | msgstr "顯示秒" 408 | 409 | #: ../gtweak/tweaks/tweak_group_shell.py:74 410 | msgid "Calendar" 411 | msgstr "行事曆" 412 | 413 | #: ../gtweak/tweaks/tweak_group_shell.py:75 414 | msgid "Show week numbers" 415 | msgstr "顯示週次" 416 | 417 | #: ../gtweak/tweaks/tweak_group_shell.py:78 418 | msgid "Power Button Action" 419 | msgstr "電源按鈕動作" 420 | 421 | #: ../gtweak/tweaks/tweak_group_shell.py:79 422 | msgid "When Laptop Lid is Closed" 423 | msgstr "當闔上筆電上蓋時" 424 | 425 | #: ../gtweak/tweaks/tweak_group_shell.py:80 426 | msgid "On Battery Power" 427 | msgstr "使用電池電源時" 428 | 429 | #: ../gtweak/tweaks/tweak_group_shell.py:81 430 | msgid "When plugged in" 431 | msgstr "當插入電源線時" 432 | 433 | #: ../gtweak/tweaks/tweak_group_shell.py:82 434 | msgid "Suspend even if an external monitor is plugged in" 435 | msgstr "但如果插入外接螢幕時則暫停" 436 | 437 | #: ../gtweak/tweaks/tweak_group_shell.py:86 438 | msgid "Number of Workspaces" 439 | msgstr "工作區數目" 440 | 441 | #: ../gtweak/tweaks/tweak_group_shell.py:87 442 | msgid "Workspaces only on primary display" 443 | msgstr "工作區僅在主顯示器上" 444 | 445 | #: ../gtweak/tweaks/tweak_group_startup.py:35 446 | msgid "Applications" 447 | msgstr "應用程式" 448 | 449 | #: ../gtweak/tweaks/tweak_group_startup.py:51 450 | msgid "running" 451 | msgstr "執行中" 452 | 453 | #: ../gtweak/tweaks/tweak_group_startup.py:61 454 | msgid "_Close" 455 | msgstr "關閉(_C)" 456 | 457 | #: ../gtweak/tweaks/tweak_group_startup.py:62 458 | msgid "Add Application" 459 | msgstr "加入應用程式" 460 | 461 | #: ../gtweak/tweaks/tweak_group_startup.py:147 462 | msgid "New startup application" 463 | msgstr "新增初始啟動期應用程式" 464 | 465 | #: ../gtweak/tweaks/tweak_group_startup.py:148 466 | msgid "Add a new application to be run at startup" 467 | msgstr "新增初始啟動期間要執行的應用程式" 468 | 469 | #: ../gtweak/tweaks/tweak_group_windows.py:56 470 | msgid "Attached Modal Dialogs" 471 | msgstr "附連的典範對話盒" 472 | 473 | #: ../gtweak/tweaks/tweak_group_windows.py:57 474 | msgid "Automatically Raise Windows" 475 | msgstr "自動抬升視窗" 476 | 477 | #: ../gtweak/tweaks/tweak_group_windows.py:58 478 | msgid "Resize with Secondary-click" 479 | msgstr "以次鍵點擊調整視窗大小" 480 | 481 | #: ../gtweak/tweaks/tweak_group_windows.py:59 482 | msgid "Window Action Key" 483 | msgstr "視窗動作按鍵" 484 | 485 | #: ../gtweak/tweaks/tweak_group_windows.py:63 486 | msgid "Focus Mode" 487 | msgstr "焦點模式" 488 | 489 | #: ../gtweak/tweaks/tweak_group_windows.py:64 490 | msgid "Titlebar Actions" 491 | msgstr "標題列動作" 492 | 493 | #: ../gtweak/tweaks/tweak_group_windows.py:65 494 | msgid "Double-click" 495 | msgstr "連按兩下" 496 | 497 | #: ../gtweak/tweaks/tweak_group_windows.py:66 498 | msgid "Middle-click" 499 | msgstr "中鍵點擊" 500 | 501 | #: ../gtweak/tweaks/tweak_group_windows.py:67 502 | msgid "Secondary-click" 503 | msgstr "次鍵點擊" 504 | 505 | #: ../gtweak/tweaks/tweak_group_windows.py:68 506 | msgid "Titlebar Buttons" 507 | msgstr "標題列按鈕" 508 | 509 | #: ../gtweak/tweaks/tweak_group_windows.py:69 510 | msgid "Maximize" 511 | msgstr "最大化" 512 | 513 | #: ../gtweak/tweaks/tweak_group_windows.py:70 514 | msgid "Minimize" 515 | msgstr "最小化" 516 | 517 | #: ../gtweak/tweaks/tweak_group_keymouse.py:29 518 | msgid "Middle-click Paste" 519 | msgstr "滑鼠中鍵貼上" 520 | 521 | #: ../gtweak/tweaks/tweak_group_keymouse.py:40 522 | msgid "Key theme" 523 | msgstr "按鍵主題" 524 | 525 | #: ../gtweak/tweaks/tweak_group_keymouse.py:59 526 | msgid "Keyboard and Mouse" 527 | msgstr "鍵盤與滑鼠" 528 | 529 | #: ../gtweak/tweaks/tweak_group_keymouse.py:60 530 | msgid "Show All Input Sources" 531 | msgstr "顯示所有的輸入來源" 532 | 533 | #: ../gtweak/tweaks/tweak_group_keymouse.py:65 534 | msgid "Switch between overview and desktop" 535 | msgstr "在活動概覽與桌面之間切換" 536 | 537 | #: ../gtweak/tweaks/tweak_group_keymouse.py:68 538 | msgid "Super left" 539 | msgstr "Super 左鍵" 540 | 541 | #: ../gtweak/tweaks/tweak_group_keymouse.py:68 542 | msgid "Super right" 543 | msgstr "Super 右鍵" 544 | 545 | #: ../gtweak/tweaks/tweak_group_keymouse.py:71 546 | msgid "Show location of pointer" 547 | msgstr "顯示指標的位置" 548 | 549 | #: ../gtweak/tweaks/tweak_wacom.py:29 550 | msgid "Device" 551 | msgstr "裝置" 552 | 553 | #: ../gtweak/tweaks/tweak_wacom.py:38 554 | msgid "Wacom" 555 | msgstr "Wacom" 556 | 557 | #. indicates the default theme, e.g Adwaita (default) 558 | #: ../gtweak/utils.py:70 559 | #, python-format 560 | msgid "%s (default)" 561 | msgstr "%s (預設)" 562 | 563 | #: ../gtweak/widgets.py:471 564 | msgid "Enable dark theme for all applications" 565 | msgstr "為所有應用程式啟用深黑主題" 566 | 567 | #: ../gtweak/widgets.py:472 568 | msgid "Enable the dark theme hint for all the applications in the session" 569 | msgstr "為工作階段中的所有應用程式啟用深黑主題指示" 570 | 571 | #: ../gtweak/widgets.py:480 572 | msgid "Global Dark Theme" 573 | msgstr "全域深黑佈景主題" 574 | 575 | #: ../gtweak/widgets.py:481 576 | msgid "Applications need to be restarted for change to take effect" 577 | msgstr "應用程式需要重新啟動才能套用效果" 578 | 579 | #: ../gtweak/widgets.py:507 580 | msgid "Error writing setting" 581 | msgstr "寫入設定值時發生錯誤" 582 | 583 | #~ msgid "_About Tweak Tool" 584 | #~ msgstr "關於調校工具(_A)" 585 | 586 | #~ msgid "The shell must be restarted for changes to take effect" 587 | #~ msgstr "Shell 的變更必須重新啟動才能套用效果" 588 | 589 | #~ msgid "welcome" 590 | #~ msgstr "歡迎" 591 | 592 | #~ msgid "Shell" 593 | #~ msgstr "Shell" 594 | 595 | #~ msgid "Shell Extensions" 596 | #~ msgstr "Shell 擴充套件" 597 | 598 | #~ msgid "All" 599 | #~ msgstr "全部" 600 | 601 | #~ msgid "" 602 | #~ "Disable gnome-shell dynamic workspace management, use static workspaces" 603 | #~ msgstr "停用 gnome-shell 動態工作區管理,改使用靜態工作區" 604 | 605 | #~ msgid "Advanced Settings" 606 | #~ msgstr "進階設定值" 607 | 608 | #~ msgid "Icon theme" 609 | #~ msgstr "圖示主題" 610 | 611 | #~ msgid "Keybinding theme" 612 | #~ msgstr "按鍵繫結主題" 613 | 614 | #~ msgid "The shell may need to be restarted to apply the theme" 615 | #~ msgstr "Shell 可能需要重新啟動才能套用主題" 616 | --------------------------------------------------------------------------------