├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── convenience.js ├── data ├── glib-2.0 │ └── schemas └── icons ├── extension.js ├── floatlayout.js ├── gnomesome_settings.js ├── gswindow.js ├── icons └── status │ ├── gnomesome-window-tile-floating-symbolic.svg │ ├── gnomesome-window-tile-full-symbolic.svg │ ├── gnomesome-window-tile-horizontal-symbolic.svg │ └── gnomesome-window-tile-vertical-symbolic.svg ├── layout.js ├── logging.js ├── manager.js ├── maximizelayout.js ├── menubutton.js ├── metadata.json ├── prefs.js ├── schemas ├── gschemas.compiled ├── org.gnome.shell.extensions.gnomesome.gschema.xml └── org.gnome.shell.extensions.gnomesome.keybindings.gschema.xml ├── splitlayout.js ├── stylesheet.css ├── thirdparty └── log4js │ ├── README.md │ ├── log4javascript.js │ ├── log4javascript_file_appender.js │ └── log4javascript_gjs_appender.js └── utils.js /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | *.zip 3 | *.swp 4 | *.tmp 5 | *~ 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Christoph Wick 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | SHELL := /bin/bash 2 | 3 | JS_FILES = *.js thirdparty 4 | 5 | .PHONY: clean all 6 | 7 | all: gnomesome.zip 8 | 9 | extension: ./schemas/gschemas.compiled 10 | 11 | ./schemas/gschemas.compiled: 12 | glib-compile-schemas --strict ./schemas 13 | 14 | gnomesome.zip: extension 15 | zip gnomesome.zip -r $(JS_FILES) metadata.json locale/*/*/*.mo schemas data icons 16 | 17 | clean: 18 | rm -rf gnomesome.zip ./schemas/gschemas.compiled 19 | 20 | install: 21 | test -e ~/.local/share/gnome-shell/extensions/gnomesome@chwick.github.com || ln -s $(PWD) ~/.local/share/gnome-shell/extensions/gnomesome@chwick.github.com 22 | 23 | remove: 24 | rm ~/.local/share/gnome-shell/extensions/gnomesome@chwick.github.com 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # gnomesome 2 | 3 | [![paypal](https://img.shields.io/badge/Donate-PayPal-green.svg)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=H4Q5TXRLXM4LE&source=url) 4 | 5 | 6 | Yet another gnome extension for window tiling inspired by awesome supporting multiple workspaces and screens. 7 | 8 | ### Suggested extensions to improve gnomesome 9 | * To remove title bars: https://extensions.gnome.org/extension/723/pixel-saver/ 10 | * For a better multi monitor support: https://extensions.gnome.org/extension/921/multi-monitors-add-on/ 11 | 12 | ### Disable Mod4+Digit keybindings (Gnome 3.32 / Ubuntu 19.04 or later) 13 | By default the dash-to-dock extension of Ubuntu overrides all Mod4+[Shift]+Digit keybindings for selecting a workspace, or moving windows to a certain workspace. 14 | To disable these shortcuts open the deconf-Editor, navigate to `org/gnome/shell/extensions/dash-to-dock/hot-keys` and set it to `false`, or use: 15 | ``` 16 | gsettings set org.gnome.shell.extensions.dash-to-dock hot-keys false 17 | ``` 18 | 19 | On Ubuntu 19.04 you need also to [disable all keybindings](https://askubuntu.com/questions/968103/disable-the-default-app-key-supernum-functionality-on-ubuntu-17-10-and-later) (see also [here](https://gitlab.gnome.org/GNOME/gnome-shell/-/issues/1250)): 20 | ``` 21 | gsettings set org.gnome.shell.keybindings switch-to-application-1 [] 22 | gsettings set org.gnome.shell.keybindings switch-to-application-2 [] 23 | gsettings set org.gnome.shell.keybindings switch-to-application-3 [] 24 | gsettings set org.gnome.shell.keybindings switch-to-application-4 [] 25 | gsettings set org.gnome.shell.keybindings switch-to-application-5 [] 26 | gsettings set org.gnome.shell.keybindings switch-to-application-6 [] 27 | gsettings set org.gnome.shell.keybindings switch-to-application-7 [] 28 | gsettings set org.gnome.shell.keybindings switch-to-application-8 [] 29 | gsettings set org.gnome.shell.keybindings switch-to-application-9 [] 30 | ``` 31 | 32 | ### Optional: Set static workspaces 33 | For a better feeling and working with multiple workspaces you should set the workspaces as static: 34 | Open the Tweaks Tool and navigate to workspaces. Check `Static Workspaces` and change to your desired number of workspaces, e. g. 10. Also you might want to check `workspaces span displays`. 35 | 36 | ## Current supported layouts 37 | * Floating 38 | * Vertically tiled 39 | * Horizontally tiled 40 | 41 | ## Keybindings 42 | 43 | * `Mod4+e`: Select the next layout on the current monitor and workspace 44 | * `Mod4+Shift+e`: Select the previous layout on the current monitor and workspace 45 | * `Mod4+Ctrl+f`: Switch to the floating layout 46 | * `Mod4+Ctrl+h`: Switch to the horizontal box layout 47 | * `Mod4+Ctrl+v`: Switch to the vertical box layout 48 | * `Mod4+Ctrl+m`: Switch to the maximized layout 49 | * `Mod4+j`: Select the next window on the current monitor and workspace 50 | * `Mod4+k`: Select the previous window on the current monitor and workspace 51 | * `Mod4+Shift+j`: Swap the current client with the next client in a layout 52 | * `Mod4+Shift+k`: Swap the current client with the previous client in a layout 53 | * `Mod4+Ctrl+j`: Select the next monitor 54 | * `Mod4+Ctrl+k`: Select the previous monitor 55 | * `Mod4+o`: Move the active window to the next monitor 56 | * `Mod4+Ctrl+Shift+j`: Move the active window to the next monitor 57 | * `Mod4+Ctrl+Shift+k`: Move the active window to the previous monitor 58 | * `Mod4+i`: Increase the master window area 59 | * `Mod4+u`: Decrease the master window area 60 | * `Mod4+Shift+i`: Increase the number of master windows 61 | * `Mod4+Shift+u`: Decrease the number of master windows 62 | * `Mod4+Ctrl+Return`: Swap the current window with the master 63 | * `Mod4+(1-5)`: Select the workspace with id (1-5) 64 | * `Mod4+Ctrl+(1-5)`: Move the current window to the workspace with id (1-5) 65 | * `Mod4+Shift+m`: Toggle maximize of the current window 66 | * `Mod4+Shift+f`: Toggle fullscreen of the current window 67 | * `Mod4+f`: Toggle floating of the current window 68 | * `Mod4+return`: Launch a gnome terminal 69 | 70 | ## Install 71 | To install the extension to the gnome-shell default path `~/.local/share/gnome-shell/extensions` run `make install`. To remove the extension run `make remove`. You need to restart gnome `Alt+F2 r` and enable the extension in the tweak tool to activate it. 72 | 73 | To run the settings dialog you need to install clutter: 74 | * Ubuntu: `sudo apt install gir1.2-clutter-1.0 gir1.2-clutter-gst-3.0 gir1.2-gtkclutter-1.0` 75 | -------------------------------------------------------------------------------- /convenience.js: -------------------------------------------------------------------------------- 1 | /* -*- mode: js; js-basic-offset: 4; indent-tabs-mode: nil -*- */ 2 | 3 | /* 4 | * Part of this file comes from gnome-shell-extensions: 5 | * http://git.gnome.org/browse/gnome-shell-extensions/ 6 | */ 7 | 8 | const Clutter = imports.gi.Clutter; 9 | const Gettext = imports.gettext; 10 | const Gio = imports.gi.Gio; 11 | const Lang = imports.lang; 12 | 13 | const Config = imports.misc.config; 14 | const ExtensionUtils = imports.misc.extensionUtils; 15 | 16 | /** 17 | * initTranslations: 18 | * @domain: (optional): the gettext domain to use 19 | * 20 | * Initialize Gettext to load translations from extensionsdir/locale. 21 | * If @domain is not provided, it will be taken from metadata['gettext-domain'] 22 | */ 23 | function initTranslations(domain) { 24 | let extension = ExtensionUtils.getCurrentExtension(); 25 | 26 | domain = domain || extension.metadata['gettext-domain']; 27 | 28 | // Check if this extension was built with "make zip-file", and thus 29 | // has the locale files in a subfolder 30 | // otherwise assume that extension has been installed in the 31 | // same prefix as gnome-shell 32 | let localeDir = extension.dir.get_child('locale'); 33 | if (localeDir.query_exists(null)) 34 | Gettext.bindtextdomain(domain, localeDir.get_path()); 35 | else 36 | Gettext.bindtextdomain(domain, Config.LOCALEDIR); 37 | } 38 | 39 | /** 40 | * getSettings: 41 | * @schema: (optional): the GSettings schema id 42 | * 43 | * Builds and return a GSettings schema for @schema, using schema files 44 | * in extensionsdir/schemas. If @schema is not provided, it is taken from 45 | * metadata['settings-schema']. 46 | */ 47 | function getSettings(schema) { 48 | let extension = ExtensionUtils.getCurrentExtension(); 49 | 50 | schema = schema || extension.metadata['settings-schema']; 51 | 52 | const GioSSS = Gio.SettingsSchemaSource; 53 | 54 | // Check if this extension was built with "make zip-file", and thus 55 | // has the schema files in a subfolder 56 | // otherwise assume that extension has been installed in the 57 | // same prefix as gnome-shell (and therefore schemas are available 58 | // in the standard folders) 59 | let schemaDir = extension.dir.get_child('schemas'); 60 | let schemaSource; 61 | if (schemaDir.query_exists(null)) 62 | schemaSource = GioSSS.new_from_directory(schemaDir.get_path(), 63 | GioSSS.get_default(), 64 | false); 65 | else 66 | schemaSource = GioSSS.get_default(); 67 | 68 | let schemaObj = schemaSource.lookup(schema, true); 69 | if (!schemaObj) 70 | throw new Error('Schema ' + schema + ' could not be found for extension ' 71 | + extension.metadata.uuid + '. Please check your installation.'); 72 | 73 | return new Gio.Settings({ 74 | settings_schema: schemaObj 75 | }); 76 | } 77 | 78 | /** 79 | * Simplify global signals and function injections handling 80 | * abstract class 81 | */ 82 | const BasicHandler = new Lang.Class({ 83 | Name: 'DashToDock.BasicHandler', 84 | 85 | _init: function() { 86 | this._storage = new Object(); 87 | }, 88 | 89 | add: function(/* unlimited 3-long array arguments */) { 90 | // Convert arguments object to array, concatenate with generic 91 | let args = Array.concat('generic', Array.slice(arguments)); 92 | // Call addWithLabel with ags as if they were passed arguments 93 | this.addWithLabel.apply(this, args); 94 | }, 95 | 96 | destroy: function() { 97 | for( let label in this._storage ) 98 | this.removeWithLabel(label); 99 | }, 100 | 101 | addWithLabel: function(label /* plus unlimited 3-long array arguments*/) { 102 | if (this._storage[label] == undefined) 103 | this._storage[label] = new Array(); 104 | 105 | // Skip first element of the arguments 106 | for (let i = 1; i < arguments.length; i++) { 107 | this._storage[label].push( this._create(arguments[i])); 108 | } 109 | }, 110 | 111 | removeWithLabel: function(label) { 112 | if (this._storage[label]) { 113 | for (let i = 0; i < this._storage[label].length; i++) 114 | this._remove(this._storage[label][i]); 115 | 116 | delete this._storage[label]; 117 | } 118 | }, 119 | 120 | // Virtual methods to be implemented by subclass 121 | 122 | /** 123 | * Create single element to be stored in the storage structure 124 | */ 125 | _create: function(item) { 126 | throw new Error('no implementation of _create in ' + this); 127 | }, 128 | 129 | /** 130 | * Correctly delete single element 131 | */ 132 | _remove: function(item) { 133 | throw new Error('no implementation of _remove in ' + this); 134 | } 135 | }); 136 | 137 | /** 138 | * Manage global signals 139 | */ 140 | const GlobalSignalsHandler = new Lang.Class({ 141 | Name: 'DashToDock.GlobalSignalHandler', 142 | Extends: BasicHandler, 143 | 144 | _create: function(item) { 145 | let object = item[0]; 146 | let event = item[1]; 147 | let callback = item[2] 148 | let id = object.connect(event, callback); 149 | 150 | return [object, id]; 151 | }, 152 | 153 | _remove: function(item) { 154 | item[0].disconnect(item[1]); 155 | } 156 | }); 157 | 158 | /** 159 | * Manage function injection: both instances and prototype can be overridden 160 | * and restored 161 | */ 162 | const InjectionsHandler = new Lang.Class({ 163 | Name: 'DashToDock.InjectionsHandler', 164 | Extends: BasicHandler, 165 | 166 | _create: function(item) { 167 | let object = item[0]; 168 | let name = item[1]; 169 | let injectedFunction = item[2]; 170 | let original = object[name]; 171 | 172 | object[name] = injectedFunction; 173 | return [object, name, injectedFunction, original]; 174 | }, 175 | 176 | _remove: function(item) { 177 | let object = item[0]; 178 | let name = item[1]; 179 | let original = item[3]; 180 | object[name] = original; 181 | } 182 | }); 183 | 184 | /** 185 | * Return the actual position reverseing left and right in rtl 186 | */ 187 | function getPosition(settings) { 188 | let position = settings.get_enum('dock-position'); 189 | if (Clutter.get_default_text_direction() == Clutter.TextDirection.RTL) { 190 | if (position == St.Side.LEFT) 191 | position = St.Side.RIGHT; 192 | else if (position == St.Side.RIGHT) 193 | position = St.Side.LEFT; 194 | } 195 | return position; 196 | } 197 | -------------------------------------------------------------------------------- /data/glib-2.0/schemas: -------------------------------------------------------------------------------- 1 | ../../schemas -------------------------------------------------------------------------------- /data/icons: -------------------------------------------------------------------------------- 1 | ../icons -------------------------------------------------------------------------------- /extension.js: -------------------------------------------------------------------------------- 1 | const Main = imports.ui.main; 2 | 3 | const Me = imports.misc.extensionUtils.getCurrentExtension(); 4 | const Manager = Me.imports.manager; 5 | const Logging = Me.imports.logging; 6 | 7 | let manager; 8 | 9 | function init() { 10 | global.log("[gnomesome] Initalizing log."); 11 | Logging.init(true); 12 | } 13 | 14 | function enable() { 15 | manager = new Manager.Manager; 16 | } 17 | 18 | function disable() { 19 | manager.destroy(); 20 | } 21 | -------------------------------------------------------------------------------- /floatlayout.js: -------------------------------------------------------------------------------- 1 | function enterFloatingLayout(gswindows, split_pos, n_master) { 2 | // restore all window geometry 3 | for (var idx = 0; idx < gswindows.length; ++idx) { 4 | gswindows[idx].restore_geometry(); 5 | } 6 | } 7 | 8 | function updateFloatingLayout(gswindows, split_pos, n_master) { 9 | 10 | } 11 | 12 | function exitFloatingLayout(gswindows, split_pos, n_master) { 13 | // store all window geomety 14 | for (var idx = 0; idx < gswindows.length; ++idx) { 15 | gswindows[idx].store_geometry(); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /gnomesome_settings.js: -------------------------------------------------------------------------------- 1 | const Gio = imports.gi.Gio; 2 | const Glib = imports.gi.GLib; 3 | const Config = imports.misc.config; 4 | const ExtensionUtils = imports.misc.extensionUtils; 5 | const Ext = ExtensionUtils.getCurrentExtension(); 6 | const GnomesomeSettingsConvenience = Ext.imports.convenience; 7 | 8 | const SCHEMA_ROOT = 'org.gnome.shell.extensions.gnomesome'; 9 | const KEYBINDINGS = SCHEMA_ROOT + '.keybindings'; 10 | const PREFS = SCHEMA_ROOT + '.prefs'; 11 | 12 | function envp_with_gnomesome_xdg_data_dir() { 13 | var xdg_data_base = Ext.dir.get_child('data'); 14 | if(!xdg_data_base.query_exists(null)) { 15 | return null; 16 | } 17 | xdg_data_base = xdg_data_base.get_path(); 18 | 19 | var XDG_DATA_DIRS = 'XDG_DATA_DIRS'; 20 | var old_xdg_data = Glib.getenv(XDG_DATA_DIRS); 21 | var new_xdg_data = null; 22 | if(old_xdg_data != null) { 23 | var entries = old_xdg_data.split(':'); 24 | if(entries.indexOf(xdg_data_base) == -1) { 25 | new_xdg_data = old_xdg_data + ':' + xdg_data_base; 26 | } 27 | } else { 28 | var default_xdg = "/usr/local/share/:/usr/share/"; 29 | new_xdg_data = default_xdg + ":" + xdg_data_base; 30 | } 31 | 32 | //TODO: so much effort to modify one key in the environment, 33 | // surely there is an easier way... 34 | var strings = []; 35 | strings.push(XDG_DATA_DIRS + '=' + new_xdg_data); 36 | var keys = Glib.listenv(); 37 | for(var i in keys) { 38 | var key = keys[i]; 39 | if(key == XDG_DATA_DIRS) continue; 40 | var val = Glib.getenv(key); 41 | strings.push(key + '=' + val); 42 | }; 43 | return strings; 44 | }; 45 | 46 | function get_local_gsettings(schema_path) { 47 | return GnomesomeSettingsConvenience.getSettings(schema_path); 48 | }; 49 | 50 | function Keybindings() { 51 | var self = this; 52 | var settings = this.settings = get_local_gsettings(KEYBINDINGS); 53 | this.each = function(fn, ctx) { 54 | var keys = settings.list_children(); 55 | for (var i=0; i < keys.length; i++) { 56 | var key = keys[i]; 57 | var setting = { 58 | key: key, 59 | get: function() { return settings.get_string_array(key); }, 60 | set: function(v) { settings.set_string_array(key, v); }, 61 | }; 62 | fn.call(ctx, setting); 63 | } 64 | }; 65 | }; 66 | 67 | function settingsLoader(path) { 68 | var settings = get_local_gsettings(PREFS); 69 | return { 70 | settings: settings, 71 | get_boolean: function() { return settings.get_boolean(this.key); }, 72 | set_boolean: function(v) { settings.set_boolean(this.key, v); }, 73 | get_int: function() { return settings.get_int(this.key); }, 74 | set_int: function(v) { settings.set_int(this.key, v); }, 75 | get_string: function() { return settings.get_string(this.key); }, 76 | set_string: function(v) { settings.set_string(this.key, v); } 77 | } 78 | } 79 | 80 | function Prefs() { 81 | var self = this; 82 | let l = settingsLoader(PREFS); 83 | var settings = this.settings = l.settings; 84 | 85 | this.SHOW_INDICATOR = { 86 | key: 'show-indicator', 87 | gsettings: settings, 88 | get: l.get_boolean, 89 | set: l.set_boolean, 90 | }; 91 | this.DEFAULT_LAYOUT = { 92 | key: 'default-layout', 93 | gsettings: settings, 94 | get: l.get_string, 95 | set: l.set_string, 96 | }; 97 | this.LAUNCH_TERMINAL = { 98 | key: 'launch-terminal', 99 | gsettings: settings, 100 | get: l.get_string, 101 | set: l.set_string, 102 | }; 103 | this.OUTER_GAPS = { 104 | key: 'outer-gaps', 105 | gsettings: settings, 106 | get: l.get_int, 107 | set: l.set_int, 108 | }; 109 | this.INNER_GAPS = { 110 | key: 'inner-gaps', 111 | gsettings: settings, 112 | get: l.get_int, 113 | set: l.set_int, 114 | }; 115 | this.POINTER_FOLLOWS_FOCUS = { 116 | key: 'pointer-follows-focus', 117 | gsettings: settings, 118 | get: l.get_boolean, 119 | set: l.set_boolean, 120 | }; 121 | }; 122 | 123 | function initTranslations(domain) { 124 | GnomesomeSettingsConvenience.initTranslations(domain); 125 | } 126 | 127 | var prefs = new Prefs(); 128 | -------------------------------------------------------------------------------- /gswindow.js: -------------------------------------------------------------------------------- 1 | const Lang = imports.lang; 2 | const Meta = imports.gi.Meta; 3 | const Me = imports.misc.extensionUtils.getCurrentExtension(); 4 | const Utils = Me.imports.utils; 5 | const logging = Me.imports.logging; 6 | const logger = logging.getLogger('Gnomesome.GSWindow'); 7 | const Gdk = imports.gi.Gdk; 8 | const prefs = Me.imports.gnomesome_settings.prefs; 9 | 10 | var AllowedMetaTypes = [ 11 | Meta.WindowType.NORMAL, 12 | Meta.WindowType.DIALOG, 13 | Meta.WindowType.TOOLBAR, 14 | Meta.WindowType.UTILITY, 15 | Meta.WindowType.SPLASHSCREEN, 16 | ]; 17 | 18 | var GSWindow = new Lang.Class({ 19 | Name: 'Gnomesome.Window', 20 | 21 | _init: function(window, gslayout) { 22 | this.window = window; 23 | this.floating = false; 24 | this.gslayout = gslayout; 25 | this.geometry = this.rect(); 26 | Utils.connect_and_track(this, window, 'notify::minimized', Lang.bind(this, function() { this._requestRelayout(); })); 27 | Utils.connect_and_track(this, window, 'notify::fullscreen', Lang.bind(this, function() { this._requestRelayout(); })); 28 | // relayout on maximize/minimize leads to timeout in relayout loop 29 | // window.connect('notify::maximized-horizontally', relayout_window); 30 | }, 31 | destroy: function() { 32 | logger.info("Cleaning up window."); 33 | Utils.disconnect_tracked_signals(this); 34 | if (this.gslayout) { this.gslayout.removeGSWindow(this); } 35 | this.window.gswindow = undefined; 36 | this.gslayout = null; 37 | }, 38 | reset: function() { 39 | this.floating = false; 40 | this.geometry = this.rect(); 41 | }, 42 | is_ready: function() { 43 | var rect = this.rect(); 44 | logger.debug("Window size: h " + rect.height + " w " + rect.width + " x " +rect.x + " y " + rect.y); 45 | if (rect.width === 0 || rect.height === 0) { 46 | return false; 47 | } 48 | return true; 49 | }, 50 | store_geometry: function() { 51 | this.geometry = this.rect(); 52 | }, 53 | restore_geometry: function() { 54 | this.set_maximize(false, false); 55 | this.window.move_resize_frame( 56 | true, 57 | this.geometry.x, this.geometry.y, 58 | this.geometry.width, this.geometry.height); 59 | }, 60 | get_workspace: function() { 61 | return this.window.get_workspace(); 62 | }, 63 | get_monitor: function() { 64 | return this.window.get_monitor(); 65 | }, 66 | layoutAllowed: function() { 67 | if ( this.window.get_role() == "quake" ) { 68 | return false; 69 | } 70 | if (!this.is_ready()) {return false;} 71 | var type = this.window.get_window_type(); 72 | return !this.is_minimized() && !this.is_fullscreen() 73 | && !this.is_attached_dialog() 74 | && ! this.floating && AllowedMetaTypes.indexOf(type) >= 0; 75 | }, 76 | is_fullscreen: function() { 77 | return this.window.is_fullscreen(); 78 | }, 79 | is_maximized: function() { 80 | return this.window.get_maximized() > 0; 81 | }, 82 | is_minimized: function() { 83 | return this.window.minimized; 84 | }, 85 | is_attached_dialog: function() { 86 | return this.window.is_attached_dialog(); 87 | }, 88 | unmaximize_if_not_floating: function() { 89 | if (!this.floating) { 90 | this.set_maximize(false, false); 91 | } 92 | }, 93 | set_maximize: function(maximize = true, change_floating = true) { 94 | if (maximize) { 95 | this.window.maximize(Meta.MaximizeFlags.BOTH); 96 | } else { 97 | this.window.unmaximize(Meta.MaximizeFlags.BOTH); 98 | } 99 | if (change_floating) { 100 | this.floating = this.is_maximized(); 101 | } 102 | }, 103 | 104 | rect: function() { 105 | var r = this.window.get_frame_rect(); 106 | return r; 107 | }, 108 | pos: function() { 109 | var r = this.window.get_frame_rect(); 110 | return { x: r.x, y: r.y}; 111 | }, 112 | size: function() { 113 | var r = this.window.get_frame_rect(); 114 | return { w: r.width, h: r.height}; 115 | }, 116 | center: function() { 117 | var r = this.window.get_frame_rect(); 118 | return { x: r.x + r.width / 2, y: r.y + r.height / 2}; 119 | }, 120 | _requestRelayout: function() { 121 | if (this.gslayout) { 122 | this.gslayout.relayout(); 123 | } 124 | }, 125 | activate: function(center_pointer = true) { 126 | this.window.activate(global.get_current_time()); 127 | if (center_pointer) { 128 | this.center_pointer(); 129 | } 130 | }, 131 | center_pointer: function() { 132 | if (!prefs.POINTER_FOLLOWS_FOCUS.get()) { return; } 133 | const display = Gdk.Display.get_default(); 134 | const deviceManager = display.get_device_manager(); 135 | const pointer = deviceManager.get_client_pointer(); 136 | const [screen, pointerX, pointerY] = pointer.get_position(); 137 | const p = this.center(); 138 | pointer.warp(screen, p.x, p.y); 139 | } 140 | }); 141 | -------------------------------------------------------------------------------- /icons/status/gnomesome-window-tile-floating-symbolic.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | image/svg+xml 9 | 10 | Gnome Symbolic Icon Theme 11 | 12 | 13 | 14 | 15 | 16 | 17 | Gnome Symbolic Icon Theme 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /icons/status/gnomesome-window-tile-full-symbolic.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 18 | 20 | 21 | 23 | image/svg+xml 24 | 26 | Gnome Symbolic Icon Theme 27 | 28 | 29 | 30 | 62 | 71 | 72 | Gnome Symbolic Icon Theme 74 | 76 | 82 | 88 | 93 | 102 | 103 | 108 | 113 | 118 | 123 | 128 | 134 | 140 | 141 | -------------------------------------------------------------------------------- /icons/status/gnomesome-window-tile-horizontal-symbolic.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | image/svg+xml 9 | 10 | Gnome Symbolic Icon Theme 11 | 12 | 13 | 14 | 15 | 16 | 17 | Gnome Symbolic Icon Theme 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /icons/status/gnomesome-window-tile-vertical-symbolic.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | image/svg+xml 9 | 10 | Gnome Symbolic Icon Theme 11 | 12 | 13 | 14 | 15 | 16 | 17 | Gnome Symbolic Icon Theme 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /layout.js: -------------------------------------------------------------------------------- 1 | const Lang = imports.lang; 2 | const GObject = imports.gi.GObject; 3 | const ExtensionUtils = imports.misc.extensionUtils; 4 | const Me = ExtensionUtils.getCurrentExtension(); 5 | const SplitLayout = Me.imports.splitlayout; 6 | const FloatLayout = Me.imports.floatlayout; 7 | const MaximizeLayout = Me.imports.maximizelayout; 8 | const Utils = Me.imports.utils; 9 | const logging = Me.imports.logging; 10 | const logger = logging.getLogger('Gnomesome.Layout'); 11 | 12 | const Gettext = imports.gettext.domain('gnomesome'); 13 | const _ = Gettext.gettext; 14 | 15 | var Modes = { 16 | FLOATING: 0, 17 | VBOXLAYOUT: 1, 18 | HBOXLAYOUT: 2, 19 | MAXIMIZED: 3, 20 | properties: { 21 | 0: { 22 | value: 0, name: "floating", display: _("Floating"), 23 | enterLayout: FloatLayout.enterFloatingLayout, 24 | exitLayout: FloatLayout.exitFloatingLayout, 25 | layout: FloatLayout.updateFloatingLayout, 26 | icon: "icons/status/gnomesome-window-tile-floating-symbolic.svg", 27 | }, 28 | 1: { 29 | value: 1, name: "vertical", display: _("Vertical"), 30 | enterLayout: SplitLayout.enterVBoxLayout, 31 | exitLayout: SplitLayout.exitVBoxLayout, 32 | layout: SplitLayout.applyVBoxLayout, 33 | icon: "icons/status/gnomesome-window-tile-vertical-symbolic.svg", 34 | }, 35 | 2: { 36 | value: 2, name: "horizontal", display: _("Horizontal"), 37 | enterLayout: SplitLayout.enterHBoxLayout, 38 | exitLayout: SplitLayout.exitHBoxLayout, 39 | layout: SplitLayout.applyHBoxLayout, 40 | icon: "icons/status/gnomesome-window-tile-horizontal-symbolic.svg", 41 | }, 42 | 3: { 43 | value: 3, name: "maximized", display: _("Maximized"), 44 | enterLayout: MaximizeLayout.enterMaximizeLayout, 45 | exitLayout: MaximizeLayout.exitMaximizeLayout, 46 | layout: MaximizeLayout.updateMaximizeLayout, 47 | icon: "icons/status/gnomesome-window-tile-full-symbolic.svg", 48 | }, 49 | }, 50 | byName: function(name) { 51 | for (var key in Modes.properties) { 52 | if (Modes.properties.hasOwnProperty(key)) { 53 | if (name == Modes.properties[key].name) { 54 | return key; 55 | } 56 | } 57 | } 58 | logger.warn("Error layout with label " + name + " not found"); 59 | return -1; 60 | }, 61 | }; 62 | 63 | const NumModes = Object.keys(Modes.properties).length; 64 | 65 | var Layout = new GObject.Class({ 66 | Name: 'Gnomesome.Layout', 67 | GTypeName: 'GnomesomeLayout', 68 | Properties: { 69 | 'mode': GObject.ParamSpec.int('mode', 'ModeProperty', 'Mode property', GObject.ParamFlags.READWRITE, 0, NumModes - 1, Modes.FLOATING), 70 | 'split-pos': GObject.ParamSpec.float('split-pos', "Split position", "Position of the split", GObject.ParamFlags.READWRITE, 0.2, 0.8, 0.5), 71 | 'n-master': GObject.ParamSpec.int('n-master', "Number of master", "Number of master windows", GObject.ParamFlags.READWRITE, 0, 5, 1), 72 | }, 73 | Signals: { 74 | }, 75 | 76 | _init: function(prefs) { 77 | this.parent(); 78 | this.gswindows = []; 79 | this._mode = Modes.FLOATING; 80 | this._split_pos = 0.5; 81 | this._n_master = 1; 82 | 83 | // this.connect('notify::mode', Lang.bind(this, function () {this.relayout();})); // handled in layout_changed(from, to) 84 | this.connect('notify::split-pos', Lang.bind(this, function () {this.relayout();})); 85 | this.connect('notify::n-master', Lang.bind(this, function () {this.relayout();})); 86 | 87 | // monitor prefs 88 | let update = Lang.bind(this, function() { 89 | let name = prefs.DEFAULT_LAYOUT.get(); 90 | logger.info("Updating default layout to " + name); 91 | let new_layout = Modes.byName(name); 92 | if (this._initial) { 93 | this.mode = new_layout; 94 | // keep this as initial! 95 | this._initial = true; 96 | } 97 | }); 98 | Utils.connect_and_track(this, prefs.DEFAULT_LAYOUT.gsettings, "changed::" + prefs.DEFAULT_LAYOUT.key, update); 99 | 100 | // setup to initial layout 101 | this._initial = true; 102 | update(); 103 | }, 104 | destroy: function() { 105 | logger.info("Cleaning up layout."); 106 | for (let gswindow of Object.values(this.gswindows)) { 107 | gswindow.destroy(); 108 | } 109 | this.gswindows = []; 110 | Utils.disconnect_tracked_signals(this); 111 | }, 112 | get mode() {return this._mode;}, 113 | set mode(mode) { if (this._mode != mode) { this.layout_changed(this._mode, mode); this._mode = mode; this.notify("mode"); } this._initial = false;}, 114 | //set mode(mode) { if (this._mode != mode) { this._mode = mode; this.notify("mode"); } }, 115 | 116 | get split_pos() {return this._split_pos;}, 117 | set split_pos(pos) {pos = Math.max(Math.min(pos, 0.8), 0.2); if (this._split_pos != pos) {this._split_pos = pos; this.notify("split-pos");}}, 118 | resize_master_area: function(scale) {this.split_pos += scale;}, 119 | 120 | get n_master() {return this._n_master;}, 121 | set n_master(n) {n = Math.max(Math.min(n, 5), 0); if (this._n_master != n) {this._n_master = n; this.notify("n-master"); } }, 122 | increment_n_master: function(n) {this.n_master += n;}, 123 | 124 | set_master: function(window) { 125 | const gswindow = this.getGSWindowFromWindow(window); 126 | if (!gswindow) {return;} 127 | // remove window but dont relayout 128 | this.removeGSWindow(gswindow, false); 129 | // add window at front 130 | this.gswindows.unshift(gswindow); 131 | // set n_master to 1 and force relayout if there is no change 132 | if (this.n_master != 1) {this.n_master = 1;} 133 | else {this.relayout();} 134 | }, 135 | 136 | layout_name: function() { 137 | return Modes.properties[this.mode].name; 138 | }, 139 | layout: function() { 140 | return Modes.properties[this.mode].layout; 141 | }, 142 | properties: function() { 143 | return Modes.properties[this.mode]; 144 | }, 145 | relayout: function() { 146 | logger.debug("Current layout " + this.layout_name()); 147 | const gswindows = this.allLayoutGSWindows(); 148 | this.layout()(gswindows, this.split_pos, this.n_master); 149 | }, 150 | layout_changed: function(old_mode, new_mode) { 151 | logger.debug("Layout changed from " + Modes.properties[old_mode].name 152 | + " to " + Modes.properties[new_mode].name); 153 | const gs_wnds = this.allLayoutGSWindows(); 154 | Modes.properties[old_mode].exitLayout(gs_wnds, this.split_pos, this.n_master); 155 | Modes.properties[new_mode].enterLayout(gs_wnds, this.split_pos, this.n_master); 156 | Modes.properties[new_mode].layout(gs_wnds, this.split_pos, this.n_master); 157 | }, 158 | num_layouts: function() { 159 | return NumModes; 160 | }, 161 | roll_layout: function(offset) { 162 | const next_layout_id = (this.mode + offset + this.num_layouts()) % this.num_layouts(); 163 | logger.debug("Rolling layout " + Modes.properties[next_layout_id].name); 164 | this.mode = next_layout_id; 165 | }, 166 | addGSWindow: function(gswindow, relayout=true) { 167 | if (!gswindow) {return;} 168 | gswindow.gslayout = this; 169 | const index = this.gswindows.indexOf(gswindow); 170 | if (index < 0) { 171 | // only if not in list 172 | this.gswindows.push(gswindow); 173 | if (relayout) { 174 | this.relayout(); 175 | } 176 | } 177 | }, 178 | removeGSWindow: function(gswindow, relayout=true) { 179 | if (!gswindow) {return;} 180 | gswindow.gslayout = null; 181 | const index = this.gswindows.indexOf(gswindow); 182 | if (index >= 0) { 183 | // only if in list 184 | this.gswindows.splice(index, 1); 185 | if (relayout) { 186 | this.relayout(); 187 | } 188 | } 189 | }, 190 | allWindows: function() { 191 | const windows = []; 192 | for (var idx = 0; idx < this.gswindows.length; ++idx) { 193 | windows.push(this.gswindows[idx].window); 194 | } 195 | return windows; 196 | }, 197 | allLayoutGSWindows: function() { 198 | const gswindows = []; 199 | for (var idx = 0; idx < this.gswindows.length; ++idx) { 200 | if (this.gswindows[idx].layoutAllowed()) { 201 | gswindows.push(this.gswindows[idx]); 202 | } 203 | } 204 | return gswindows; 205 | }, 206 | allLayoutWindows: function() { 207 | var windows = []; 208 | for (var idx = 0; idx < this.gswindows.length; ++idx) { 209 | if (this.gswindows[idx].layoutAllowed()) { 210 | windows.push(this.gswindows[idx].window); 211 | } 212 | } 213 | return windows; 214 | }, 215 | sortedWindowsByStacking: function() { 216 | var windows = this.allWindows(); 217 | return global.display.sort_windows_by_stacking(windows); 218 | }, 219 | topmostWindow: function() { 220 | var sortedWindows = this.sortedWindowsByStacking(); 221 | return this.getGSWindowFromWindow(sortedWindows[sortedWindows.length - 1]); 222 | }, 223 | getGSWindowFromWindow: function(window) { 224 | if (window && window.gswindow && window.gswindow.gslayout == this) { 225 | return window.gswindow; 226 | } 227 | for (var idx = 0; idx < this.gswindows.length; ++idx) { 228 | if (this.gswindows[idx].window === window) { 229 | return this.gswindows[idx]; 230 | } 231 | } 232 | return null; 233 | }, 234 | numberOfWindows: function() { 235 | return this.gswindows.length; 236 | }, 237 | indexOfWindow: function(window) { 238 | for (var idx = 0; idx < this.gswindows.length; ++idx) { 239 | if (this.gswindows[idx].window === window) { 240 | return idx; 241 | } 242 | } 243 | return -1; 244 | }, 245 | gsWindowByIndex: function(index) { 246 | return this.gswindows[index]; 247 | }, 248 | swap_with_window: function(window, offset) { 249 | const gswindow = this.getGSWindowFromWindow(window); 250 | // do nothing if the current window is not in a true layout 251 | if (!gswindow || !gswindow.layoutAllowed()) {return;} 252 | 253 | let idx = 0; 254 | for (; idx < this.gswindows.length; ++idx) { 255 | if (this.gswindows[idx] === gswindow) { 256 | break; 257 | } 258 | } 259 | let oidx; 260 | for (oidx = (idx + offset + this.gswindows.length) % this.gswindows.length; 261 | oidx !== idx; 262 | oidx = (oidx + offset + this.gswindows.length) % this.gswindows.length) { 263 | // find next window in layout 264 | if (this.gswindows[oidx].layoutAllowed()) { 265 | break; 266 | } 267 | } 268 | if (idx === oidx) { 269 | // same window 270 | return; 271 | } 272 | this.gswindows[idx] = this.gswindows[oidx]; 273 | this.gswindows[oidx] = gswindow; 274 | this.relayout(); 275 | gswindow.center_pointer(); 276 | } 277 | }); 278 | -------------------------------------------------------------------------------- /logging.js: -------------------------------------------------------------------------------- 1 | // used elsewhere in the extension to enable additional safety 2 | // checks that "should never happen". Set to `true` when GNOMESOME_DEBUG=true|1|all 3 | let PARANOID = false; 4 | 5 | const ExtensionUtils = imports.misc.extensionUtils; 6 | const Me = ExtensionUtils.getCurrentExtension(); 7 | const log4js_root = Me.imports.thirdparty.log4js; 8 | const log4js = log4js_root.log4javascript.log4javascript; 9 | const log4js_fa = log4js_root.log4javascript_file_appender; 10 | const log4js_gjsa = log4js_root.log4javascript_gjs_appender; 11 | 12 | const root_logger = log4js.getLogger('gnomesome'); 13 | function getLogger(name) { return log4js.getLogger('gnomesome.' + name); } 14 | 15 | function init(main) { 16 | const GLib = imports.gi.GLib; 17 | const GjsAppender = log4js_gjsa.init(log4js); 18 | const appender = new GjsAppender(); 19 | appender.setLayout(new log4js.PatternLayout("%-5p: %m")); 20 | let gnomesome_debug = GLib.getenv("GNOMESOME_DEBUG"); 21 | 22 | let root_level = log4js.Level.INFO; 23 | root_logger.addAppender(appender); 24 | root_logger.setLevel(root_level); 25 | 26 | if (!gnomesome_debug) { 27 | return; 28 | } 29 | const FileAppender = log4js_fa.init(log4js); 30 | if (main === true) { 31 | // only the main process should write gnomesome.log 32 | // (prefs.js is loaded in a separate process, and we don't 33 | // want that to overwrite the real logs) 34 | var fileAppender = new FileAppender(GLib.getenv('GNOMESOME_LOG') || "/tmp/gnomesome.log"); 35 | fileAppender.setLayout(new log4js.PatternLayout("%d{HH:mm:ss,SSS} %-5p [%c]: %m")); 36 | root_logger.addAppender(fileAppender); 37 | } 38 | 39 | if (gnomesome_debug === "true" || gnomesome_debug === "all" || gnomesome_debug === "1") { 40 | root_level = log4js.Level.DEBUG; 41 | PARANOID = true; 42 | root_logger.info("set log level DEBUG for gnomesome.*"); 43 | 44 | const NotificationAppender = function NotificationAppender() { }; 45 | NotificationAppender.prototype = new log4js.Appender(); 46 | NotificationAppender.prototype.layout = new log4js.PatternLayout("[gnomesome] %c: %m"); 47 | NotificationAppender.prototype.threshold = log4js.Level.ERROR; 48 | NotificationAppender.prototype.append = function(loggingEvent) { 49 | const formattedMessage = FileAppender.getFormattedMessage(this, loggingEvent); 50 | imports.ui.main.notify(formattedMessage); 51 | }; 52 | 53 | const notificationAppender = new NotificationAppender(); 54 | root_logger.addAppender(notificationAppender); 55 | 56 | } else { 57 | const debug_topics = gnomesome_debug.split(","); 58 | debug_topics.map(function(topic) { 59 | const log_name = "gnomesome." + topic; 60 | const logger = log4js.getLogger(log_name); 61 | logger.setLevel(log4js.Level.DEBUG); 62 | root_logger.info("set log level DEBUG for " + log_name); 63 | }); 64 | } 65 | 66 | } 67 | -------------------------------------------------------------------------------- /manager.js: -------------------------------------------------------------------------------- 1 | const Lang = imports.lang; 2 | const Main = imports.ui.main; 3 | const Shell = imports.gi.Shell; 4 | const Util = imports.misc.util; 5 | const Meta = imports.gi.Meta; 6 | const ExtensionUtils = imports.misc.extensionUtils; 7 | const Me = ExtensionUtils.getCurrentExtension(); 8 | const MenuButton = Me.imports.menubutton; 9 | const Convenience = Me.imports.convenience; 10 | const Utils = Me.imports.utils; 11 | const Layout = Me.imports.layout; 12 | const GSWindow = Me.imports.gswindow; 13 | const GnomesomeSettings = Me.imports.gnomesome_settings; 14 | var LaunchTerminalCmd = []; 15 | const MainLoop = imports.mainloop; 16 | const logging = Me.imports.logging; 17 | const logger = logging.getLogger('Gnomesome.Manager'); 18 | 19 | 20 | var current_window = function() { 21 | return global.display['focus_window']; 22 | }; 23 | 24 | var Manager = new Lang.Class({ 25 | Name: 'Gnomesome.Manager', 26 | 27 | _init: function() { 28 | const screen = Utils.DisplayWrapper.getScreen(); 29 | const display = Utils.DisplayWrapper.getDisplay(); 30 | const workspace_manager = Utils.DisplayWrapper.getWorkspaceManager(); 31 | 32 | this.layouts = []; 33 | 34 | 35 | let extension = ExtensionUtils.getCurrentExtension(); 36 | let schema = extension.metadata['settings-keybindings']; 37 | this.gsettings = Convenience.getSettings(schema); 38 | this.prefs = GnomesomeSettings.prefs; 39 | this._bound_keybindings = {}; 40 | 41 | this.initKeyBindings(); 42 | 43 | logger.info("Number of workspaces is " + workspace_manager.get_n_workspaces()); 44 | for (var id = 0; id < workspace_manager.get_n_workspaces(); ++id) { 45 | this.prepare_workspace(id); 46 | } 47 | 48 | Utils.connect_and_track(this, workspace_manager, 'workspace-added', 49 | Lang.bind(this, function(screen, id) { 50 | logger.debug("Workspace added " + id); 51 | this.prepare_workspace(id); 52 | }) 53 | ); 54 | Utils.connect_and_track(this, workspace_manager, 'workspace-removed', 55 | Lang.bind(this, function(screen, id) { 56 | logger.debug("Workspace removed " + id); 57 | this.remove_workspace(id); 58 | }) 59 | ); 60 | 61 | Utils.connect_and_track(this, screen, 'window-entered-monitor', 62 | Lang.bind(this, function(screen, mid, window) { 63 | logger.debug("window-entered-monitor " + mid); 64 | var ws = window.get_workspace(); 65 | if (ws && window.gswindow) { 66 | var wid = ws.index(); 67 | var gslayout = this.layouts[wid][mid].addGSWindow(window.gswindow); 68 | } 69 | }) 70 | ); 71 | 72 | Utils.connect_and_track(this, screen, 'window-left-monitor', 73 | Lang.bind(this, function(screen, mid, window) { 74 | logger.debug("window-left-monitor " + mid); 75 | var ws = window.get_workspace(); 76 | if (ws && window.gswindow) { 77 | var wid = ws.index(); 78 | var gslayout = this.layouts[wid][mid].removeGSWindow(window.gswindow); 79 | } 80 | }) 81 | ); 82 | 83 | Utils.connect_and_track(this, screen, 'notify::focus-window', 84 | Lang.bind(this, function(display, window) { 85 | // update current display 86 | }) 87 | ); 88 | 89 | Utils.connect_and_track(this, display, 'window-created', 90 | Lang.bind(this, function(display, window, user_data) { 91 | const cl = this.current_layout(); 92 | if (cl) {cl.relayout();} 93 | }) 94 | ); 95 | 96 | Utils.connect_and_track(this, display, 'grab-op-end', 97 | Lang.bind(this, function(display, screen, window, grabop, user_data) { 98 | this.current_layout().relayout(); 99 | }) 100 | ); 101 | 102 | this.initSettingsMonitor(); 103 | this.initLaunchTerminalMonitor(); 104 | }, 105 | destroy: function() { 106 | this.releaseKeyBindings(); 107 | if (this.menuButton) { this.menuButton.destroy(); } 108 | Utils.disconnect_tracked_signals(this); 109 | while (this.layouts.length > 0) { 110 | this.remove_workspace(0); 111 | } 112 | //this.parent(); 113 | }, 114 | initKeyBindings: function() { 115 | this.handleKey("next-layout", Lang.bind(this, function() {this.current_layout().roll_layout(+1);})); 116 | this.handleKey("previous-layout", Lang.bind(this, function() {this.current_layout().roll_layout(-1);})); 117 | this.handleKey("layout-horizontal", Lang.bind(this, function() {this.current_layout().mode = Layout.Modes.HBOXLAYOUT;})); 118 | this.handleKey("layout-vertical", Lang.bind(this, function() {this.current_layout().mode = Layout.Modes.VBOXLAYOUT;})); 119 | this.handleKey("layout-floating", Lang.bind(this, function() {this.current_layout().mode = Layout.Modes.FLOATING;})); 120 | this.handleKey("layout-maximized", Lang.bind(this, function() {this.current_layout().mode = Layout.Modes.MAXIMIZED;})); 121 | 122 | this.handleKey("next-window", Lang.bind(this, this.next_window)); 123 | this.handleKey("previous-window", Lang.bind(this, this.previous_window)); 124 | this.handleKey("swap-with-next-window", Lang.bind(this, function() {this.current_layout().swap_with_window(this.current_window(), +1);})); 125 | this.handleKey("swap-with-previous-window", Lang.bind(this, function() {this.current_layout().swap_with_window(this.current_window(), -1);})); 126 | this.handleKey("next-monitor", Lang.bind(this, function() {this.roll_monitor(+1);})); 127 | this.handleKey("previous-monitor", Lang.bind(this, function() {this.roll_monitor(-1);})); 128 | this.handleKey("move-to-next-monitor-alt", Lang.bind(this, function() {this.roll_move_to_monitor(+1);})); 129 | this.handleKey("move-to-next-monitor", Lang.bind(this, function() {this.roll_move_to_monitor(+1);})); 130 | this.handleKey("move-to-previous-monitor", Lang.bind(this, function() {this.roll_move_to_monitor(-1);})); 131 | this.handleKey("increase-master-area", Lang.bind(this, function() {this.current_layout().resize_master_area(0.05);})); 132 | this.handleKey("decrease-master-area", Lang.bind(this, function() {this.current_layout().resize_master_area(-0.05);})); 133 | this.handleKey("increase-n-master", Lang.bind(this, function() {this.current_layout().increment_n_master(+1);})); 134 | this.handleKey("decrease-n-master", Lang.bind(this, function() {this.current_layout().increment_n_master(-1);})); 135 | this.handleKey("swap-window-with-master", Lang.bind(this, function() {this.current_layout().set_master(this.current_window()); })); 136 | this.handleKey("set-workspace-1", Lang.bind(this, function() {this.set_workspace(0);})); 137 | this.handleKey("set-workspace-2", Lang.bind(this, function() {this.set_workspace(1);})); 138 | this.handleKey("set-workspace-3", Lang.bind(this, function() {this.set_workspace(2);})); 139 | this.handleKey("set-workspace-4", Lang.bind(this, function() {this.set_workspace(3);})); 140 | this.handleKey("set-workspace-5", Lang.bind(this, function() {this.set_workspace(4);})); 141 | this.handleKey("set-workspace-6", Lang.bind(this, function() {this.set_workspace(5);})); 142 | this.handleKey("set-workspace-7", Lang.bind(this, function() {this.set_workspace(6);})); 143 | this.handleKey("set-workspace-8", Lang.bind(this, function() {this.set_workspace(7);})); 144 | this.handleKey("set-workspace-9", Lang.bind(this, function() {this.set_workspace(8);})); 145 | this.handleKey("set-workspace-10", Lang.bind(this, function() {this.set_workspace(9);})); 146 | 147 | this.handleKey("move-window-to-workspace-1", Lang.bind(this, function() {this.set_workspace(0, this.current_window());})); 148 | this.handleKey("move-window-to-workspace-2", Lang.bind(this, function() {this.set_workspace(1, this.current_window());})); 149 | this.handleKey("move-window-to-workspace-3", Lang.bind(this, function() {this.set_workspace(2, this.current_window());})); 150 | this.handleKey("move-window-to-workspace-4", Lang.bind(this, function() {this.set_workspace(3, this.current_window());})); 151 | this.handleKey("move-window-to-workspace-5", Lang.bind(this, function() {this.set_workspace(4, this.current_window());})); 152 | this.handleKey("move-window-to-workspace-6", Lang.bind(this, function() {this.set_workspace(5, this.current_window());})); 153 | this.handleKey("move-window-to-workspace-7", Lang.bind(this, function() {this.set_workspace(6, this.current_window());})); 154 | this.handleKey("move-window-to-workspace-8", Lang.bind(this, function() {this.set_workspace(7, this.current_window());})); 155 | this.handleKey("move-window-to-workspace-9", Lang.bind(this, function() {this.set_workspace(8, this.current_window());})); 156 | this.handleKey("move-window-to-workspace-10", Lang.bind(this, function() {this.set_workspace(9, this.current_window());})); 157 | 158 | this.handleKey("window-toggle-maximize", Lang.bind(this, this.toggle_maximize)); 159 | this.handleKey("window-toggle-fullscreen", Lang.bind(this, this.toggle_fullscreen)); 160 | this.handleKey("window-toggle-floating", Lang.bind(this, this.toggle_floating)); 161 | this.handleKey("launch-terminal", function() {Util.spawn(LaunchTerminalCmd);}); 162 | 163 | }, 164 | releaseKeyBindings: function() { 165 | var display = global.display; 166 | for (var k in this._bound_keybindings) { 167 | if(!this._bound_keybindings.hasOwnProperty(k)) continue; 168 | var desc = "unbinding key " + k; 169 | this._do(function() { 170 | logger.debug(desc); 171 | if (Main.wm.removeKeybinding) { 172 | Main.wm.removeKeybinding(k); 173 | } else { 174 | display.remove_keybinding(k); 175 | } 176 | }, desc); 177 | } 178 | }, 179 | 180 | initSettingsMonitor: function() { 181 | let initial = true; 182 | 183 | // indicator 184 | let indPref = this.prefs.SHOW_INDICATOR; 185 | let indUpdate = Lang.bind(this, function() { 186 | let indPref = this.prefs.SHOW_INDICATOR; 187 | var val = indPref.get(); 188 | logger.debug("Setting show-indicator to " + val); 189 | if (val) { 190 | // create indicator icon 191 | if (!this.menuButton) { 192 | this.menuButton = new MenuButton.MenuButton(this); 193 | Main.panel.addToStatusArea('gnomesome-manager', this.menuButton); 194 | } 195 | } else { 196 | if (this.menuButton) { 197 | this.menuButton.destroy(); 198 | this.menuButton = null; 199 | } 200 | } 201 | }); 202 | Utils.connect_and_track(this, indPref.gsettings, 'changed::' + indPref.key, indUpdate); 203 | 204 | Utils.connect_and_track(this, this.prefs.OUTER_GAPS.gsettings, 'changed::' + this.prefs.OUTER_GAPS.key, () => this.relayout_all()); 205 | Utils.connect_and_track(this, this.prefs.INNER_GAPS.gsettings, 'changed::' + this.prefs.INNER_GAPS.key, () => this.relayout_all()); 206 | 207 | 208 | indUpdate(); 209 | }, 210 | 211 | initLaunchTerminalMonitor: function() { 212 | let termPref = this.prefs.LAUNCH_TERMINAL; 213 | let termUpdate = Lang.bind(this, function() { 214 | let termPref = this.prefs.LAUNCH_TERMINAL; 215 | var val = termPref.get(); 216 | var array = val.split(' '); // split string into an array of words 217 | LaunchTerminalCmd = array.filter(function(x) { 218 | return (x !== (undefined || null || '')); // remove empty elements 219 | }); 220 | logger.debug("Setting launch new terminal command to " + val); 221 | }); 222 | Utils.connect_and_track(this, termPref.gsettings, 'changed::' + termPref.key, termUpdate); 223 | termUpdate(); 224 | }, 225 | 226 | // Utility method that binds a callback to a named keypress-action. 227 | handleKey: function (name, func) { 228 | this._bound_keybindings[name] = true; 229 | var flags = Meta.KeyBindingFlags.NONE; 230 | 231 | // API for 3.8+ only 232 | var ModeType = Shell.hasOwnProperty('ActionMode') ? Shell.ActionMode : Shell.KeyBindingMode; 233 | var added = Main.wm.addKeybinding( 234 | name, 235 | this.gsettings, 236 | flags, 237 | ModeType.NORMAL | ModeType.OVERVIEW, 238 | Lang.bind(this, function() {this._do(func, "handler for binding " + name);})); 239 | if(!added) { 240 | logger.error("Error: failed to add keybinding handler for: " + name); 241 | } 242 | }, 243 | 244 | // Safely execute a callback by catching any 245 | // exceptions and logging the traceback and a caller-provided 246 | // description of the action. 247 | _do: function(action, desc, fail) { 248 | try { 249 | logger.debug("start action: " + desc); 250 | action(); 251 | return null; 252 | } catch (e) { 253 | logger.error("Uncaught error in " + desc + ": " + e + "\n" + e.stack); 254 | if(fail) throw e; 255 | return e; 256 | } 257 | }, 258 | 259 | set_workspace: function (new_index, window) { 260 | if(new_index < 0 || new_index >= Utils.DisplayWrapper.getWorkspaceManager().get_n_workspaces()) { 261 | logger.warn("No such workspace; ignoring"); 262 | return; 263 | } 264 | var next_workspace = Utils.DisplayWrapper.getWorkspaceManager().get_workspace_by_index(new_index); 265 | if(window !== undefined) { 266 | window.change_workspace(next_workspace); 267 | next_workspace.activate_with_focus(window, global.get_current_time()) 268 | } else { 269 | next_workspace.activate(global.get_current_time()); 270 | } 271 | }, 272 | prepare_workspace: function (index) { 273 | logger.debug("Preparing workspace with index " + index) 274 | const workspace = Utils.DisplayWrapper.getWorkspaceManager().get_workspace_by_index(index); 275 | const layouts_for_monitors = []; 276 | for (let id = 0; id < Utils.DisplayWrapper.getScreen().get_n_monitors(); ++id) { 277 | logger.debug("Preparing monitor with index " + id + " for workspace with index " + index); 278 | let l = new Layout.Layout(this.prefs); 279 | l.connect("notify::mode", Lang.bind(this, function(l) { 280 | if (this.menuButton) { this.menuButton.setLayout(l.properties()); } 281 | const cw = this.current_window(); 282 | if (cw.gswindow) { cw.gswindow.center_pointer(); } 283 | })); 284 | layouts_for_monitors.push(l); 285 | } 286 | this.layouts.splice(index, 0, layouts_for_monitors); 287 | 288 | // add all existing windows 289 | const windows = workspace.list_windows(); 290 | for (let id = 0; id < windows.length; ++id) { 291 | this.window_added(workspace, windows[id]); 292 | } 293 | 294 | Utils.connect_and_track(this, workspace, "window-added", Lang.bind(this, this.window_added)); 295 | Utils.connect_and_track(this, workspace, "window-removed", Lang.bind(this, this.window_removed)); 296 | }, 297 | remove_workspace: function (index) { 298 | logger.debug("Removing workspace with index " + index) 299 | var layouts_for_monitors = this.layouts[index]; 300 | for (var lidx = 0; lidx < layouts_for_monitors.length; ++lidx) { 301 | layouts_for_monitors[lidx].destroy(); 302 | } 303 | this.layouts.splice(index, 1); 304 | }, 305 | update_workspaces: function () { 306 | 307 | }, 308 | window_added: function(workspace, window) { 309 | logger.debug("Window added " + workspace.index() + " " + window.get_monitor()); 310 | var gslayout = this.layouts[workspace.index()][window.get_monitor()]; 311 | var gswindow = window.gswindow; 312 | if (window.gswindow) { 313 | gslayout.addGSWindow(window.gswindow); 314 | logger.debug("Window already registered as gswindow"); 315 | } else { 316 | gswindow = new GSWindow.GSWindow(window, gslayout); 317 | window.gswindow = gswindow; 318 | gslayout.addGSWindow(gswindow); 319 | } 320 | // attempt to relayout, but we need to wait until the window is ready 321 | logger.debug("Waiting until window is ready"); 322 | var attempt = function(remainingAttempts) { 323 | if (gswindow.is_ready()) { 324 | MainLoop.timeout_add(50, function() { 325 | gswindow.reset(); 326 | gslayout.relayout(); 327 | if (gswindow.window === current_window()) { 328 | gswindow.center_pointer(); 329 | } 330 | }); 331 | } else { 332 | if (remainingAttempts === 0) { 333 | logger.warn("To many attempts to relayout"); 334 | } else { 335 | MainLoop.timeout_add(10, function() { 336 | attempt(remainingAttempts - 1); 337 | }); 338 | } 339 | } 340 | }; 341 | attempt(50); 342 | }, 343 | window_removed: function(workspace, window) { 344 | logger.debug("Window removed " + workspace.index() + " " + window.get_monitor()); 345 | const gslayout = this.layouts[workspace.index()][window.get_monitor()]; 346 | if (window.gswindow) { 347 | gslayout.removeGSWindow(window.gswindow); 348 | } else { 349 | // logger.warn("Error: Window without gswindow removed"); 350 | const gswindow = gslayout.getGSWindowFromWindow(window); 351 | if (gswindow) { 352 | gswindow.destroy(); 353 | gslayout.removeGSWindow(gswindow); 354 | } 355 | } 356 | }, 357 | current_window: function() { 358 | return current_window(); 359 | }, 360 | current_monitor_index: function() { 361 | var cw = global.display['focus_window']; 362 | if (cw) {return cw.get_monitor();} 363 | else {return Utils.DisplayWrapper.getScreen().get_current_monitor();} 364 | }, 365 | current_workspace_index: function() { 366 | var cw = global.display['focus_window']; 367 | if (cw) {return cw.get_workspace().index();} 368 | else {return Utils.DisplayWrapper.getWorkspaceManager().get_active_workspace_index();} 369 | }, 370 | current_layout: function() { 371 | var cm = this.current_monitor_index(); 372 | var cw = this.current_workspace_index(); 373 | logger.debug("Current window/monitor: " + cw + "/" + cm); 374 | logger.debug("Current layout size: " + this.layouts.length); 375 | if (cw !== null && cm !== null && cw >= 0 && cm >= 0 && this.layouts.length > 0) { 376 | return this.layouts[cw][cm]; 377 | } 378 | logger.debug("Current monitor or current window are not set cw/cm: " + cw + "/" + cm); 379 | return null; 380 | }, 381 | relayout_all: function() { 382 | logger.debug("Relayouting all windows and workspaces"); 383 | for (let i = 0; i < this.layouts.length; i++) { 384 | for (let j = 0; j < this.layouts[i].length; j++) { 385 | this.layouts[i][j].relayout(); 386 | } 387 | } 388 | }, 389 | roll_window: function(offset) { 390 | const cw = global.display['focus_window']; 391 | const monitor = this.current_monitor_index(); 392 | const workspace = this.current_workspace_index(); 393 | const gslayout = this.layouts[workspace][monitor]; 394 | const n = gslayout.numberOfWindows(); 395 | if (!cw || n === 0) { 396 | // no windows on that screen and workspace 397 | return; 398 | } 399 | 400 | let index = gslayout.indexOfWindow(cw); 401 | if (index < 0) { 402 | logger.warn.log("Warning: current window is not in layout!"); 403 | index = 0; 404 | } else { 405 | index += offset; 406 | } 407 | 408 | for (let attempt = 0; attempt < n; attempt++) { 409 | index = (index + n) % n; 410 | const newGSWindow = gslayout.gsWindowByIndex(index); 411 | if (newGSWindow && !newGSWindow.is_minimized()) { 412 | newGSWindow.activate(); 413 | break; 414 | } 415 | index += 1; 416 | } 417 | 418 | }, 419 | next_window: function() { 420 | this.roll_window(+1); 421 | }, 422 | previous_window: function() { 423 | this.roll_window(-1); 424 | }, 425 | roll_monitor: function(offset) { 426 | var monitor = this.current_monitor_index(); 427 | var workspace = this.current_workspace_index(); 428 | var n_monitors = Utils.DisplayWrapper.getScreen().get_n_monitors(); 429 | var next_monitor = (monitor + offset + n_monitors) % n_monitors; 430 | var next_gslayout = this.layouts[workspace][next_monitor]; 431 | var newGSWindow = next_gslayout.topmostWindow(); 432 | if (newGSWindow) { 433 | // check if there is a window on that workspace 434 | newGSWindow.activate(); 435 | } 436 | 437 | }, 438 | roll_move_to_monitor: function(offset) { 439 | var midx = this.current_monitor_index(); 440 | var cw = global.display['focus_window']; 441 | var n_monitors = Utils.DisplayWrapper.getScreen().get_n_monitors(); 442 | var next_midx = (midx + offset + n_monitors) % n_monitors; 443 | cw.move_to_monitor(next_midx); 444 | if (cw.gswindow) { cw.gswindow.center_pointer(); } 445 | }, 446 | set_current_layout_mode: function(mode) { 447 | this.current_layout().mode = mode; 448 | }, 449 | toggle_maximize: function(maximize) { 450 | var cw = this.current_window(); 451 | if (!cw) {return;} 452 | var gswindow = cw.gswindow; 453 | if (maximize === true) { 454 | gswindow.set_maximize(true); 455 | } else if (maximize === false) { 456 | gswindow.set_maximize(false); 457 | } else if (cw.get_maximized()) { 458 | gswindow.set_maximize(false); 459 | } else { 460 | gswindow.set_maximize(true); 461 | } 462 | this.current_layout().relayout(); 463 | }, 464 | toggle_fullscreen: function(fullscreen) { 465 | var cw = this.current_window(); 466 | if (!cw) {return;} 467 | if (fullscreen === true) { 468 | cw.make_fullscreen(); 469 | } else if (fullscreen === false) { 470 | cw.unmake_fullscreen(); 471 | } else if (cw.is_fullscreen()) { 472 | cw.unmake_fullscreen(); 473 | } else { 474 | cw.make_fullscreen(); 475 | } 476 | this.current_layout().relayout(); 477 | }, 478 | toggle_floating: function() { 479 | var gw = this.current_window().gswindow; 480 | gw.floating = !gw.floating; 481 | this.current_layout().relayout(); 482 | }, 483 | }); 484 | -------------------------------------------------------------------------------- /maximizelayout.js: -------------------------------------------------------------------------------- 1 | function enterMaximizeLayout(gswindows, split_pos, n_master) { 2 | } 3 | 4 | function updateMaximizeLayout(gswindows, split_pos, n_master) { 5 | for (var idx = 0; idx < gswindows.length; ++idx) { 6 | // we do not want to change the floating status of the window 7 | gswindows[idx].set_maximize(true, false); 8 | } 9 | } 10 | 11 | function exitMaximizeLayout(gswindows, split_pos, n_master) { 12 | } 13 | -------------------------------------------------------------------------------- /menubutton.js: -------------------------------------------------------------------------------- 1 | const St = imports.gi.St; 2 | const Lang = imports.lang; 3 | const Clutter = imports.gi.Clutter; 4 | const ExtensionUtils = imports.misc.extensionUtils; 5 | const Me = ExtensionUtils.getCurrentExtension(); 6 | const Utils = Me.imports.utils; 7 | const LayoutModes = Me.imports.layout.Modes; 8 | const Gio = imports.gi.Gio; 9 | 10 | const PanelMenu = imports.ui.panelMenu; 11 | const PopupMenu = imports.ui.popupMenu; 12 | 13 | const rootPath = Me.path; 14 | const Config = imports.misc.config; 15 | 16 | const POST_3_36 = parseFloat(Config.PACKAGE_VERSION) >= 3.36; 17 | 18 | 19 | var MenuButton = new Lang.Class({ 20 | Name: 'Gnomesome.MenuButton', 21 | Extends: PanelMenu.Button, 22 | 23 | _init: function(manager){ 24 | this._manager = manager; 25 | this.parent(0.0, _("Gnomesome MenuButton"), false); 26 | 27 | this._currentWorkspace = Utils.DisplayWrapper.getWorkspaceManager().get_active_workspace().index(); 28 | this.statusLabel = new St.Label({ y_align: Clutter.ActorAlign.CENTER, 29 | text: "0" }); 30 | 31 | // add the icon 32 | this.show(); 33 | this._iconBox = new St.BoxLayout(); 34 | this._iconIndicator = new St.Icon({ 35 | style_class: 'system-status-icon', 36 | icon_name: 'gnomesome-window-tile-floating-symbolic.svg', 37 | }); 38 | this._iconBox.add(this._iconIndicator); 39 | this.add_actor(this._iconBox); 40 | this.add_style_class_name('panel-status-button'); 41 | 42 | // initialize menu 43 | const addLayout = (layout) => { 44 | const item = new PopupMenu.PopupImageMenuItem(layout.display, Gio.icon_new_for_string(rootPath + '/' + layout.icon)); 45 | this.menu.addMenuItem(item); 46 | item.connect('activate', () => { 47 | this._manager.set_current_layout_mode(layout.value); 48 | }); 49 | }; 50 | for (let v of Object.values(LayoutModes.properties)) { 51 | addLayout(v); 52 | } 53 | 54 | // Separator 55 | this.menu.addMenuItem(new PopupMenu.PopupSeparatorMenuItem()); 56 | 57 | // Settings 58 | const item = new PopupMenu.PopupImageMenuItem("Gnomesome Settings", 'preferences-other') 59 | item.connect('activate', () => { 60 | const uuid = 'gnomesome@chwick.github.com'; 61 | if (POST_3_36) { 62 | imports.misc.util.spawn(['gnome-extensions', 'prefs', uuid]) 63 | } else { 64 | imports.misc.util.spawn(['gnome-shell-extension-prefs', uuid]) 65 | } 66 | }); 67 | this.menu.addMenuItem(item); 68 | 69 | //this.actor.add_actor(this.statusLabel); 70 | 71 | Utils.connect_and_track(this, Utils.DisplayWrapper.getWorkspaceManager(), 72 | 'workspace-switched', 73 | Lang.bind(this, this._updateIndicator)); 74 | Utils.connect_and_track(this, Utils.DisplayWrapper.getScreen(), 75 | 'window-entered-monitor', 76 | Lang.bind(this, this._updateIndicator)); 77 | Utils.connect_and_track(this, Utils.DisplayWrapper.getScreen(), 78 | 'window-left-monitor', 79 | Lang.bind(this, this._updateIndicator)); 80 | Utils.connect_and_track(this, Utils.DisplayWrapper.getScreen(), 81 | 'notify::focus-window', 82 | Lang.bind(this, this._updateIndicator)); 83 | Utils.connect_and_track(this, this, 84 | 'scroll-event', 85 | Lang.bind(this, this._scrollEvent)); 86 | 87 | this._updateIndicator(); 88 | }, 89 | destroy: function() { 90 | Utils.disconnect_tracked_signals(this); 91 | this.parent(); 92 | }, 93 | setLayout: function(layout) { 94 | this._iconIndicator.gicon = Gio.icon_new_for_string(rootPath + '/' + layout.icon); 95 | }, 96 | _updateIndicator: function() { 97 | this._currentWorkspace = Utils.DisplayWrapper.getWorkspaceManager().get_active_workspace().index(); 98 | var current_window = global.display['focus-window']; 99 | var monitor = 0; 100 | if (current_window) { 101 | monitor = current_window.get_monitor(); 102 | } 103 | this.statusLabel.set_text("W" + (this._currentWorkspace + 1).toString() 104 | + "M" + monitor); 105 | 106 | const clayout = this._manager.current_layout(); 107 | if (clayout) { 108 | this.setLayout(this._manager.current_layout().properties()); 109 | } 110 | }, 111 | _scrollEvent: function(actor, event) { 112 | const direction = event.get_scroll_direction(); 113 | const cl = this._manager.current_layout(); 114 | if (!cl) {return;} 115 | if (direction === Clutter.ScrollDirection.DOWN) { 116 | cl.roll_layout(+1); 117 | } else if (direction === Clutter.ScrollDirection.UP) { 118 | cl.roll_layout(-1); 119 | } 120 | }, 121 | }); 122 | -------------------------------------------------------------------------------- /metadata.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Gnomesome", 3 | "description": "Tiling window manager with awesome keybindings", 4 | "uuid": "gnomesome@chwick.github.com", 5 | "shell-version": ["3.36", "3.38"], 6 | "settings-keybindings": "org.gnome.shell.extensions.gnomesome.keybindings", 7 | "url": "https://github.com/chwick/gnomesome" 8 | } 9 | -------------------------------------------------------------------------------- /prefs.js: -------------------------------------------------------------------------------- 1 | /* -*- mode: js; js-basic-offset: 4; indent-tabs-mode: spaces -*- */ 2 | 3 | const Gtk = imports.gi.Gtk; 4 | const GLib = imports.gi.GLib 5 | const Config = imports.misc.config; 6 | const Me = imports.misc.extensionUtils.getCurrentExtension(); 7 | const GnomesomeSettings = Me.imports.gnomesome_settings; 8 | 9 | const Gettext = imports.gettext.domain('gnomesome'); 10 | const _ = Gettext.gettext; 11 | 12 | 13 | function init(){ 14 | // TODO: Translation 15 | // GnomesomeSettings.initTranslations(); 16 | } 17 | 18 | function buildPrefsWidget() { 19 | let settings = new GnomesomeSettings.Prefs(); 20 | 21 | let frame = new Gtk.Box({ 22 | orientation: Gtk.Orientation.VERTICAL, 23 | border_width: 10 24 | }); 25 | 26 | let vbox = new Gtk.Box({ 27 | orientation: Gtk.Orientation.VERTICAL, 28 | spacing: 14 29 | }); 30 | 31 | // General 32 | // =============================================================== 33 | 34 | (function() { 35 | let label = new Gtk.Label({ 36 | label: _("General:"), 37 | use_markup: true, 38 | xalign: 0 39 | }); 40 | vbox.add(label) 41 | })(); 42 | 43 | (function() { 44 | var hbox = new Gtk.Box({ 45 | orientation: Gtk.Orientation.HORIZONTAL, 46 | spacing: 20 47 | }); 48 | var label = new Gtk.Label({ label: _("Launch new terminal command:")} ); 49 | var textfield = new Gtk.Entry(); 50 | hbox.pack_start(label, false, false, 0); 51 | hbox.pack_start(textfield, false, false, 0); 52 | vbox.add(hbox); 53 | 54 | var pref = settings.LAUNCH_TERMINAL; 55 | textfield.set_text(pref.get()); 56 | textfield.connect('activate', function() { 57 | pref.set(textfield.get_text()); 58 | }); 59 | })(); 60 | 61 | (function() { 62 | var hbox = new Gtk.Box({ 63 | orientation: Gtk.Orientation.HORIZONTAL, 64 | spacing: 20 65 | }); 66 | 67 | var checkbutton = new Gtk.CheckButton({ label: _("Show indicator in status panel") }); 68 | 69 | hbox.pack_end(checkbutton, true, true, 0); 70 | vbox.add(hbox); 71 | 72 | var pref = settings.SHOW_INDICATOR; 73 | checkbutton.set_active(pref.get()); 74 | checkbutton.connect('toggled', function(sw) { 75 | var newval = sw.get_active(); 76 | if (newval !== pref.get()) { 77 | pref.set(newval); 78 | } 79 | }); 80 | })(); 81 | 82 | (function() { 83 | var hbox = new Gtk.Box({ 84 | orientation: Gtk.Orientation.HORIZONTAL, 85 | spacing: 20 86 | }); 87 | 88 | var checkbutton = new Gtk.CheckButton({ label: _("Pointer follows focus") }); 89 | 90 | hbox.pack_end(checkbutton, true, true, 0); 91 | vbox.add(hbox); 92 | 93 | var pref = settings.POINTER_FOLLOWS_FOCUS; 94 | checkbutton.set_active(pref.get()); 95 | checkbutton.connect('toggled', function(sw) { 96 | var newval = sw.get_active(); 97 | if (newval !== pref.get()) { 98 | pref.set(newval); 99 | } 100 | }); 101 | })(); 102 | 103 | // Tiling 104 | // =============================================================== 105 | 106 | (function() { 107 | let label = new Gtk.Label({ 108 | label: _("Tiling:"), 109 | use_markup: true, 110 | xalign: 0 111 | }); 112 | vbox.add(label); 113 | })(); 114 | 115 | (function() { 116 | var hbox = new Gtk.Box({ 117 | orientation: Gtk.Orientation.HORIZONTAL, 118 | spacing: 20 119 | }); 120 | 121 | var label = new Gtk.Label({ label: _("Default layout:"), }); 122 | var radio_box = new Gtk.Box({ 123 | orientation: Gtk.Orientation.VERTICAL, 124 | spacing: 2 125 | }); 126 | var r_floating = new Gtk.RadioButton( { label: _("Floating") }); 127 | var r_vertical = new Gtk.RadioButton( { label: _("Vertical"), group: r_floating }); 128 | var r_horizontal = new Gtk.RadioButton({ label: _("Horizontal"), group: r_floating }); 129 | var r_maximized = new Gtk.RadioButton({ label: _("Maximized"), group: r_floating }); 130 | 131 | var layout_radios = 132 | { 133 | 'floating': r_floating, 134 | 'horizontal': r_horizontal, 135 | 'vertical': r_vertical, 136 | 'maximized': r_maximized 137 | }; 138 | 139 | var pref = settings.DEFAULT_LAYOUT; 140 | var active = layout_radios[pref.get()]; 141 | if(active) { 142 | active.set_active(true); 143 | } 144 | var init_radio = function(k) { 145 | var radio = layout_radios[k]; 146 | radio.connect('toggled', function() { 147 | if(radio.get_active()) { 148 | pref.set(k); 149 | } 150 | }); 151 | radio_box.add(radio); 152 | }; 153 | init_radio('floating'); 154 | init_radio('vertical'); 155 | init_radio('horizontal'); 156 | init_radio('maximized'); 157 | 158 | hbox.add(label); 159 | hbox.add(radio_box); 160 | vbox.add(hbox); 161 | })(); 162 | 163 | (function() { 164 | var hbox = new Gtk.Box({ 165 | orientation: Gtk.Orientation.HORIZONTAL, 166 | spacing: 20 167 | }); 168 | var label = new Gtk.Label({ label: _("Inner gaps:")} ); 169 | var spin = new Gtk.SpinButton(); 170 | spin.set_range(0, 100); 171 | spin.set_increments(1, 5); 172 | hbox.add(label); 173 | hbox.add(spin); 174 | vbox.add(hbox); 175 | 176 | var pref = settings.INNER_GAPS; 177 | spin.set_value(pref.get()); 178 | spin.connect('value-changed', function() { 179 | pref.set(spin.get_value_as_int()); 180 | }); 181 | })(); 182 | 183 | (function() { 184 | var hbox = new Gtk.Box({ 185 | orientation: Gtk.Orientation.HORIZONTAL, 186 | spacing: 20 187 | }); 188 | var label = new Gtk.Label({ label: _("Outer gaps:")} ); 189 | var spin = new Gtk.SpinButton(); 190 | spin.set_range(0, 100); 191 | spin.set_increments(1, 5); 192 | hbox.add(label); 193 | hbox.add(spin); 194 | vbox.add(hbox); 195 | 196 | var pref = settings.OUTER_GAPS; 197 | spin.set_value(pref.get()); 198 | spin.connect('value-changed', function() { 199 | pref.set(spin.get_value_as_int()); 200 | }); 201 | })(); 202 | 203 | let sep = new Gtk.HSeparator(); 204 | vbox.add(sep); 205 | 206 | // Keybindings 207 | // =============================================================== 208 | 209 | (function() { 210 | let label = new Gtk.Label({ 211 | label: _("Advanced settings:"), 212 | use_markup: true, 213 | xalign: 0 214 | }); 215 | vbox.add(label); 216 | })(); 217 | 218 | (function() { 219 | var hbox = new Gtk.Box({ 220 | orientation: Gtk.Orientation.HORIZONTAL, 221 | spacing: 20 222 | }); 223 | 224 | var label = new Gtk.Label({ 225 | label: _("Edit keyboard settings") + 226 | "\n"+_("(make sure you have dconf-editor installed)")+"\n" + 227 | _("Navigate to")+" org/gnome/shell/extensions/gnomesome.keybindings", 228 | use_markup: true}); 229 | var button = new Gtk.Button({ 230 | label: 'dconf-editor' 231 | }); 232 | var error_msg = new Gtk.Label(); 233 | button.connect('clicked', function(sw) { 234 | try { 235 | // The magic sauce that lets dconf-editor see our local schema: 236 | var envp = GnomesomeSettings.envp_with_gnomesome_xdg_data_dir(); 237 | GLib.spawn_async(null, ['dconf-editor'], envp, GLib.SpawnFlags.SEARCH_PATH, null); 238 | } catch(e) { 239 | error_msg.set_label(_("ERROR: Could not launch dconf-editor. Is it installed?")); 240 | throw e; 241 | } 242 | }); 243 | 244 | hbox.add(label); 245 | hbox.pack_end(button, false, false, 0); 246 | vbox.add(hbox); 247 | vbox.add(error_msg); 248 | 249 | })(); 250 | 251 | frame.add(vbox); 252 | 253 | 254 | frame.show_all(); 255 | return frame; 256 | } 257 | 258 | 259 | -------------------------------------------------------------------------------- /schemas/gschemas.compiled: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChWick/gnomesome/7d04aa26f6dac9db5f72d56da4100d4f0b98a8cc/schemas/gschemas.compiled -------------------------------------------------------------------------------- /schemas/org.gnome.shell.extensions.gnomesome.gschema.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | "gnome-terminal" 6 | Command to launch a new terminal. 7 | 8 | 9 | 10 | true 11 | Whether or not to show the indicator in the status bar. 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | "floating" 22 | 23 | 24 | 25 | 20 26 | 27 | 28 | 29 | 10 30 | 31 | 32 | false 33 | Whether or not the pointer follows the focused window (centers) 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /schemas/org.gnome.shell.extensions.gnomesome.keybindings.gschema.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | ["<Mod4>e"] 6 | Select the next layout. 7 | Select the next layout on the current monitor and workspace. 8 | 9 | 10 | ["<Mod4><Shift>e"] 11 | Select the previous layout. 12 | Select the previous layout on the current monitor and workspace. 13 | 14 | 15 | ["<Mod4><Ctrl>h"] 16 | Switch to horizontal layout 17 | Change the layout of the current monitor and workspace to horizontal. 18 | 19 | 20 | ["<Mod4><Ctrl>v"] 21 | Switch to vertical layout 22 | Change the layout of the current monitor and workspace to vertical. 23 | 24 | 25 | ["<Mod4><Ctrl>f"] 26 | Switch to floating layout 27 | Change the layout of the current monitor and workspace to floating. 28 | 29 | 30 | ["<Mod4><Ctrl>m"] 31 | Switch to maximized layout 32 | Change the layout of the current monitor and workspace to maximized. 33 | 34 | 35 | ["<Mod4>j"] 36 | Select the next window. 37 | Select the next window on the current screen and workspace. 38 | 39 | 40 | ["<Mod4>k"] 41 | Select the previous window. 42 | Select the previous window on the current screen and workspace. 43 | 44 | 45 | ["<Mod4><Shift>j"] 46 | Switch client with next window. 47 | Switch the current client with the next client in a layout. 48 | 49 | 50 | ["<Mod4><Shift>k"] 51 | Select the previous monitor. 52 | Switch the current client with the previous client in a layout. 53 | 54 | 55 | ["<Mod4><Ctrl>j"] 56 | Select the next monitor. 57 | Select the topmost window on the next screen and current workspace. 58 | 59 | 60 | ["<Mod4><Ctrl>k"] 61 | Select the previous monitor. 62 | Select the topmost window on the previous screen and current workspace. 63 | 64 | 65 | ["<Mod4>o"] 66 | Move the current window to the next monitor 67 | Move the current focused window to the next monitor on the current workspace. 68 | 69 | 70 | ["<Mod4><Ctrl><Shift>j"] 71 | Move the current window to the next monitor 72 | Move the current focused window to the next monitor on the current workspace. 73 | 74 | 75 | ["<Mod4><Ctrl><Shift>k"] 76 | Move the current window to the previous monitor 77 | Move the current focused window to the previous monitor on the current workspace. 78 | 79 | 80 | ["<Mod4>i"] 81 | Increase the master window area. 82 | Move the split position of master clients to increase the master window area by 5%. 83 | 84 | 85 | ["<Mod4>u"] 86 | Decrease the master window area. 87 | Move the split position of master clients to decrease the master window area by 5%. 88 | 89 | 90 | ["<Mod4><Shift>i"] 91 | Increase the number of master windows. 92 | Increase the number of splits in the master area. 93 | 94 | 95 | ["<Mod4><Shift>u"] 96 | Decrease the number of master windows. 97 | Decrease the number of splits in the master area. 98 | 99 | 100 | ["<Mod4><Ctrl>return"] 101 | Swap the current window with the master. 102 | Swaps the current window with the master. The number of master windows will be set to one. 103 | 104 | 105 | ["<Mod4>1"] 106 | Select the workspace 1. 107 | Select the workspace with id 1. 108 | 109 | 110 | ["<Mod4>2"] 111 | Select the workspace 2. 112 | Select the workspace with id 2. 113 | 114 | 115 | ["<Mod4>3"] 116 | Select the workspace 3. 117 | Select the workspace with id 3. 118 | 119 | 120 | ["<Mod4>4"] 121 | Select the workspace 4. 122 | Select the workspace with id 4. 123 | 124 | 125 | ["<Mod4>5"] 126 | Select the workspace 5. 127 | Select the workspace with id 5. 128 | 129 | 130 | ["<Mod4>6"] 131 | Select the workspace 6. 132 | Select the workspace with id 6. 133 | 134 | 135 | ["<Mod4>7"] 136 | Select the workspace 7. 137 | Select the workspace with id 7. 138 | 139 | 140 | ["<Mod4>8"] 141 | Select the workspace 8. 142 | Select the workspace with id 8. 143 | 144 | 145 | ["<Mod4>9"] 146 | Select the workspace 9. 147 | Select the workspace with id 9. 148 | 149 | 150 | ["<Mod4>0"] 151 | Select the workspace 10. 152 | Select the workspace with id 10. 153 | 154 | 155 | ["<Mod4><Ctrl>1"] 156 | Move window to workspace 1. 157 | Move the current window to the workspace with id 1. 158 | 159 | 160 | ["<Mod4><Ctrl>2"] 161 | Move window to workspace 2. 162 | Move the current window to the workspace with id 2. 163 | 164 | 165 | ["<Mod4><Ctrl>3"] 166 | Move window to workspace 3. 167 | Move the current window to the workspace with id 3. 168 | 169 | 170 | ["<Mod4><Ctrl>4"] 171 | Move window to workspace 4. 172 | Move the current window to the workspace with id 4. 173 | 174 | 175 | ["<Mod4><Ctrl>5"] 176 | Move window to workspace 5. 177 | Move the current window to the workspace with id 5. 178 | 179 | 180 | ["<Mod4><Ctrl>6"] 181 | Move window to workspace 6. 182 | Move the current window to the workspace with id 6. 183 | 184 | 185 | ["<Mod4><Ctrl>7"] 186 | Move window to workspace 7. 187 | Move the current window to the workspace with id 7. 188 | 189 | 190 | ["<Mod4><Ctrl>8"] 191 | Move window to workspace 8. 192 | Move the current window to the workspace with id 8. 193 | 194 | 195 | ["<Mod4><Ctrl>9"] 196 | Move window to workspace 9. 197 | Move the current window to the workspace with id 9. 198 | 199 | 200 | ["<Mod4><Ctrl>0"] 201 | Move window to workspace 10. 202 | Move the current window to the workspace with id 10. 203 | 204 | 205 | ["<Mod4><Shift>m"] 206 | Toggle maximize. 207 | Toggle maximize of the current window. 208 | 209 | 210 | ["<Mod4><Shift>f"] 211 | Toggle fullscreen. 212 | Toggle fullscreen of the current window. 213 | 214 | 215 | ["<Mod4>f"] 216 | Toggle floating. 217 | Toggle floating of the current window. 218 | 219 | 220 | ["<Mod4>return"] 221 | Lanch an terminal. 222 | Launches a gnome terminal. 223 | 224 | 225 | 226 | -------------------------------------------------------------------------------- /splitlayout.js: -------------------------------------------------------------------------------- 1 | const Me = imports.misc.extensionUtils.getCurrentExtension(); 2 | const GnomesomeSettings = Me.imports.gnomesome_settings; 3 | const logging = Me.imports.logging; 4 | const logger = logging.getLogger('Gnomesome.Layout.Split'); 5 | 6 | function apply(gswindows, split_pos, n_master, orientation) { 7 | const settings = new GnomesomeSettings.Prefs(); 8 | logger.debug("Info: relayout max_nwindows" + gswindows.length); 9 | if (gswindows.length <= 0) { 10 | // no windows available 11 | return; 12 | } 13 | // check consistency and add allowed windows 14 | var monitor_idx = gswindows[0].get_monitor(); 15 | var workspace = gswindows[0].get_workspace(); 16 | var gswindows_to_layout = gswindows; 17 | for (var idx = 0; idx < gswindows.length; ++idx) { 18 | gswindows[idx].unmaximize_if_not_floating(); 19 | } 20 | 21 | const work_area = workspace.get_work_area_for_monitor(monitor_idx); 22 | work_area.x += settings.OUTER_GAPS.get(); 23 | work_area.y += settings.OUTER_GAPS.get(); 24 | work_area.width -= 2 * settings.OUTER_GAPS.get(); 25 | work_area.height -= 2 * settings.OUTER_GAPS.get(); 26 | logger.debug("Info: windows to layout " + gswindows_to_layout.length); 27 | 28 | var user = false; 29 | // handle dependend on number of windows 30 | if (gswindows_to_layout.length === 0) { 31 | // nothing to do 32 | } else if (gswindows_to_layout.length === 1) { 33 | // only one image, fill work area 34 | gswindows_to_layout[0].window.move_resize_frame( 35 | user, 36 | work_area.x, work_area.y, 37 | work_area.width, work_area.height); 38 | } else { 39 | // determine areas 40 | var gsmasters = gswindows_to_layout.slice(0, n_master); 41 | var gsclients = gswindows_to_layout.slice(n_master, gswindows_to_layout.length); 42 | logger.debug("master client" + gsmasters.length + " " + gsclients.length); 43 | 44 | if (orientation === 1) { 45 | // Horizontal layout 46 | let master_width = work_area.width * split_pos - settings.INNER_GAPS.get() / 2; 47 | let client_width = work_area.width - master_width - settings.INNER_GAPS.get(); 48 | if (gsclients.length === 0) {master_width = work_area.width; client_width = 0;} 49 | if (gsmasters.length === 0) {master_width = 0; client_width = work_area.width;} 50 | 51 | if (gsmasters.length > 0) { 52 | const sub_height = (work_area.height - (gsmasters.length - 1) * settings.INNER_GAPS.get()) / gsmasters.length; 53 | for (let idx = 0; idx < gsmasters.length; ++idx) { 54 | // first image half size, all others in rows 55 | gsmasters[idx].window.move_resize_frame( 56 | user, 57 | work_area.x, 58 | work_area.y + idx * (sub_height + settings.INNER_GAPS.get()), 59 | master_width, sub_height); 60 | } 61 | } 62 | 63 | if (gsclients.length > 0) { 64 | const sub_height = (work_area.height - (gsclients.length - 1) * settings.INNER_GAPS.get()) / gsclients.length; 65 | for (let idx = 0; idx < gsclients.length; ++idx) { 66 | gsclients[idx].window.move_resize_frame( 67 | user, 68 | work_area.x + master_width + settings.INNER_GAPS.get(), 69 | work_area.y + idx * (sub_height + settings.INNER_GAPS.get()), 70 | client_width, sub_height); 71 | } 72 | } 73 | } else { 74 | // vertical layout 75 | let master_height = work_area.height * split_pos - settings.INNER_GAPS.get() / 2; 76 | let client_height = work_area.height - master_height - settings.INNER_GAPS.get(); 77 | if (gsclients.length === 0) {master_height = work_area.height; client_height = 0;} 78 | if (gsmasters.length === 0) {master_height = 0; client_height = work_area.height;} 79 | 80 | if (gsmasters.length > 0) { 81 | const sub_width = (work_area.width - (gsmasters.length - 1) * settings.INNER_GAPS.get()) / gsmasters.length; 82 | for (let idx = 0; idx < gsmasters.length; ++idx) { 83 | // first image half size, all others in rows 84 | gsmasters[idx].window.move_resize_frame( 85 | user, 86 | work_area.x + idx * (sub_width + settings.INNER_GAPS.get()), 87 | work_area.y, 88 | sub_width, master_height); 89 | } 90 | } 91 | 92 | if (gsclients.length > 0) { 93 | var sub_width = (work_area.width - (gsclients.length - 1) * settings.INNER_GAPS.get()) / gsclients.length; 94 | for (let idx = 0; idx < gsclients.length; ++idx) { 95 | gsclients[idx].window.move_resize_frame( 96 | user, 97 | work_area.x + idx * (sub_width + settings.INNER_GAPS.get()), 98 | work_area.y + master_height + settings.INNER_GAPS.get(), 99 | sub_width, client_height); 100 | } 101 | } 102 | } 103 | } 104 | } 105 | 106 | function enterVBoxLayout(gswindows, split_pos, n_master) { 107 | } 108 | 109 | function applyVBoxLayout(gswindows, split_pos, n_master) { 110 | apply(gswindows, split_pos, n_master, 1); 111 | } 112 | 113 | function exitVBoxLayout(gswindows, split_pos, n_master) { 114 | } 115 | 116 | 117 | 118 | function exitHBoxLayout(gswindows, split_pos, n_master) { 119 | } 120 | 121 | function applyHBoxLayout(gswindows, split_pos, n_master) { 122 | apply(gswindows, split_pos, n_master, 0); 123 | } 124 | 125 | function enterHBoxLayout(gswindows, split_pos, n_master) { 126 | } 127 | -------------------------------------------------------------------------------- /stylesheet.css: -------------------------------------------------------------------------------- 1 | .system-status-icon { 2 | height: 1em; 3 | width: 1em; 4 | } 5 | -------------------------------------------------------------------------------- /thirdparty/log4js/README.md: -------------------------------------------------------------------------------- 1 | # Licence 2 | * log4javascript is licenced under the Apache License, Version 2.0 (see http://log4javascript.org/). 3 | * log4javascript_file/gjs_appender is licenced under GPL v3 (see https://github.com/timbertson/shellshape) 4 | -------------------------------------------------------------------------------- /thirdparty/log4js/log4javascript.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2014 Tim Down. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | /** 18 | * log4javascript 19 | * 20 | * log4javascript is a logging framework for JavaScript based on log4j 21 | * for Java. This file contains all core log4javascript code and is the only 22 | * file required to use log4javascript, unless you require support for 23 | * document.domain, in which case you will also need console.html, which must be 24 | * stored in the same directory as the main log4javascript.js file. 25 | * 26 | * Author: Tim Down 27 | * Version: 1.4.9 28 | * Edition: log4javascript 29 | * Build date: 12 May 2014 30 | * Website: http://log4javascript.org 31 | */ 32 | 33 | /* -------------------------------------------------------------------------- */ 34 | 35 | var log4javascript = (function() { 36 | 37 | function isUndefined(obj) { 38 | return typeof obj == "undefined"; 39 | } 40 | 41 | /* ---------------------------------------------------------------------- */ 42 | // Custom event support 43 | 44 | function EventSupport() {} 45 | 46 | EventSupport.prototype = { 47 | eventTypes: [], 48 | eventListeners: {}, 49 | setEventTypes: function(eventTypesParam) { 50 | if (eventTypesParam instanceof Array) { 51 | this.eventTypes = eventTypesParam; 52 | this.eventListeners = {}; 53 | for (var i = 0, len = this.eventTypes.length; i < len; i++) { 54 | this.eventListeners[this.eventTypes[i]] = []; 55 | } 56 | } else { 57 | handleError("log4javascript.EventSupport [" + this + "]: setEventTypes: eventTypes parameter must be an Array"); 58 | } 59 | }, 60 | 61 | addEventListener: function(eventType, listener) { 62 | if (typeof listener == "function") { 63 | if (!array_contains(this.eventTypes, eventType)) { 64 | handleError("log4javascript.EventSupport [" + this + "]: addEventListener: no event called '" + eventType + "'"); 65 | } 66 | this.eventListeners[eventType].push(listener); 67 | } else { 68 | handleError("log4javascript.EventSupport [" + this + "]: addEventListener: listener must be a function"); 69 | } 70 | }, 71 | 72 | removeEventListener: function(eventType, listener) { 73 | if (typeof listener == "function") { 74 | if (!array_contains(this.eventTypes, eventType)) { 75 | handleError("log4javascript.EventSupport [" + this + "]: removeEventListener: no event called '" + eventType + "'"); 76 | } 77 | array_remove(this.eventListeners[eventType], listener); 78 | } else { 79 | handleError("log4javascript.EventSupport [" + this + "]: removeEventListener: listener must be a function"); 80 | } 81 | }, 82 | 83 | dispatchEvent: function(eventType, eventArgs) { 84 | if (array_contains(this.eventTypes, eventType)) { 85 | var listeners = this.eventListeners[eventType]; 86 | for (var i = 0, len = listeners.length; i < len; i++) { 87 | listeners[i](this, eventType, eventArgs); 88 | } 89 | } else { 90 | handleError("log4javascript.EventSupport [" + this + "]: dispatchEvent: no event called '" + eventType + "'"); 91 | } 92 | } 93 | }; 94 | 95 | /* -------------------------------------------------------------------------- */ 96 | 97 | var applicationStartDate = new Date(); 98 | var uniqueId = "log4javascript_" + applicationStartDate.getTime() + "_" + 99 | Math.floor(Math.random() * 100000000); 100 | var emptyFunction = function() {}; 101 | var newLine = "\r\n"; 102 | var pageLoaded = false; 103 | 104 | // Create main log4javascript object; this will be assigned public properties 105 | function Log4JavaScript() {} 106 | Log4JavaScript.prototype = new EventSupport(); 107 | 108 | log4javascript = new Log4JavaScript(); 109 | log4javascript.version = "1.4.9"; 110 | log4javascript.edition = "log4javascript"; 111 | 112 | /* -------------------------------------------------------------------------- */ 113 | // Utility functions 114 | 115 | function toStr(obj) { 116 | if (obj && obj.toString) { 117 | return obj.toString(); 118 | } else { 119 | return String(obj); 120 | } 121 | } 122 | 123 | function getExceptionMessage(ex) { 124 | if (ex.message) { 125 | return ex.message; 126 | } else if (ex.description) { 127 | return ex.description; 128 | } else { 129 | return toStr(ex); 130 | } 131 | } 132 | 133 | // Gets the portion of the URL after the last slash 134 | function getUrlFileName(url) { 135 | var lastSlashIndex = Math.max(url.lastIndexOf("/"), url.lastIndexOf("\\")); 136 | return url.substr(lastSlashIndex + 1); 137 | } 138 | 139 | // Returns a nicely formatted representation of an error 140 | function getExceptionStringRep(ex) { 141 | if (ex) { 142 | var exStr = "Exception: " + getExceptionMessage(ex); 143 | try { 144 | if (ex.lineNumber) { 145 | exStr += " on line number " + ex.lineNumber; 146 | } 147 | if (ex.fileName) { 148 | exStr += " in file " + getUrlFileName(ex.fileName); 149 | } 150 | } catch (localEx) { 151 | logLog.warn("Unable to obtain file and line information for error"); 152 | } 153 | if (showStackTraces && ex.stack) { 154 | exStr += newLine + "Stack trace:" + newLine + ex.stack; 155 | } 156 | return exStr; 157 | } 158 | return null; 159 | } 160 | 161 | function bool(obj) { 162 | return Boolean(obj); 163 | } 164 | 165 | function trim(str) { 166 | return str.replace(/^\s+/, "").replace(/\s+$/, ""); 167 | } 168 | 169 | function splitIntoLines(text) { 170 | // Ensure all line breaks are \n only 171 | var text2 = text.replace(/\r\n/g, "\n").replace(/\r/g, "\n"); 172 | return text2.split("\n"); 173 | } 174 | 175 | var urlEncode = (typeof window.encodeURIComponent != "undefined") ? 176 | function(str) { 177 | return encodeURIComponent(str); 178 | }: 179 | function(str) { 180 | return escape(str).replace(/\+/g, "%2B").replace(/"/g, "%22").replace(/'/g, "%27").replace(/\//g, "%2F").replace(/=/g, "%3D"); 181 | }; 182 | 183 | var urlDecode = (typeof window.decodeURIComponent != "undefined") ? 184 | function(str) { 185 | return decodeURIComponent(str); 186 | }: 187 | function(str) { 188 | return unescape(str).replace(/%2B/g, "+").replace(/%22/g, "\"").replace(/%27/g, "'").replace(/%2F/g, "/").replace(/%3D/g, "="); 189 | }; 190 | 191 | function array_remove(arr, val) { 192 | var index = -1; 193 | for (var i = 0, len = arr.length; i < len; i++) { 194 | if (arr[i] === val) { 195 | index = i; 196 | break; 197 | } 198 | } 199 | if (index >= 0) { 200 | arr.splice(index, 1); 201 | return true; 202 | } else { 203 | return false; 204 | } 205 | } 206 | 207 | function array_contains(arr, val) { 208 | for(var i = 0, len = arr.length; i < len; i++) { 209 | if (arr[i] == val) { 210 | return true; 211 | } 212 | } 213 | return false; 214 | } 215 | 216 | function extractBooleanFromParam(param, defaultValue) { 217 | if (isUndefined(param)) { 218 | return defaultValue; 219 | } else { 220 | return bool(param); 221 | } 222 | } 223 | 224 | function extractStringFromParam(param, defaultValue) { 225 | if (isUndefined(param)) { 226 | return defaultValue; 227 | } else { 228 | return String(param); 229 | } 230 | } 231 | 232 | function extractIntFromParam(param, defaultValue) { 233 | if (isUndefined(param)) { 234 | return defaultValue; 235 | } else { 236 | try { 237 | var value = parseInt(param, 10); 238 | return isNaN(value) ? defaultValue : value; 239 | } catch (ex) { 240 | logLog.warn("Invalid int param " + param, ex); 241 | return defaultValue; 242 | } 243 | } 244 | } 245 | 246 | function extractFunctionFromParam(param, defaultValue) { 247 | if (typeof param == "function") { 248 | return param; 249 | } else { 250 | return defaultValue; 251 | } 252 | } 253 | 254 | function isError(err) { 255 | return (err instanceof Error); 256 | } 257 | 258 | if (!Function.prototype.apply){ 259 | Function.prototype.apply = function(obj, args) { 260 | var methodName = "__apply__"; 261 | if (typeof obj[methodName] != "undefined") { 262 | methodName += String(Math.random()).substr(2); 263 | } 264 | obj[methodName] = this; 265 | 266 | var argsStrings = []; 267 | for (var i = 0, len = args.length; i < len; i++) { 268 | argsStrings[i] = "args[" + i + "]"; 269 | } 270 | var script = "obj." + methodName + "(" + argsStrings.join(",") + ")"; 271 | var returnValue = eval(script); 272 | delete obj[methodName]; 273 | return returnValue; 274 | }; 275 | } 276 | 277 | if (!Function.prototype.call){ 278 | Function.prototype.call = function(obj) { 279 | var args = []; 280 | for (var i = 1, len = arguments.length; i < len; i++) { 281 | args[i - 1] = arguments[i]; 282 | } 283 | return this.apply(obj, args); 284 | }; 285 | } 286 | 287 | function getListenersPropertyName(eventName) { 288 | return "__log4javascript_listeners__" + eventName; 289 | } 290 | 291 | function addEvent(node, eventName, listener, useCapture, win) { 292 | win = win ? win : window; 293 | if (node.addEventListener) { 294 | node.addEventListener(eventName, listener, useCapture); 295 | } else if (node.attachEvent) { 296 | node.attachEvent("on" + eventName, listener); 297 | } else { 298 | var propertyName = getListenersPropertyName(eventName); 299 | if (!node[propertyName]) { 300 | node[propertyName] = []; 301 | // Set event handler 302 | node["on" + eventName] = function(evt) { 303 | evt = getEvent(evt, win); 304 | var listenersPropertyName = getListenersPropertyName(eventName); 305 | 306 | // Clone the array of listeners to leave the original untouched 307 | var listeners = this[listenersPropertyName].concat([]); 308 | var currentListener; 309 | 310 | // Call each listener in turn 311 | while ((currentListener = listeners.shift())) { 312 | currentListener.call(this, evt); 313 | } 314 | }; 315 | } 316 | node[propertyName].push(listener); 317 | } 318 | } 319 | 320 | function removeEvent(node, eventName, listener, useCapture) { 321 | if (node.removeEventListener) { 322 | node.removeEventListener(eventName, listener, useCapture); 323 | } else if (node.detachEvent) { 324 | node.detachEvent("on" + eventName, listener); 325 | } else { 326 | var propertyName = getListenersPropertyName(eventName); 327 | if (node[propertyName]) { 328 | array_remove(node[propertyName], listener); 329 | } 330 | } 331 | } 332 | 333 | function getEvent(evt, win) { 334 | win = win ? win : window; 335 | return evt ? evt : win.event; 336 | } 337 | 338 | function stopEventPropagation(evt) { 339 | if (evt.stopPropagation) { 340 | evt.stopPropagation(); 341 | } else if (typeof evt.cancelBubble != "undefined") { 342 | evt.cancelBubble = true; 343 | } 344 | evt.returnValue = false; 345 | } 346 | 347 | /* ---------------------------------------------------------------------- */ 348 | // Simple logging for log4javascript itself 349 | 350 | var logLog = { 351 | quietMode: false, 352 | 353 | debugMessages: [], 354 | 355 | setQuietMode: function(quietMode) { 356 | this.quietMode = bool(quietMode); 357 | }, 358 | 359 | numberOfErrors: 0, 360 | 361 | alertAllErrors: false, 362 | 363 | setAlertAllErrors: function(alertAllErrors) { 364 | this.alertAllErrors = alertAllErrors; 365 | }, 366 | 367 | debug: function(message) { 368 | this.debugMessages.push(message); 369 | }, 370 | 371 | displayDebug: function() { 372 | alert(this.debugMessages.join(newLine)); 373 | }, 374 | 375 | warn: function(message, exception) { 376 | }, 377 | 378 | error: function(message, exception) { 379 | if (++this.numberOfErrors == 1 || this.alertAllErrors) { 380 | if (!this.quietMode) { 381 | var alertMessage = "log4javascript error: " + message; 382 | if (exception) { 383 | alertMessage += newLine + newLine + "Original error: " + getExceptionStringRep(exception); 384 | } 385 | alert(alertMessage); 386 | } 387 | } 388 | } 389 | }; 390 | log4javascript.logLog = logLog; 391 | 392 | log4javascript.setEventTypes(["load", "error"]); 393 | 394 | function handleError(message, exception) { 395 | logLog.error(message, exception); 396 | log4javascript.dispatchEvent("error", { "message": message, "exception": exception }); 397 | } 398 | 399 | log4javascript.handleError = handleError; 400 | 401 | /* ---------------------------------------------------------------------- */ 402 | 403 | var enabled = !((typeof log4javascript_disabled != "undefined") && 404 | log4javascript_disabled); 405 | 406 | log4javascript.setEnabled = function(enable) { 407 | enabled = bool(enable); 408 | }; 409 | 410 | log4javascript.isEnabled = function() { 411 | return enabled; 412 | }; 413 | 414 | var useTimeStampsInMilliseconds = true; 415 | 416 | log4javascript.setTimeStampsInMilliseconds = function(timeStampsInMilliseconds) { 417 | useTimeStampsInMilliseconds = bool(timeStampsInMilliseconds); 418 | }; 419 | 420 | log4javascript.isTimeStampsInMilliseconds = function() { 421 | return useTimeStampsInMilliseconds; 422 | }; 423 | 424 | 425 | // This evaluates the given expression in the current scope, thus allowing 426 | // scripts to access private variables. Particularly useful for testing 427 | log4javascript.evalInScope = function(expr) { 428 | return eval(expr); 429 | }; 430 | 431 | var showStackTraces = false; 432 | 433 | log4javascript.setShowStackTraces = function(show) { 434 | showStackTraces = bool(show); 435 | }; 436 | 437 | /* ---------------------------------------------------------------------- */ 438 | // Levels 439 | 440 | var Level = function(level, name) { 441 | this.level = level; 442 | this.name = name; 443 | }; 444 | 445 | Level.prototype = { 446 | toString: function() { 447 | return this.name; 448 | }, 449 | equals: function(level) { 450 | return this.level == level.level; 451 | }, 452 | isGreaterOrEqual: function(level) { 453 | return this.level >= level.level; 454 | } 455 | }; 456 | 457 | Level.ALL = new Level(Number.MIN_VALUE, "ALL"); 458 | Level.TRACE = new Level(10000, "TRACE"); 459 | Level.DEBUG = new Level(20000, "DEBUG"); 460 | Level.INFO = new Level(30000, "INFO"); 461 | Level.WARN = new Level(40000, "WARN"); 462 | Level.ERROR = new Level(50000, "ERROR"); 463 | Level.FATAL = new Level(60000, "FATAL"); 464 | Level.OFF = new Level(Number.MAX_VALUE, "OFF"); 465 | 466 | log4javascript.Level = Level; 467 | 468 | /* ---------------------------------------------------------------------- */ 469 | // Timers 470 | 471 | function Timer(name, level) { 472 | this.name = name; 473 | this.level = isUndefined(level) ? Level.INFO : level; 474 | this.start = new Date(); 475 | } 476 | 477 | Timer.prototype.getElapsedTime = function() { 478 | return new Date().getTime() - this.start.getTime(); 479 | }; 480 | 481 | /* ---------------------------------------------------------------------- */ 482 | // Loggers 483 | 484 | var anonymousLoggerName = "[anonymous]"; 485 | var defaultLoggerName = "[default]"; 486 | var nullLoggerName = "[null]"; 487 | var rootLoggerName = "root"; 488 | 489 | function Logger(name) { 490 | this.name = name; 491 | this.parent = null; 492 | this.children = []; 493 | 494 | var appenders = []; 495 | var loggerLevel = null; 496 | var isRoot = (this.name === rootLoggerName); 497 | var isNull = (this.name === nullLoggerName); 498 | 499 | var appenderCache = null; 500 | var appenderCacheInvalidated = false; 501 | 502 | this.addChild = function(childLogger) { 503 | this.children.push(childLogger); 504 | childLogger.parent = this; 505 | childLogger.invalidateAppenderCache(); 506 | }; 507 | 508 | // Additivity 509 | var additive = true; 510 | this.getAdditivity = function() { 511 | return additive; 512 | }; 513 | 514 | this.setAdditivity = function(additivity) { 515 | var valueChanged = (additive != additivity); 516 | additive = additivity; 517 | if (valueChanged) { 518 | this.invalidateAppenderCache(); 519 | } 520 | }; 521 | 522 | // Create methods that use the appenders variable in this scope 523 | this.addAppender = function(appender) { 524 | if (isNull) { 525 | handleError("Logger.addAppender: you may not add an appender to the null logger"); 526 | } else { 527 | if (appender instanceof log4javascript.Appender) { 528 | if (!array_contains(appenders, appender)) { 529 | appenders.push(appender); 530 | appender.setAddedToLogger(this); 531 | this.invalidateAppenderCache(); 532 | } 533 | } else { 534 | handleError("Logger.addAppender: appender supplied ('" + 535 | toStr(appender) + "') is not a subclass of Appender"); 536 | } 537 | } 538 | }; 539 | 540 | this.removeAppender = function(appender) { 541 | array_remove(appenders, appender); 542 | appender.setRemovedFromLogger(this); 543 | this.invalidateAppenderCache(); 544 | }; 545 | 546 | this.removeAllAppenders = function() { 547 | var appenderCount = appenders.length; 548 | if (appenderCount > 0) { 549 | for (var i = 0; i < appenderCount; i++) { 550 | appenders[i].setRemovedFromLogger(this); 551 | } 552 | appenders.length = 0; 553 | this.invalidateAppenderCache(); 554 | } 555 | }; 556 | 557 | this.getEffectiveAppenders = function() { 558 | if (appenderCache === null || appenderCacheInvalidated) { 559 | // Build appender cache 560 | var parentEffectiveAppenders = (isRoot || !this.getAdditivity()) ? 561 | [] : this.parent.getEffectiveAppenders(); 562 | appenderCache = parentEffectiveAppenders.concat(appenders); 563 | appenderCacheInvalidated = false; 564 | } 565 | return appenderCache; 566 | }; 567 | 568 | this.invalidateAppenderCache = function() { 569 | appenderCacheInvalidated = true; 570 | for (var i = 0, len = this.children.length; i < len; i++) { 571 | this.children[i].invalidateAppenderCache(); 572 | } 573 | }; 574 | 575 | this.log = function(level, params) { 576 | if (enabled && level.isGreaterOrEqual(this.getEffectiveLevel())) { 577 | // Check whether last param is an exception 578 | var exception; 579 | var finalParamIndex = params.length - 1; 580 | var lastParam = params[finalParamIndex]; 581 | if (params.length > 1 && isError(lastParam)) { 582 | exception = lastParam; 583 | finalParamIndex--; 584 | } 585 | 586 | // Construct genuine array for the params 587 | var messages = []; 588 | for (var i = 0; i <= finalParamIndex; i++) { 589 | messages[i] = params[i]; 590 | } 591 | 592 | var loggingEvent = new LoggingEvent( 593 | this, new Date(), level, messages, exception); 594 | 595 | this.callAppenders(loggingEvent); 596 | } 597 | }; 598 | 599 | this.callAppenders = function(loggingEvent) { 600 | var effectiveAppenders = this.getEffectiveAppenders(); 601 | for (var i = 0, len = effectiveAppenders.length; i < len; i++) { 602 | effectiveAppenders[i].doAppend(loggingEvent); 603 | } 604 | }; 605 | 606 | this.setLevel = function(level) { 607 | // Having a level of null on the root logger would be very bad. 608 | if (isRoot && level === null) { 609 | handleError("Logger.setLevel: you cannot set the level of the root logger to null"); 610 | } else if (level instanceof Level) { 611 | loggerLevel = level; 612 | } else { 613 | handleError("Logger.setLevel: level supplied to logger " + 614 | this.name + " is not an instance of log4javascript.Level"); 615 | } 616 | }; 617 | 618 | this.getLevel = function() { 619 | return loggerLevel; 620 | }; 621 | 622 | this.getEffectiveLevel = function() { 623 | for (var logger = this; logger !== null; logger = logger.parent) { 624 | var level = logger.getLevel(); 625 | if (level !== null) { 626 | return level; 627 | } 628 | } 629 | }; 630 | 631 | this.group = function(name, initiallyExpanded) { 632 | if (enabled) { 633 | var effectiveAppenders = this.getEffectiveAppenders(); 634 | for (var i = 0, len = effectiveAppenders.length; i < len; i++) { 635 | effectiveAppenders[i].group(name, initiallyExpanded); 636 | } 637 | } 638 | }; 639 | 640 | this.groupEnd = function() { 641 | if (enabled) { 642 | var effectiveAppenders = this.getEffectiveAppenders(); 643 | for (var i = 0, len = effectiveAppenders.length; i < len; i++) { 644 | effectiveAppenders[i].groupEnd(); 645 | } 646 | } 647 | }; 648 | 649 | var timers = {}; 650 | 651 | this.time = function(name, level) { 652 | if (enabled) { 653 | if (isUndefined(name)) { 654 | handleError("Logger.time: a name for the timer must be supplied"); 655 | } else if (level && !(level instanceof Level)) { 656 | handleError("Logger.time: level supplied to timer " + 657 | name + " is not an instance of log4javascript.Level"); 658 | } else { 659 | timers[name] = new Timer(name, level); 660 | } 661 | } 662 | }; 663 | 664 | this.timeEnd = function(name) { 665 | if (enabled) { 666 | if (isUndefined(name)) { 667 | handleError("Logger.timeEnd: a name for the timer must be supplied"); 668 | } else if (timers[name]) { 669 | var timer = timers[name]; 670 | var milliseconds = timer.getElapsedTime(); 671 | this.log(timer.level, ["Timer " + toStr(name) + " completed in " + milliseconds + "ms"]); 672 | delete timers[name]; 673 | } else { 674 | logLog.warn("Logger.timeEnd: no timer found with name " + name); 675 | } 676 | } 677 | }; 678 | 679 | this.assert = function(expr) { 680 | if (enabled && !expr) { 681 | var args = []; 682 | for (var i = 1, len = arguments.length; i < len; i++) { 683 | args.push(arguments[i]); 684 | } 685 | args = (args.length > 0) ? args : ["Assertion Failure"]; 686 | args.push(newLine); 687 | args.push(expr); 688 | this.log(Level.ERROR, args); 689 | } 690 | }; 691 | 692 | this.toString = function() { 693 | return "Logger[" + this.name + "]"; 694 | }; 695 | } 696 | 697 | Logger.prototype = { 698 | trace: function() { 699 | this.log(Level.TRACE, arguments); 700 | }, 701 | 702 | debug: function() { 703 | this.log(Level.DEBUG, arguments); 704 | }, 705 | 706 | info: function() { 707 | this.log(Level.INFO, arguments); 708 | }, 709 | 710 | warn: function() { 711 | this.log(Level.WARN, arguments); 712 | }, 713 | 714 | error: function() { 715 | this.log(Level.ERROR, arguments); 716 | }, 717 | 718 | fatal: function() { 719 | this.log(Level.FATAL, arguments); 720 | }, 721 | 722 | isEnabledFor: function(level) { 723 | return level.isGreaterOrEqual(this.getEffectiveLevel()); 724 | }, 725 | 726 | isTraceEnabled: function() { 727 | return this.isEnabledFor(Level.TRACE); 728 | }, 729 | 730 | isDebugEnabled: function() { 731 | return this.isEnabledFor(Level.DEBUG); 732 | }, 733 | 734 | isInfoEnabled: function() { 735 | return this.isEnabledFor(Level.INFO); 736 | }, 737 | 738 | isWarnEnabled: function() { 739 | return this.isEnabledFor(Level.WARN); 740 | }, 741 | 742 | isErrorEnabled: function() { 743 | return this.isEnabledFor(Level.ERROR); 744 | }, 745 | 746 | isFatalEnabled: function() { 747 | return this.isEnabledFor(Level.FATAL); 748 | } 749 | }; 750 | 751 | Logger.prototype.trace.isEntryPoint = true; 752 | Logger.prototype.debug.isEntryPoint = true; 753 | Logger.prototype.info.isEntryPoint = true; 754 | Logger.prototype.warn.isEntryPoint = true; 755 | Logger.prototype.error.isEntryPoint = true; 756 | Logger.prototype.fatal.isEntryPoint = true; 757 | 758 | /* ---------------------------------------------------------------------- */ 759 | // Logger access methods 760 | 761 | // Hashtable of loggers keyed by logger name 762 | var loggers = {}; 763 | var loggerNames = []; 764 | 765 | var ROOT_LOGGER_DEFAULT_LEVEL = Level.DEBUG; 766 | var rootLogger = new Logger(rootLoggerName); 767 | rootLogger.setLevel(ROOT_LOGGER_DEFAULT_LEVEL); 768 | 769 | log4javascript.getRootLogger = function() { 770 | return rootLogger; 771 | }; 772 | 773 | log4javascript.getLogger = function(loggerName) { 774 | // Use default logger if loggerName is not specified or invalid 775 | if (!(typeof loggerName == "string")) { 776 | loggerName = anonymousLoggerName; 777 | logLog.warn("log4javascript.getLogger: non-string logger name " + 778 | toStr(loggerName) + " supplied, returning anonymous logger"); 779 | } 780 | 781 | // Do not allow retrieval of the root logger by name 782 | if (loggerName == rootLoggerName) { 783 | handleError("log4javascript.getLogger: root logger may not be obtained by name"); 784 | } 785 | 786 | // Create the logger for this name if it doesn't already exist 787 | if (!loggers[loggerName]) { 788 | var logger = new Logger(loggerName); 789 | loggers[loggerName] = logger; 790 | loggerNames.push(loggerName); 791 | 792 | // Set up parent logger, if it doesn't exist 793 | var lastDotIndex = loggerName.lastIndexOf("."); 794 | var parentLogger; 795 | if (lastDotIndex > -1) { 796 | var parentLoggerName = loggerName.substring(0, lastDotIndex); 797 | parentLogger = log4javascript.getLogger(parentLoggerName); // Recursively sets up grandparents etc. 798 | } else { 799 | parentLogger = rootLogger; 800 | } 801 | parentLogger.addChild(logger); 802 | } 803 | return loggers[loggerName]; 804 | }; 805 | 806 | var defaultLogger = null; 807 | log4javascript.getDefaultLogger = function() { 808 | if (!defaultLogger) { 809 | defaultLogger = log4javascript.getLogger(defaultLoggerName); 810 | var a = new log4javascript.PopUpAppender(); 811 | defaultLogger.addAppender(a); 812 | } 813 | return defaultLogger; 814 | }; 815 | 816 | var nullLogger = null; 817 | log4javascript.getNullLogger = function() { 818 | if (!nullLogger) { 819 | nullLogger = new Logger(nullLoggerName); 820 | nullLogger.setLevel(Level.OFF); 821 | } 822 | return nullLogger; 823 | }; 824 | 825 | // Destroys all loggers 826 | log4javascript.resetConfiguration = function() { 827 | rootLogger.setLevel(ROOT_LOGGER_DEFAULT_LEVEL); 828 | loggers = {}; 829 | }; 830 | 831 | /* ---------------------------------------------------------------------- */ 832 | // Logging events 833 | 834 | var LoggingEvent = function(logger, timeStamp, level, messages, 835 | exception) { 836 | this.logger = logger; 837 | this.timeStamp = timeStamp; 838 | this.timeStampInMilliseconds = timeStamp.getTime(); 839 | this.timeStampInSeconds = Math.floor(this.timeStampInMilliseconds / 1000); 840 | this.milliseconds = this.timeStamp.getMilliseconds(); 841 | this.level = level; 842 | this.messages = messages; 843 | this.exception = exception; 844 | }; 845 | 846 | LoggingEvent.prototype = { 847 | getThrowableStrRep: function() { 848 | return this.exception ? 849 | getExceptionStringRep(this.exception) : ""; 850 | }, 851 | getCombinedMessages: function() { 852 | return (this.messages.length == 1) ? this.messages[0] : 853 | this.messages.join(newLine); 854 | }, 855 | toString: function() { 856 | return "LoggingEvent[" + this.level + "]"; 857 | } 858 | }; 859 | 860 | log4javascript.LoggingEvent = LoggingEvent; 861 | 862 | /* ---------------------------------------------------------------------- */ 863 | // Layout prototype 864 | 865 | var Layout = function() { 866 | }; 867 | 868 | Layout.prototype = { 869 | defaults: { 870 | loggerKey: "logger", 871 | timeStampKey: "timestamp", 872 | millisecondsKey: "milliseconds", 873 | levelKey: "level", 874 | messageKey: "message", 875 | exceptionKey: "exception", 876 | urlKey: "url" 877 | }, 878 | loggerKey: "logger", 879 | timeStampKey: "timestamp", 880 | millisecondsKey: "milliseconds", 881 | levelKey: "level", 882 | messageKey: "message", 883 | exceptionKey: "exception", 884 | urlKey: "url", 885 | batchHeader: "", 886 | batchFooter: "", 887 | batchSeparator: "", 888 | returnsPostData: false, 889 | overrideTimeStampsSetting: false, 890 | useTimeStampsInMilliseconds: null, 891 | 892 | format: function() { 893 | handleError("Layout.format: layout supplied has no format() method"); 894 | }, 895 | 896 | ignoresThrowable: function() { 897 | handleError("Layout.ignoresThrowable: layout supplied has no ignoresThrowable() method"); 898 | }, 899 | 900 | getContentType: function() { 901 | return "text/plain"; 902 | }, 903 | 904 | allowBatching: function() { 905 | return true; 906 | }, 907 | 908 | setTimeStampsInMilliseconds: function(timeStampsInMilliseconds) { 909 | this.overrideTimeStampsSetting = true; 910 | this.useTimeStampsInMilliseconds = bool(timeStampsInMilliseconds); 911 | }, 912 | 913 | isTimeStampsInMilliseconds: function() { 914 | return this.overrideTimeStampsSetting ? 915 | this.useTimeStampsInMilliseconds : useTimeStampsInMilliseconds; 916 | }, 917 | 918 | getTimeStampValue: function(loggingEvent) { 919 | return this.isTimeStampsInMilliseconds() ? 920 | loggingEvent.timeStampInMilliseconds : loggingEvent.timeStampInSeconds; 921 | }, 922 | 923 | getDataValues: function(loggingEvent, combineMessages) { 924 | var dataValues = [ 925 | [this.loggerKey, loggingEvent.logger.name], 926 | [this.timeStampKey, this.getTimeStampValue(loggingEvent)], 927 | [this.levelKey, loggingEvent.level.name], 928 | [this.urlKey, window.location.href], 929 | [this.messageKey, combineMessages ? loggingEvent.getCombinedMessages() : loggingEvent.messages] 930 | ]; 931 | if (!this.isTimeStampsInMilliseconds()) { 932 | dataValues.push([this.millisecondsKey, loggingEvent.milliseconds]); 933 | } 934 | if (loggingEvent.exception) { 935 | dataValues.push([this.exceptionKey, getExceptionStringRep(loggingEvent.exception)]); 936 | } 937 | if (this.hasCustomFields()) { 938 | for (var i = 0, len = this.customFields.length; i < len; i++) { 939 | var val = this.customFields[i].value; 940 | 941 | // Check if the value is a function. If so, execute it, passing it the 942 | // current layout and the logging event 943 | if (typeof val === "function") { 944 | val = val(this, loggingEvent); 945 | } 946 | dataValues.push([this.customFields[i].name, val]); 947 | } 948 | } 949 | return dataValues; 950 | }, 951 | 952 | setKeys: function(loggerKey, timeStampKey, levelKey, messageKey, 953 | exceptionKey, urlKey, millisecondsKey) { 954 | this.loggerKey = extractStringFromParam(loggerKey, this.defaults.loggerKey); 955 | this.timeStampKey = extractStringFromParam(timeStampKey, this.defaults.timeStampKey); 956 | this.levelKey = extractStringFromParam(levelKey, this.defaults.levelKey); 957 | this.messageKey = extractStringFromParam(messageKey, this.defaults.messageKey); 958 | this.exceptionKey = extractStringFromParam(exceptionKey, this.defaults.exceptionKey); 959 | this.urlKey = extractStringFromParam(urlKey, this.defaults.urlKey); 960 | this.millisecondsKey = extractStringFromParam(millisecondsKey, this.defaults.millisecondsKey); 961 | }, 962 | 963 | setCustomField: function(name, value) { 964 | var fieldUpdated = false; 965 | for (var i = 0, len = this.customFields.length; i < len; i++) { 966 | if (this.customFields[i].name === name) { 967 | this.customFields[i].value = value; 968 | fieldUpdated = true; 969 | } 970 | } 971 | if (!fieldUpdated) { 972 | this.customFields.push({"name": name, "value": value}); 973 | } 974 | }, 975 | 976 | hasCustomFields: function() { 977 | return (this.customFields.length > 0); 978 | }, 979 | 980 | formatWithException: function(loggingEvent) { 981 | var formatted = this.format(loggingEvent); 982 | if (loggingEvent.exception && this.ignoresThrowable()) { 983 | formatted += loggingEvent.getThrowableStrRep(); 984 | } 985 | return formatted; 986 | }, 987 | 988 | toString: function() { 989 | handleError("Layout.toString: all layouts must override this method"); 990 | } 991 | }; 992 | 993 | log4javascript.Layout = Layout; 994 | 995 | /* ---------------------------------------------------------------------- */ 996 | // Appender prototype 997 | 998 | var Appender = function() {}; 999 | 1000 | Appender.prototype = new EventSupport(); 1001 | 1002 | Appender.prototype.layout = new PatternLayout(); 1003 | Appender.prototype.threshold = Level.ALL; 1004 | Appender.prototype.loggers = []; 1005 | 1006 | // Performs threshold checks before delegating actual logging to the 1007 | // subclass's specific append method. 1008 | Appender.prototype.doAppend = function(loggingEvent) { 1009 | if (enabled && loggingEvent.level.level >= this.threshold.level) { 1010 | this.append(loggingEvent); 1011 | } 1012 | }; 1013 | 1014 | Appender.prototype.append = function(loggingEvent) {}; 1015 | 1016 | Appender.prototype.setLayout = function(layout) { 1017 | if (layout instanceof Layout) { 1018 | this.layout = layout; 1019 | } else { 1020 | handleError("Appender.setLayout: layout supplied to " + 1021 | this.toString() + " is not a subclass of Layout"); 1022 | } 1023 | }; 1024 | 1025 | Appender.prototype.getLayout = function() { 1026 | return this.layout; 1027 | }; 1028 | 1029 | Appender.prototype.setThreshold = function(threshold) { 1030 | if (threshold instanceof Level) { 1031 | this.threshold = threshold; 1032 | } else { 1033 | handleError("Appender.setThreshold: threshold supplied to " + 1034 | this.toString() + " is not a subclass of Level"); 1035 | } 1036 | }; 1037 | 1038 | Appender.prototype.getThreshold = function() { 1039 | return this.threshold; 1040 | }; 1041 | 1042 | Appender.prototype.setAddedToLogger = function(logger) { 1043 | this.loggers.push(logger); 1044 | }; 1045 | 1046 | Appender.prototype.setRemovedFromLogger = function(logger) { 1047 | array_remove(this.loggers, logger); 1048 | }; 1049 | 1050 | Appender.prototype.group = emptyFunction; 1051 | Appender.prototype.groupEnd = emptyFunction; 1052 | 1053 | Appender.prototype.toString = function() { 1054 | handleError("Appender.toString: all appenders must override this method"); 1055 | }; 1056 | 1057 | log4javascript.Appender = Appender; 1058 | 1059 | /* ---------------------------------------------------------------------- */ 1060 | // SimpleLayout 1061 | 1062 | function SimpleLayout() { 1063 | this.customFields = []; 1064 | } 1065 | 1066 | SimpleLayout.prototype = new Layout(); 1067 | 1068 | SimpleLayout.prototype.format = function(loggingEvent) { 1069 | return loggingEvent.level.name + " - " + loggingEvent.getCombinedMessages(); 1070 | }; 1071 | 1072 | SimpleLayout.prototype.ignoresThrowable = function() { 1073 | return true; 1074 | }; 1075 | 1076 | SimpleLayout.prototype.toString = function() { 1077 | return "SimpleLayout"; 1078 | }; 1079 | 1080 | log4javascript.SimpleLayout = SimpleLayout; 1081 | /* ----------------------------------------------------------------------- */ 1082 | // NullLayout 1083 | 1084 | function NullLayout() { 1085 | this.customFields = []; 1086 | } 1087 | 1088 | NullLayout.prototype = new Layout(); 1089 | 1090 | NullLayout.prototype.format = function(loggingEvent) { 1091 | return loggingEvent.messages; 1092 | }; 1093 | 1094 | NullLayout.prototype.ignoresThrowable = function() { 1095 | return true; 1096 | }; 1097 | 1098 | NullLayout.prototype.formatWithException = function(loggingEvent) { 1099 | var messages = loggingEvent.messages, ex = loggingEvent.exception; 1100 | return ex ? messages.concat([ex]) : messages; 1101 | }; 1102 | 1103 | NullLayout.prototype.toString = function() { 1104 | return "NullLayout"; 1105 | }; 1106 | 1107 | log4javascript.NullLayout = NullLayout; 1108 | 1109 | /* ---------------------------------------------------------------------- */ 1110 | // formatObjectExpansion 1111 | 1112 | function formatObjectExpansion(obj, depth, indentation) { 1113 | var objectsExpanded = []; 1114 | 1115 | function doFormat(obj, depth, indentation) { 1116 | var i, j, len, childDepth, childIndentation, childLines, expansion, 1117 | childExpansion; 1118 | 1119 | if (!indentation) { 1120 | indentation = ""; 1121 | } 1122 | 1123 | function formatString(text) { 1124 | var lines = splitIntoLines(text); 1125 | for (var j = 1, jLen = lines.length; j < jLen; j++) { 1126 | lines[j] = indentation + lines[j]; 1127 | } 1128 | return lines.join(newLine); 1129 | } 1130 | 1131 | if (obj === null) { 1132 | return "null"; 1133 | } else if (typeof obj == "undefined") { 1134 | return "undefined"; 1135 | } else if (typeof obj == "string") { 1136 | return formatString(obj); 1137 | } else if (typeof obj == "object" && array_contains(objectsExpanded, obj)) { 1138 | try { 1139 | expansion = toStr(obj); 1140 | } catch (ex) { 1141 | expansion = "Error formatting property. Details: " + getExceptionStringRep(ex); 1142 | } 1143 | return expansion + " [already expanded]"; 1144 | } else if ((obj instanceof Array) && depth > 0) { 1145 | objectsExpanded.push(obj); 1146 | expansion = "[" + newLine; 1147 | childDepth = depth - 1; 1148 | childIndentation = indentation + " "; 1149 | childLines = []; 1150 | for (i = 0, len = obj.length; i < len; i++) { 1151 | try { 1152 | childExpansion = doFormat(obj[i], childDepth, childIndentation); 1153 | childLines.push(childIndentation + childExpansion); 1154 | } catch (ex) { 1155 | childLines.push(childIndentation + "Error formatting array member. Details: " + 1156 | getExceptionStringRep(ex) + ""); 1157 | } 1158 | } 1159 | expansion += childLines.join("," + newLine) + newLine + indentation + "]"; 1160 | return expansion; 1161 | } else if (Object.prototype.toString.call(obj) == "[object Date]") { 1162 | return obj.toString(); 1163 | } else if (typeof obj == "object" && depth > 0) { 1164 | objectsExpanded.push(obj); 1165 | expansion = "{" + newLine; 1166 | childDepth = depth - 1; 1167 | childIndentation = indentation + " "; 1168 | childLines = []; 1169 | for (i in obj) { 1170 | try { 1171 | childExpansion = doFormat(obj[i], childDepth, childIndentation); 1172 | childLines.push(childIndentation + i + ": " + childExpansion); 1173 | } catch (ex) { 1174 | childLines.push(childIndentation + i + ": Error formatting property. Details: " + 1175 | getExceptionStringRep(ex)); 1176 | } 1177 | } 1178 | expansion += childLines.join("," + newLine) + newLine + indentation + "}"; 1179 | return expansion; 1180 | } else { 1181 | return formatString(toStr(obj)); 1182 | } 1183 | } 1184 | return doFormat(obj, depth, indentation); 1185 | } 1186 | /* ---------------------------------------------------------------------- */ 1187 | // Date-related stuff 1188 | 1189 | var SimpleDateFormat; 1190 | 1191 | (function() { 1192 | var regex = /('[^']*')|(G+|y+|M+|w+|W+|D+|d+|F+|E+|a+|H+|k+|K+|h+|m+|s+|S+|Z+)|([a-zA-Z]+)|([^a-zA-Z']+)/; 1193 | var monthNames = ["January", "February", "March", "April", "May", "June", 1194 | "July", "August", "September", "October", "November", "December"]; 1195 | var dayNames = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"]; 1196 | var TEXT2 = 0, TEXT3 = 1, NUMBER = 2, YEAR = 3, MONTH = 4, TIMEZONE = 5; 1197 | var types = { 1198 | G : TEXT2, 1199 | y : YEAR, 1200 | M : MONTH, 1201 | w : NUMBER, 1202 | W : NUMBER, 1203 | D : NUMBER, 1204 | d : NUMBER, 1205 | F : NUMBER, 1206 | E : TEXT3, 1207 | a : TEXT2, 1208 | H : NUMBER, 1209 | k : NUMBER, 1210 | K : NUMBER, 1211 | h : NUMBER, 1212 | m : NUMBER, 1213 | s : NUMBER, 1214 | S : NUMBER, 1215 | Z : TIMEZONE 1216 | }; 1217 | var ONE_DAY = 24 * 60 * 60 * 1000; 1218 | var ONE_WEEK = 7 * ONE_DAY; 1219 | var DEFAULT_MINIMAL_DAYS_IN_FIRST_WEEK = 1; 1220 | 1221 | var newDateAtMidnight = function(year, month, day) { 1222 | var d = new Date(year, month, day, 0, 0, 0); 1223 | d.setMilliseconds(0); 1224 | return d; 1225 | }; 1226 | 1227 | Date.prototype.getDifference = function(date) { 1228 | return this.getTime() - date.getTime(); 1229 | }; 1230 | 1231 | Date.prototype.isBefore = function(d) { 1232 | return this.getTime() < d.getTime(); 1233 | }; 1234 | 1235 | Date.prototype.getUTCTime = function() { 1236 | return Date.UTC(this.getFullYear(), this.getMonth(), this.getDate(), this.getHours(), this.getMinutes(), 1237 | this.getSeconds(), this.getMilliseconds()); 1238 | }; 1239 | 1240 | Date.prototype.getTimeSince = function(d) { 1241 | return this.getUTCTime() - d.getUTCTime(); 1242 | }; 1243 | 1244 | Date.prototype.getPreviousSunday = function() { 1245 | // Using midday avoids any possibility of DST messing things up 1246 | var midday = new Date(this.getFullYear(), this.getMonth(), this.getDate(), 12, 0, 0); 1247 | var previousSunday = new Date(midday.getTime() - this.getDay() * ONE_DAY); 1248 | return newDateAtMidnight(previousSunday.getFullYear(), previousSunday.getMonth(), 1249 | previousSunday.getDate()); 1250 | }; 1251 | 1252 | Date.prototype.getWeekInYear = function(minimalDaysInFirstWeek) { 1253 | if (isUndefined(this.minimalDaysInFirstWeek)) { 1254 | minimalDaysInFirstWeek = DEFAULT_MINIMAL_DAYS_IN_FIRST_WEEK; 1255 | } 1256 | var previousSunday = this.getPreviousSunday(); 1257 | var startOfYear = newDateAtMidnight(this.getFullYear(), 0, 1); 1258 | var numberOfSundays = previousSunday.isBefore(startOfYear) ? 1259 | 0 : 1 + Math.floor(previousSunday.getTimeSince(startOfYear) / ONE_WEEK); 1260 | var numberOfDaysInFirstWeek = 7 - startOfYear.getDay(); 1261 | var weekInYear = numberOfSundays; 1262 | if (numberOfDaysInFirstWeek < minimalDaysInFirstWeek) { 1263 | weekInYear--; 1264 | } 1265 | return weekInYear; 1266 | }; 1267 | 1268 | Date.prototype.getWeekInMonth = function(minimalDaysInFirstWeek) { 1269 | if (isUndefined(this.minimalDaysInFirstWeek)) { 1270 | minimalDaysInFirstWeek = DEFAULT_MINIMAL_DAYS_IN_FIRST_WEEK; 1271 | } 1272 | var previousSunday = this.getPreviousSunday(); 1273 | var startOfMonth = newDateAtMidnight(this.getFullYear(), this.getMonth(), 1); 1274 | var numberOfSundays = previousSunday.isBefore(startOfMonth) ? 1275 | 0 : 1 + Math.floor(previousSunday.getTimeSince(startOfMonth) / ONE_WEEK); 1276 | var numberOfDaysInFirstWeek = 7 - startOfMonth.getDay(); 1277 | var weekInMonth = numberOfSundays; 1278 | if (numberOfDaysInFirstWeek >= minimalDaysInFirstWeek) { 1279 | weekInMonth++; 1280 | } 1281 | return weekInMonth; 1282 | }; 1283 | 1284 | Date.prototype.getDayInYear = function() { 1285 | var startOfYear = newDateAtMidnight(this.getFullYear(), 0, 1); 1286 | return 1 + Math.floor(this.getTimeSince(startOfYear) / ONE_DAY); 1287 | }; 1288 | 1289 | /* ------------------------------------------------------------------ */ 1290 | 1291 | SimpleDateFormat = function(formatString) { 1292 | this.formatString = formatString; 1293 | }; 1294 | 1295 | /** 1296 | * Sets the minimum number of days in a week in order for that week to 1297 | * be considered as belonging to a particular month or year 1298 | */ 1299 | SimpleDateFormat.prototype.setMinimalDaysInFirstWeek = function(days) { 1300 | this.minimalDaysInFirstWeek = days; 1301 | }; 1302 | 1303 | SimpleDateFormat.prototype.getMinimalDaysInFirstWeek = function() { 1304 | return isUndefined(this.minimalDaysInFirstWeek) ? 1305 | DEFAULT_MINIMAL_DAYS_IN_FIRST_WEEK : this.minimalDaysInFirstWeek; 1306 | }; 1307 | 1308 | var padWithZeroes = function(str, len) { 1309 | while (str.length < len) { 1310 | str = "0" + str; 1311 | } 1312 | return str; 1313 | }; 1314 | 1315 | var formatText = function(data, numberOfLetters, minLength) { 1316 | return (numberOfLetters >= 4) ? data : data.substr(0, Math.max(minLength, numberOfLetters)); 1317 | }; 1318 | 1319 | var formatNumber = function(data, numberOfLetters) { 1320 | var dataString = "" + data; 1321 | // Pad with 0s as necessary 1322 | return padWithZeroes(dataString, numberOfLetters); 1323 | }; 1324 | 1325 | SimpleDateFormat.prototype.format = function(date) { 1326 | var formattedString = ""; 1327 | var result; 1328 | var searchString = this.formatString; 1329 | while ((result = regex.exec(searchString))) { 1330 | var quotedString = result[1]; 1331 | var patternLetters = result[2]; 1332 | var otherLetters = result[3]; 1333 | var otherCharacters = result[4]; 1334 | 1335 | // If the pattern matched is quoted string, output the text between the quotes 1336 | if (quotedString) { 1337 | if (quotedString == "''") { 1338 | formattedString += "'"; 1339 | } else { 1340 | formattedString += quotedString.substring(1, quotedString.length - 1); 1341 | } 1342 | } else if (otherLetters) { 1343 | // Swallow non-pattern letters by doing nothing here 1344 | } else if (otherCharacters) { 1345 | // Simply output other characters 1346 | formattedString += otherCharacters; 1347 | } else if (patternLetters) { 1348 | // Replace pattern letters 1349 | var patternLetter = patternLetters.charAt(0); 1350 | var numberOfLetters = patternLetters.length; 1351 | var rawData = ""; 1352 | switch(patternLetter) { 1353 | case "G": 1354 | rawData = "AD"; 1355 | break; 1356 | case "y": 1357 | rawData = date.getFullYear(); 1358 | break; 1359 | case "M": 1360 | rawData = date.getMonth(); 1361 | break; 1362 | case "w": 1363 | rawData = date.getWeekInYear(this.getMinimalDaysInFirstWeek()); 1364 | break; 1365 | case "W": 1366 | rawData = date.getWeekInMonth(this.getMinimalDaysInFirstWeek()); 1367 | break; 1368 | case "D": 1369 | rawData = date.getDayInYear(); 1370 | break; 1371 | case "d": 1372 | rawData = date.getDate(); 1373 | break; 1374 | case "F": 1375 | rawData = 1 + Math.floor((date.getDate() - 1) / 7); 1376 | break; 1377 | case "E": 1378 | rawData = dayNames[date.getDay()]; 1379 | break; 1380 | case "a": 1381 | rawData = (date.getHours() >= 12) ? "PM" : "AM"; 1382 | break; 1383 | case "H": 1384 | rawData = date.getHours(); 1385 | break; 1386 | case "k": 1387 | rawData = date.getHours() || 24; 1388 | break; 1389 | case "K": 1390 | rawData = date.getHours() % 12; 1391 | break; 1392 | case "h": 1393 | rawData = (date.getHours() % 12) || 12; 1394 | break; 1395 | case "m": 1396 | rawData = date.getMinutes(); 1397 | break; 1398 | case "s": 1399 | rawData = date.getSeconds(); 1400 | break; 1401 | case "S": 1402 | rawData = date.getMilliseconds(); 1403 | break; 1404 | case "Z": 1405 | rawData = date.getTimezoneOffset(); // This returns the number of minutes since GMT was this time. 1406 | break; 1407 | } 1408 | // Format the raw data depending on the type 1409 | switch(types[patternLetter]) { 1410 | case TEXT2: 1411 | formattedString += formatText(rawData, numberOfLetters, 2); 1412 | break; 1413 | case TEXT3: 1414 | formattedString += formatText(rawData, numberOfLetters, 3); 1415 | break; 1416 | case NUMBER: 1417 | formattedString += formatNumber(rawData, numberOfLetters); 1418 | break; 1419 | case YEAR: 1420 | if (numberOfLetters <= 3) { 1421 | // Output a 2-digit year 1422 | var dataString = "" + rawData; 1423 | formattedString += dataString.substr(2, 2); 1424 | } else { 1425 | formattedString += formatNumber(rawData, numberOfLetters); 1426 | } 1427 | break; 1428 | case MONTH: 1429 | if (numberOfLetters >= 3) { 1430 | formattedString += formatText(monthNames[rawData], numberOfLetters, numberOfLetters); 1431 | } else { 1432 | // NB. Months returned by getMonth are zero-based 1433 | formattedString += formatNumber(rawData + 1, numberOfLetters); 1434 | } 1435 | break; 1436 | case TIMEZONE: 1437 | var isPositive = (rawData > 0); 1438 | // The following line looks like a mistake but isn't 1439 | // because of the way getTimezoneOffset measures. 1440 | var prefix = isPositive ? "-" : "+"; 1441 | var absData = Math.abs(rawData); 1442 | 1443 | // Hours 1444 | var hours = "" + Math.floor(absData / 60); 1445 | hours = padWithZeroes(hours, 2); 1446 | // Minutes 1447 | var minutes = "" + (absData % 60); 1448 | minutes = padWithZeroes(minutes, 2); 1449 | 1450 | formattedString += prefix + hours + minutes; 1451 | break; 1452 | } 1453 | } 1454 | searchString = searchString.substr(result.index + result[0].length); 1455 | } 1456 | return formattedString; 1457 | }; 1458 | })(); 1459 | 1460 | log4javascript.SimpleDateFormat = SimpleDateFormat; 1461 | 1462 | /* ---------------------------------------------------------------------- */ 1463 | // PatternLayout 1464 | 1465 | function PatternLayout(pattern) { 1466 | if (pattern) { 1467 | this.pattern = pattern; 1468 | } else { 1469 | this.pattern = PatternLayout.DEFAULT_CONVERSION_PATTERN; 1470 | } 1471 | this.customFields = []; 1472 | } 1473 | 1474 | PatternLayout.TTCC_CONVERSION_PATTERN = "%r %p %c - %m%n"; 1475 | PatternLayout.DEFAULT_CONVERSION_PATTERN = "%m%n"; 1476 | PatternLayout.ISO8601_DATEFORMAT = "yyyy-MM-dd HH:mm:ss,SSS"; 1477 | PatternLayout.DATETIME_DATEFORMAT = "dd MMM yyyy HH:mm:ss,SSS"; 1478 | PatternLayout.ABSOLUTETIME_DATEFORMAT = "HH:mm:ss,SSS"; 1479 | 1480 | PatternLayout.prototype = new Layout(); 1481 | 1482 | PatternLayout.prototype.format = function(loggingEvent) { 1483 | var regex = /%(-?[0-9]+)?(\.?[0-9]+)?([acdfmMnpr%])(\{([^\}]+)\})?|([^%]+)/; 1484 | var formattedString = ""; 1485 | var result; 1486 | var searchString = this.pattern; 1487 | 1488 | // Cannot use regex global flag since it doesn't work with exec in IE5 1489 | while ((result = regex.exec(searchString))) { 1490 | var matchedString = result[0]; 1491 | var padding = result[1]; 1492 | var truncation = result[2]; 1493 | var conversionCharacter = result[3]; 1494 | var specifier = result[5]; 1495 | var text = result[6]; 1496 | 1497 | // Check if the pattern matched was just normal text 1498 | if (text) { 1499 | formattedString += "" + text; 1500 | } else { 1501 | // Create a raw replacement string based on the conversion 1502 | // character and specifier 1503 | var replacement = ""; 1504 | switch(conversionCharacter) { 1505 | case "a": // Array of messages 1506 | case "m": // Message 1507 | var depth = 0; 1508 | if (specifier) { 1509 | depth = parseInt(specifier, 10); 1510 | if (isNaN(depth)) { 1511 | handleError("PatternLayout.format: invalid specifier '" + 1512 | specifier + "' for conversion character '" + conversionCharacter + 1513 | "' - should be a number"); 1514 | depth = 0; 1515 | } 1516 | } 1517 | var messages = (conversionCharacter === "a") ? loggingEvent.messages[0] : loggingEvent.messages; 1518 | for (var i = 0, len = messages.length; i < len; i++) { 1519 | if (i > 0 && (replacement.charAt(replacement.length - 1) !== " ")) { 1520 | replacement += " "; 1521 | } 1522 | if (depth === 0) { 1523 | replacement += messages[i]; 1524 | } else { 1525 | replacement += formatObjectExpansion(messages[i], depth); 1526 | } 1527 | } 1528 | break; 1529 | case "c": // Logger name 1530 | var loggerName = loggingEvent.logger.name; 1531 | if (specifier) { 1532 | var precision = parseInt(specifier, 10); 1533 | var loggerNameBits = loggingEvent.logger.name.split("."); 1534 | if (precision >= loggerNameBits.length) { 1535 | replacement = loggerName; 1536 | } else { 1537 | replacement = loggerNameBits.slice(loggerNameBits.length - precision).join("."); 1538 | } 1539 | } else { 1540 | replacement = loggerName; 1541 | } 1542 | break; 1543 | case "d": // Date 1544 | var dateFormat = PatternLayout.ISO8601_DATEFORMAT; 1545 | if (specifier) { 1546 | dateFormat = specifier; 1547 | // Pick up special cases 1548 | if (dateFormat == "ISO8601") { 1549 | dateFormat = PatternLayout.ISO8601_DATEFORMAT; 1550 | } else if (dateFormat == "ABSOLUTE") { 1551 | dateFormat = PatternLayout.ABSOLUTETIME_DATEFORMAT; 1552 | } else if (dateFormat == "DATE") { 1553 | dateFormat = PatternLayout.DATETIME_DATEFORMAT; 1554 | } 1555 | } 1556 | // Format the date 1557 | replacement = (new SimpleDateFormat(dateFormat)).format(loggingEvent.timeStamp); 1558 | break; 1559 | case "f": // Custom field 1560 | if (this.hasCustomFields()) { 1561 | var fieldIndex = 0; 1562 | if (specifier) { 1563 | fieldIndex = parseInt(specifier, 10); 1564 | if (isNaN(fieldIndex)) { 1565 | handleError("PatternLayout.format: invalid specifier '" + 1566 | specifier + "' for conversion character 'f' - should be a number"); 1567 | } else if (fieldIndex === 0) { 1568 | handleError("PatternLayout.format: invalid specifier '" + 1569 | specifier + "' for conversion character 'f' - must be greater than zero"); 1570 | } else if (fieldIndex > this.customFields.length) { 1571 | handleError("PatternLayout.format: invalid specifier '" + 1572 | specifier + "' for conversion character 'f' - there aren't that many custom fields"); 1573 | } else { 1574 | fieldIndex = fieldIndex - 1; 1575 | } 1576 | } 1577 | var val = this.customFields[fieldIndex].value; 1578 | if (typeof val == "function") { 1579 | val = val(this, loggingEvent); 1580 | } 1581 | replacement = val; 1582 | } 1583 | break; 1584 | case "n": // New line 1585 | replacement = newLine; 1586 | break; 1587 | case "p": // Level 1588 | replacement = loggingEvent.level.name; 1589 | break; 1590 | case "r": // Milliseconds since log4javascript startup 1591 | replacement = "" + loggingEvent.timeStamp.getDifference(applicationStartDate); 1592 | break; 1593 | case "%": // Literal % sign 1594 | replacement = "%"; 1595 | break; 1596 | default: 1597 | replacement = matchedString; 1598 | break; 1599 | } 1600 | // Format the replacement according to any padding or 1601 | // truncation specified 1602 | var l; 1603 | 1604 | // First, truncation 1605 | if (truncation) { 1606 | l = parseInt(truncation.substr(1), 10); 1607 | var strLen = replacement.length; 1608 | if (l < strLen) { 1609 | replacement = replacement.substring(strLen - l, strLen); 1610 | } 1611 | } 1612 | // Next, padding 1613 | if (padding) { 1614 | if (padding.charAt(0) == "-") { 1615 | l = parseInt(padding.substr(1), 10); 1616 | // Right pad with spaces 1617 | while (replacement.length < l) { 1618 | replacement += " "; 1619 | } 1620 | } else { 1621 | l = parseInt(padding, 10); 1622 | // Left pad with spaces 1623 | while (replacement.length < l) { 1624 | replacement = " " + replacement; 1625 | } 1626 | } 1627 | } 1628 | formattedString += replacement; 1629 | } 1630 | searchString = searchString.substr(result.index + result[0].length); 1631 | } 1632 | return formattedString; 1633 | }; 1634 | 1635 | PatternLayout.prototype.ignoresThrowable = function() { 1636 | return true; 1637 | }; 1638 | 1639 | PatternLayout.prototype.toString = function() { 1640 | return "PatternLayout"; 1641 | }; 1642 | 1643 | log4javascript.PatternLayout = PatternLayout; 1644 | 1645 | /* ---------------------------------------------------------------------- */ 1646 | // Main load 1647 | return log4javascript; 1648 | })(); 1649 | -------------------------------------------------------------------------------- /thirdparty/log4js/log4javascript_file_appender.js: -------------------------------------------------------------------------------- 1 | const Gio = imports.gi.Gio; 2 | function init(log4javascript) { 3 | function FileAppender() { 4 | this.init.apply(this, arguments); 5 | } 6 | 7 | FileAppender.prototype = new log4javascript.Appender(); 8 | FileAppender.prototype.layout = new log4javascript.NullLayout(); 9 | FileAppender.prototype.threshold = log4javascript.Level.DEBUG; 10 | FileAppender.prototype.init = function(filename) { 11 | this.filename = filename; 12 | }; 13 | 14 | FileAppender.prototype.write = function() { 15 | // On first invocation, open the file. 16 | // Then replace the `write` function with the actual implementation. 17 | let f = Gio.file_new_for_path(this.filename); 18 | try { 19 | f.delete(null); 20 | } catch(e) { 21 | // ignore, file presumably doesn't exist 22 | } 23 | let stream = f.create(Gio.FileCreateFlags.NONE, null); 24 | let write = function(str) { 25 | str = str + "\n"; 26 | stream.write(str, null); 27 | }; 28 | this.write = write; 29 | write.apply(this, arguments); 30 | }; 31 | FileAppender.prototype.append = function(loggingEvent) { 32 | this.write(FileAppender.getFormattedMessage(this, loggingEvent)); 33 | }; 34 | 35 | FileAppender.getFormattedMessage = function(appender, loggingEvent) { 36 | var layout = appender.getLayout(); 37 | var formattedMessage = layout.format(loggingEvent); 38 | if (layout.ignoresThrowable() && loggingEvent.exception) { 39 | formattedMessage += "\n " + loggingEvent.getThrowableStrRep(); 40 | } 41 | return formattedMessage; 42 | }; 43 | return FileAppender; 44 | } 45 | -------------------------------------------------------------------------------- /thirdparty/log4js/log4javascript_gjs_appender.js: -------------------------------------------------------------------------------- 1 | function init(log4javascript) { 2 | function GjsAppender() {}; 3 | 4 | GjsAppender.prototype = new log4javascript.Appender(); 5 | GjsAppender.prototype.layout = new log4javascript.NullLayout(); 6 | GjsAppender.prototype.threshold = log4javascript.Level.DEBUG; 7 | GjsAppender.prototype.append = function(loggingEvent) { 8 | var appender = this; 9 | var getFormattedMessage = function() { 10 | var layout = appender.getLayout(); 11 | var formattedMessage = layout.format(loggingEvent); 12 | if (layout.ignoresThrowable() && loggingEvent.exception) { 13 | formattedMessage += "\n " + loggingEvent.getThrowableStrRep(); 14 | } 15 | return formattedMessage; 16 | }; 17 | 18 | print(getFormattedMessage()); 19 | }; 20 | return GjsAppender; 21 | } 22 | -------------------------------------------------------------------------------- /utils.js: -------------------------------------------------------------------------------- 1 | const Meta = imports.gi.Meta; 2 | const Me = imports.misc.extensionUtils.getCurrentExtension(); 3 | const logging = Me.imports.logging; 4 | const logger = logging.getLogger('Gnomesome.Utils'); 5 | 6 | // of each added connection in `owner.bound_signals`, 7 | // for later cleanup in disconnect_tracked_signals(). 8 | // Also logs any exceptions that occur. 9 | function connect_and_track(owner, subject, name, cb, after) { 10 | if (typeof owner.bound_signals === 'undefined') { 11 | owner.bound_signals = []; 12 | } 13 | const method = after ? 'connect_after':'connect'; 14 | owner.bound_signals.push({ 15 | subject: subject, 16 | binding: subject[method](name, function() { 17 | const t = this; 18 | try { 19 | return cb.apply(t,arguments); 20 | } catch(e) { 21 | logger.error("Uncaught error in " + name + " signal handler: " + e + "\n" + e.stack); 22 | throw e; 23 | } 24 | }) 25 | }); 26 | } 27 | 28 | // Disconnect all tracked signals from the given object. 29 | // Used for reverting signals bound via `connect_and_track()` 30 | function disconnect_tracked_signals(owner, subject) { 31 | if (arguments.length > 1 && !subject) { 32 | throw new Error("[gnomesome] disconnect_tracked_signals called with null subject"); 33 | } 34 | let count = 0; 35 | for (let i = owner.bound_signals.length-1; i >= 0; i--) { 36 | const sig = owner.bound_signals[i]; 37 | if (subject == null || subject === sig.subject) { 38 | sig.subject.disconnect(sig.binding); 39 | // delete signal 40 | owner.bound_signals.splice(i, 1); 41 | count++; 42 | } 43 | } 44 | if (count>0) { 45 | logger.info("disconnected " + count + " listeners from " + 46 | owner + (subject == null ? "" : (" on " + subject))); 47 | } 48 | } 49 | 50 | var DisplayWrapper = { 51 | getScreen() { 52 | return global.screen || global.display; 53 | }, 54 | getWorkspaceManager() { 55 | return global.screen || global.workspace_manager; 56 | }, 57 | getMonitorManager() { 58 | return global.screen || Meta.MonitorManager.get(); 59 | }, 60 | getDisplay() { 61 | return global.display || global.screen; 62 | }, 63 | }; 64 | --------------------------------------------------------------------------------