├── .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 | [](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 |
36 |
--------------------------------------------------------------------------------
/icons/status/gnomesome-window-tile-full-symbolic.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
141 |
--------------------------------------------------------------------------------
/icons/status/gnomesome-window-tile-horizontal-symbolic.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
36 |
--------------------------------------------------------------------------------
/icons/status/gnomesome-window-tile-vertical-symbolic.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
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 |
--------------------------------------------------------------------------------