pax_global_header 0000666 0000000 0000000 00000000064 13572142376 0014524 g ustar 00root root 0000000 0000000 52 comment=9e4ba646fd259ba43589ab8c1c488f9dd32426d9
pixel-saver-1.20/ 0000775 0000000 0000000 00000000000 13572142376 0013705 5 ustar 00root root 0000000 0000000 pixel-saver-1.20/.gitignore 0000664 0000000 0000000 00000000020 13572142376 0015665 0 ustar 00root root 0000000 0000000 pixelsaver.zip
pixel-saver-1.20/LICENSE 0000664 0000000 0000000 00000002063 13572142376 0014713 0 ustar 00root root 0000000 0000000 The MIT License
Copyright (C) 2013 Amaury Séchet
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
pixel-saver-1.20/README.md 0000664 0000000 0000000 00000006204 13572142376 0015166 0 ustar 00root root 0000000 0000000 Pixel Saver
===========
Pixel Saver is an extension for Gnome Shell that merge the activity bar and the
title bar of maximized window. It is especially interesting for small screens,
but MOAR pixels for your apps is always good!
The extension has no configuration. Its behavior is made to mimic the one of
the title bar and settings affecting the title bar should reflect in
Pixel Saver. It **Just Works**!
For applications using the modern GTK header bar, there are no space savings,
but the application title is still displayed in the top panel to achieve a
uniform appearance.
 |
 |
The title bar is completely gone and integrated to the activity bar. |
It is largely inspired by [bios and mathematicalcoffee's Window Buttons Extension](https://github.com/mathematicalcoffee/Gnome-Shell-Window-Buttons-Extension) and [mathematicalcoffee's maximus extension](https://bitbucket.org/mathematicalcoffee/maximus-gnome-shell-extension) and some code come from there. You may want to check theses out, especially if you want something more configurable.
Installation
------------
Install it with one click from the [GNOME extension repository](https://extensions.gnome.org/extension/723/pixel-saver/).
You can also follow these simple instructions for manual installation :
```bash
# Clone repository
git clone https://github.com/deadalnix/pixel-saver.git
# Enter cloned directory
cd pixel-saver
# copy to extensions directory
cp -r pixel-saver@deadalnix.me -t ~/.local/share/gnome-shell/extensions
# activate
gnome-shell-extension-tool -e pixel-saver@deadalnix.me
```
For code changes to become effective, you might need to reload GNOME Shell
by pressing Alt + F2 and entering r .
### Dependencies
Pixel Saver depends on Xorg's xprop and xwininfo utilities. If not already
present on your system, these can be installed using:
* Debian/Ubuntu: `apt install x11-utils`
* Fedora/RHEL: `dnf install xorg-x11-utils`
* Arch: `pacman -S xorg-xprop`
Configuration
-------------
Don't be silly!
Support for older versions of gnome shell
-----------
If you use an older version of gnome shell, here are the versions of pixel saver that you should use.
| Gnome Shell | Latest recommended version |
|-------------|----------------------------------------------------------------------|
| 3.12 | [1.3](https://github.com/deadalnix/pixel-saver/releases/tag/1.3) |
| 3.14 | [1.5.1](https://github.com/deadalnix/pixel-saver/releases/tag/1.5.1) |
| 3.15 | [1.10](https://github.com/deadalnix/pixel-saver/releases/tag/1.10) |
Screenshots
-----------
If you want to see what the full desktop look like with this extension, you can check out what a [unmaximized window](https://raw.github.com/deadalnix/pixel-saver/master/unmax.png) looks like, as well as a [maximized one](https://raw.github.com/deadalnix/pixel-saver/master/max.png).
pixel-saver-1.20/icons.png 0000664 0000000 0000000 00000002550 13572142376 0015530 0 ustar 00root root 0000000 0000000 PNG
IHDR 4 @ { }PLTE
!# $%#%'$&(%)+(*,)+-*,-+-.,1303424635746857968979:8:;9<=;=?@>AB@BCACEBDFCEGDFHEHIGIJHLNKMOLOQNPROQSPUVTWYVY[X[]Z]_\^`]ac`bdadecefdfgeghfhjgkmjmolnpmqspsurwyvxzw}|~}~¾ÿɋ> bKGD H pHYs tIME
* tEXtComment Created with GIMPW LIDATx헋WPF5-Lą*I Sдo!,f퍻
)5t~p{AA4_"JzB6}m4]źf62Y2$4iPC
]gv7bläJo&ܥJiF;kL{'C5zw vG;~{X"}2z2P)-ĔqVT!NDž;Ló|_KXBڋw4ߑ6FY3Ɖ;i)3z2h7zmv@fEglqV"-1)xWi)'f
]9EsB;PSv ҈2$3~r@g;]Y^O)C9>~ֲ%ipqHK~6Ғ5F2A\Ai o4ٍf0u4$!N.!ё_kYU85I5mtM+(-r^itzVWJGCKpC/<"$^0*M'jښ;,6岶{]g^=p<`э5w:|hXjGD9QY#zX˖<1lh>=W=1?QRXiQRB
-J)Oi/*Ua 너ֲ?K;#5y1a@!y!pJ3MI{jTh0i)Q0~ =eI^)mس.;aӓNP7CIʻ%E0aG+'=g'f
iC0K:=m>xxpFsy b? _Fia IENDB` pixel-saver-1.20/max.png 0000664 0000000 0000000 00000017302 13572142376 0015203 0 ustar 00root root 0000000 0000000 PNG
IHDR `f PLTE
! !# " !# %""$"$%# '#"&(%'$&(%#*&')'%,()+(*,)+-*,-+'/+-.,)0-./-,02.0-*2./1.,3/02/130231-51342/624630845746852:67968973;79:87;=5<9:;96=97>:<=;9@<=?@>:B>@A?9CD;C?AB@BCA=EACEB?GCDFCEGDAIEFHEGIFHIGCKGIJHJKIEMIJLJENIAPJGOKLNKMOLDRMIQMOQNKSOPROFUOLTPNSUQSPSURIXROWSUVTKZUQYUVXUWYVS[WN]XY[XU]YZ\Y[]ZQ`[V_[T``]_\Ya]^`]Td^aaY`b_\d`^ceac`]eabdaXgadecefd`hdZjdfgeghfhigejl]mghjgjlikmjmolnpmqspsuruwtvxuwyvxzwy{xz|y}|~}~¾ÿ'L pHYs tIME $; tEXtComment Created with GIMPW 0IDATxp}f23 adn#; [l)"7JSŎx.*{GʕE?%N
Q 1?j^[{yO0~>3ϓ_Mgљו~_Z'XQQQT, H3~4{3.qb
W_6ilEY7:n@@wxY=z:>}t]^ַwƌ2wd<УD2Y\<+C]6oxe~^
oG']hb(yݺukZ`+Q2~ze.B4tUWU8}.!.{?P#(䊲!ihYĕ+-Ytو2ilP
]UcBڱG;AZ>vT?GfeY&Tˎu8Е6e}?w蠯2z{AC|/%_iҾo\Z>
+挹vYRݧ:j#F/[2o̘|7L;;N4tMO;^E褝ܟ3y&~cO'N[+j֟ 7cެiiŅ^z]/ LpqNvfnc;~x4=_ƨ[/\éÆsMW
JvzGA;o_gњnaBY 3S=/i{SOU\͵Ʃ/ԬhHݢ{:e}dv#-jBcQrakہn~3;u5qǽW-zY-j!/64-<ِ9ۙҪ\:47.%W^2y~ݐFЭWWvm:4YjG*y-G+zV}ÓB]}₩WïJgEG/6}cU+2T.۱3Ig_Ƿ tv'Е?Jl!SB_sږ\)nE-ʏZۦa[i/.٣F|Ġ_dGޏ&>χ9(,D;~b\'=HׄheX?N=oN,.;TTT3ТPU=locdGt6{ʧujoJμ}U_spFtݱKmyGR5^k)~=u;;7O<;'Gڻ0DU֘O|\9ӧ_3s֣~േw,%^x~ǂ(h:s[84}{AXPYԚkƭ=Q~VQƵOzj^~nh!@>sЦ3Tdƾ~2?NX3EGv6ydO`Yr}(ukY7ou0еE.*/osպE
tƲs-o@7~
mH}ZG7.a²T7l}3}
zaf?{wO++',Æ}mԨoM8e۷lEm(V}χQ♹7&lj:=<6eSeW}ֆqCR*zqjg٭; ;ƤV[;/?̜9(nxìh+fj|=;?Thux[Ff;?T"ƛSF:ay@gO`.ЃƧq3ТC}Vlu>#ptm
eFлAG_tN6' ?'Q|O4^H=1)Z0+s;=w(?
c~wְt'_2{7x͚0;C0(ڽ5*x\z.UqOf{7u5K⻘jZ%J,uwvUejn\[\vGKܜ_ƿit4P>OkŻo5'iǪZx^eǦ:-_d9U;Wr]v$Г
ϴy
:r21|B%V
]^ҳW/FG9jGIQA'fֽ5wᏛ{pnS>[\8nox[5QCxdKHGSBg'(բβ[wtg(\5dSGs9>Vo\Vd&U8l2wƛ&+j#oş'«NAKO}(z' ;Tf_q|xN},*@$B'mC_tRN
oXA(>uQ.ܾ
h*V}ߎ3\=sۜ9^>zj}SţѼ414dpev3Jj>3#qt`::nui+kVv$Щqi-EXe{/P]UUНԢ.kx6x7Wm,i64
tfZ6~8va鰯S.=gΜ77ׅ{ߴtfr/#®yKB4+3[2~<|Q5^UmUXJ^4sIyn,tȱ%˳
F\5|NϦ@79ҳ^=?;qլ'ov
7q4cQCW +R?Z8uW8VڱpSТU!%VB. t{W
B;?Ihw@O6/r6ܝۄ5my@wRņA~ͩǻ=> -
.wd&_<^w~?mpK.qwyw-CIA/{hږhufr;;h;^ua_d፳[wpg9USϋlO[/UTv52|f3t*BS TgUQ7;ae^UrW1gfn]mT4cá8g}X?5F2
Bce}FQ"DŽ/g-Eg%
KSɩHwcsQ2OK>ۏu kW8T9t]\&N*ieIuI哾qhLA)vn'?3o2od"*I~>B]zñJg|]>g,?,5^2k1?ﯿQ:J.y+/3+|8Ι?{\ţ
j^?JuKvϥ˞0v\wDo]pq5Aq|Wr#.hJ'_A~xǝ;\ţk
p]~7_&Orw`5S.5/Ɏ44
_F9
-'Y.%t~vʞ;
|9g%qߨҙ{oO7k.5ͭygɚcϝիJj{\^.nz]H+ջO6Y_yLsM.K[OÞ[pn՛M-[EM/Ѕ=aĈ-qƽ}ElsW=?QO,ihyO.eQeEQu7W<
{n!йk7H<`ѵύŴ&z$pק9..ԟú侂E9-+y0=6)<;ZWw[ڼvl|c캂E鿎1ɢ[6Jy-hd\&ߌZ][{`Q~NcF'r5YYՉD4%k;S(.n(މ{w-Qtּ{hkzO>2D(J6(?1ѩt@wOA7Txo$@+
dg*ڤڂE9qlZ
?ʵ}pG@v浩kcү8XpclEE-:O^/KkKFѺɩ[kDQŬV2<<:wS"VϏTisS[2kƦW7YIuKjW>!@tt[SE:7{=w-X8'*{Pma@A{gUvdWo^{ݶ[nH/&1?jyG>jeB.NC\"?
t4su@t@>w#_Eh @h @h @ h h @h @h @ @ h h @h @h @ @ h h @h @h @ @ h @h @h @h @ @ h @h @h @h @ h h @h @h @ @ h h @h @h @ @ h h @h @h @ @ h @h @h @h @ @ h @h @h @h @ h h @h @h @ @ h h @h v h @h @h @h @ h h @h @h @ @ h h @h @h @ @ h h @h @h @ @ h @h @h @h @ h h @h @h @h @ h h @h @h @ @ h h @h @h @ @ h h @h @h @ @ h @h @h @h @ h h @h @h @h @ h h @h @h @ @ h h @h @h @ @ h h @h @h @ @ h @h @h @h @ h h @h @h @! h @h @h @ @ h @h @h @h @ h h @h @h @ @ h h @h @h @ @ h h @h @h @ @ h @h @h @h @ @ h @h @h @h @ h h @h @h @ @ h h @h @h @ @ h h @h @h @ @ h @h @h @h @ @ h @h @h @h @ h h @h @h @ @ h h @h @h @ @ h h @h @h @ @ h @h @h @h @ h @h @ @ h h @h @h @ @ h @h @h @h @ h h @h @h @h @ h h @h @h @ @ h h @h @h @ @ h h @h @h @ @ h @h @h @h @ h h @h @h @h @rRǰ-Z IENDB` pixel-saver-1.20/pack.sh 0000775 0000000 0000000 00000000105 13572142376 0015156 0 ustar 00root root 0000000 0000000 #!/bin/bash
cd pixel-saver@deadalnix.me
zip ../pixelsaver.zip -r *
pixel-saver-1.20/pixel-saver@deadalnix.me/ 0000775 0000000 0000000 00000000000 13572142376 0020516 5 ustar 00root root 0000000 0000000 pixel-saver-1.20/pixel-saver@deadalnix.me/app_menu.js 0000664 0000000 0000000 00000012021 13572142376 0022654 0 ustar 00root root 0000000 0000000 const Lang = imports.lang;
const Main = imports.ui.main;
const Mainloop = imports.mainloop;
const Shell = imports.gi.Shell;
const St = imports.gi.St;
const Tweener = imports.ui.tweener;
const ExtensionUtils = imports.misc.extensionUtils;
const Me = ExtensionUtils.getCurrentExtension();
const Util = Me.imports.util;
function LOG(message) {
// log("[pixel-saver]: " + message);
}
function WARN(message) {
log("[pixel-saver]: " + message);
}
let appMenu = null;
/**
* AppMenu synchronization
*/
function updateAppMenu() {
let win = global.display.focus_window;
if (!win) {
return false;
}
let title = win.title;
// Not the topmost maximized window.
if (win !== Util.getWindow()) {
let app = Shell.WindowTracker.get_default().get_window_app(win);
title = app.get_name();
}
if (title.length > 64) {
title = title.substr(0, 62) + '...';
}
title = title.replace(/\n/g, " ");
LOG('Override title ' + title);
appMenu._label.set_text(title);
tooltip.text = title;
return false;
}
/**
* Track the focused window's title
*/
let activeWindow = null;
let awCallbackID = 0;
function changeActiveWindow(win) {
if (win === activeWindow) {
return;
}
if (activeWindow) {
activeWindow.disconnect(awCallbackID);
}
activeWindow = win;
if (win) {
awCallbackID = win.connect('notify::title', updateAppMenu);
updateAppMenu();
}
}
/**
* Focus change
*/
function onFocusChange() {
let input_mode_check = (global.stage_input_mode === undefined)
? true
: global.stage_input_mode == Shell.StageInputMode.FOCUSED;
if (!Shell.WindowTracker.get_default().focus_app && input_mode_check) {
// If the app has just lost focus to the panel, pretend
// nothing happened; otherwise you can't keynav to the
// app menu.
return false;
}
changeActiveWindow(global.display.focus_window);
return false;
}
/**
* tooltip
*/
let tooltip = null;
let showTooltip = false;
let SHOW_DELAY = 350;
let SHOW_DURATION = 0.15;
let HIDE_DURATION = 0.1;
let tooltipDelayCallbackID = 0;
let menuCallbackID = 0;
function resetMenuCallback() {
if (menuCallbackID) {
appMenu.menu.disconnect(menuCallbackID);
menuCallbackID = 0;
}
}
function onAppMenuHover(actor) {
let hover = actor.get_hover();
if (showTooltip === hover) {
return false;
}
// We are not in the right state, let's fix that.
showTooltip = hover;
if (showTooltip) {
tooltipDelayCallbackID = Mainloop.timeout_add(SHOW_DELAY, function() {
if (!showTooltip) {
WARN('showTooltip is false and delay callback ran.');
}
// Something wants us to stop.
if (tooltipDelayCallbackID === 0) {
return false;
}
let label = appMenu._label;
if (label == null || !label.get_clutter_text().get_layout().is_ellipsized()) {
// Do not need to hide.
tooltipDelayCallbackID = 0;
return false;
}
Main.uiGroup.add_actor(tooltip);
resetMenuCallback();
menuCallbackID = appMenu.menu.connect('open-state-changed', function(menu, open) {
if (open) {
Main.uiGroup.remove_actor(tooltip);
} else {
Main.uiGroup.add_actor(tooltip);
}
});
[px, py] = Main.panel.actor.get_transformed_position();
[bx, by] = label.get_transformed_position();
[w, h] = label.get_transformed_size();
let y = py + Main.panel.actor.get_height() + 3;
let x = bx - Math.round((tooltip.get_width() - w)/2);
tooltip.opacity = 0;
tooltip.set_position(x, y);
LOG('show title tooltip');
Tweener.removeTweens(tooltip);
Tweener.addTween(tooltip, {
opacity: 255,
time: SHOW_DURATION,
transition: 'easeOutQuad',
});
return false;
});
} else if (tooltipDelayCallbackID > 0) {
// If the event ran, then we hide.
LOG('hide title tooltip');
resetMenuCallback();
Tweener.removeTweens(tooltip);
Tweener.addTween(tooltip, {
opacity: 0,
time: HIDE_DURATION,
transition: 'easeOutQuad',
onComplete: function() {
Main.uiGroup.remove_actor(tooltip);
}
});
tooltipDelayCallbackID = 0;
}
return false;
}
/**
* Subextension hooks
*/
function init() {}
let wmCallbackIDs = [];
let focusCallbackID = 0;
let tooltipCallbackID = 0;
function enable() {
appMenu = Main.panel.statusArea.appMenu;
tooltip = new St.Label({
style_class: 'tooltip dash-label',
text: '',
opacity: 0
});
wmCallbackIDs = wmCallbackIDs.concat(Util.onSizeChange(updateAppMenu));
focusCallbackID = global.display.connect('notify::focus-window', onFocusChange);
tooltipCallbackID = appMenu.actor.connect('notify::hover', onAppMenuHover);
}
function disable() {
wmCallbackIDs.forEach(function(id) {
global.window_manager.disconnect(id);
});
wmCallbackIDs = [];
global.display.disconnect(focusCallbackID);
focusCallbackID = 0;
appMenu.actor.disconnect(tooltipCallbackID);
tooltipCallbackID = 0;
if (activeWindow) {
activeWindow.disconnect(awCallbackID);
awCallbackID = 0;
activeWindow = null;
}
if (tooltipDelayCallbackID) {
Mainloop.source_remove(tooltipDelayCallbackID);
tooltipDelayCallbackID = 0;
}
resetMenuCallback();
tooltip.destroy();
tooltip = null;
}
pixel-saver-1.20/pixel-saver@deadalnix.me/buttons.js 0000664 0000000 0000000 00000013725 13572142376 0022562 0 ustar 00root root 0000000 0000000 const GLib = imports.gi.GLib;
const Gio = imports.gi.Gio;
const Gtk = imports.gi.Gtk;
const Main = imports.ui.main;
const Mainloop = imports.mainloop;
const Meta = imports.gi.Meta;
const St = imports.gi.St;
const ExtensionUtils = imports.misc.extensionUtils;
const Me = ExtensionUtils.getCurrentExtension();
const Util = Me.imports.util;
function LOG(message) {
// log("[pixel-saver]: " + message);
}
function WARN(message) {
log("[pixel-saver]: " + message);
}
/**
* Buttons
*/
const DCONF_META_PATH = 'org.gnome.desktop.wm.preferences';
let actors = [], boxes = [];
function createButtons() {
// Ensure we do not create buttons twice.
destroyButtons();
actors = [
new St.Bin({ style_class: 'box-bin'}),
new St.Bin({ style_class: 'box-bin'})
];
boxes = [
new St.BoxLayout({ style_class: 'button-box' }),
new St.BoxLayout({ style_class: 'button-box' })
];
actors.forEach(function(actor, i) {
actor.add_actor(boxes[i]);
});
let order = new Gio.Settings({schema_id: DCONF_META_PATH}).get_string('button-layout');
LOG('Buttons layout : ' + order);
if (order.indexOf(':') == -1 && order.length <= 1) {
LOG('Button layout empty')
return
}
let orders = order.replace(/ /g, '').split(':');
orders[0] = orders[0].split(',');
// Check if it's actually exists, if not then create it
if(typeof orders[1] == 'undefined') orders[1] = '';
orders[1] = orders[1].split(',');
const callbacks = {
minimize : minimize,
maximize : maximize,
close : close
};
for (let bi = 0; bi < boxes.length; ++bi) {
let order = orders[bi],
box = boxes[bi];
for (let i = 0; i < order.length; ++i) {
if (!order[i]) {
continue;
}
if (!callbacks[order[i]]) {
// Skip if the button's name is not right...
WARN("\'%s\' is not a valid button.".format(order[i]));
continue;
}
let button = new St.Button({
style_class: order[i] + ' window-button',
track_hover: true
});
button.connect('button-release-event', leftclick(callbacks[order[i]]));
box.add(button);
}
}
Mainloop.idle_add(function () {
// 1 for activity button and -1 for the menu
if (boxes[0].get_children().length) {
Main.panel._leftBox.insert_child_at_index(actors[0], 1);
}
if (boxes[1].get_children().length) {
Main.panel._rightBox.insert_child_at_index(actors[1], Main.panel._rightBox.get_children().length - 1);
}
updateVisibility();
return false;
});
}
function destroyButtons() {
actors.forEach(function(actor, i) {
actor.destroy();
boxes[i].destroy();
});
actors = [];
boxes = [];
}
/**
* Buttons actions
*/
function leftclick(callback) {
return function(actor, event) {
if (event.get_button() !== 1) {
return null;
}
return callback(actor, event);
}
}
function minimize() {
let win = Util.getWindow();
if (!win || win.minimized) {
WARN('impossible to minimize');
return;
}
win.minimize();
}
function maximize() {
let win = Util.getWindow();
if (!win) {
WARN('impossible to maximize');
return;
}
const MAXIMIZED = Meta.MaximizeFlags.BOTH;
if (win.get_maximized() === MAXIMIZED) {
win.unmaximize(MAXIMIZED);
} else {
WARN('window shoud already be maximized');
win.maximize(MAXIMIZED);
}
win.activate(global.get_current_time());
}
function close() {
let win = Util.getWindow();
if (!win) {
WARN('impossible to close');
return;
}
win.delete(global.get_current_time());
}
/**
* Theming
*/
let activeCSS = false;
function loadTheme() {
let theme = Gtk.Settings.get_default().gtk_theme_name,
cssPath = GLib.build_filenamev([extensionPath, 'themes', theme, 'style.css']);
LOG('Load theme ' + theme);
if (!GLib.file_test(cssPath, GLib.FileTest.EXISTS)) {
cssPath = GLib.build_filenamev([extensionPath, 'themes/default/style.css']);
}
if (cssPath === activeCSS) {
return;
}
unloadTheme();
// Load the new style
let cssFile = Gio.file_new_for_path(cssPath);
St.ThemeContext.get_for_stage(global.stage).get_theme().load_stylesheet(cssFile);
// Force style update.
actors.forEach(function(actor) {
actor.grab_key_focus();
});
activeCSS = cssPath;
}
function unloadTheme() {
if (activeCSS) {
LOG('Unload ' + activeCSS);
let cssFile = Gio.file_new_for_path(activeCSS);
St.ThemeContext.get_for_stage(global.stage).get_theme().unload_stylesheet(cssFile);
activeCSS = false;
}
}
/**
* callbacks
*/
function updateVisibility() {
// If we have a window to control, then we show the buttons.
let visible = !Main.overview.visible;
if (visible) {
visible = false;
let win = Util.getWindow();
if (win) {
visible = !win.decorated;
}
}
actors.forEach(function(actor, i) {
if (!boxes[i].get_children().length) {
return;
}
if (visible) {
actor.show();
} else {
actor.hide();
}
});
return false;
}
/**
* Subextension hooks
*/
let extensionPath;
function init(extensionMeta) {
extensionPath = extensionMeta.path;
}
let wmCallbackIDs = [];
let overviewCallbackIDs = [];
let themeCallbackID = 0;
function enable() {
loadTheme();
createButtons();
overviewCallbackIDs.push(Main.overview.connect('showing', updateVisibility));
overviewCallbackIDs.push(Main.overview.connect('hidden', updateVisibility));
let wm = global.window_manager;
wmCallbackIDs.push(wm.connect('switch-workspace', updateVisibility));
wmCallbackIDs.push(wm.connect('map', updateVisibility));
wmCallbackIDs.push(wm.connect('minimize', updateVisibility));
wmCallbackIDs.push(wm.connect('unminimize', updateVisibility));
wmCallbackIDs = wmCallbackIDs.concat(Util.onSizeChange(updateVisibility));
themeCallbackID = Gtk.Settings.get_default().connect('notify::gtk-theme-name', loadTheme);
}
function disable() {
wmCallbackIDs.forEach(function(id) {
global.window_manager.disconnect(id);
});
overviewCallbackIDs.forEach(function(id) {
Main.overview.disconnect(id);
});
wmCallbackIDs = [];
overviewCallbackIDs = [];
if (themeCallbackID !== 0) {
Gtk.Settings.get_default().disconnect(0);
themeCallbackID = 0;
}
destroyButtons();
unloadTheme();
}
pixel-saver-1.20/pixel-saver@deadalnix.me/decoration.js 0000664 0000000 0000000 00000032750 13572142376 0023212 0 ustar 00root root 0000000 0000000 const GLib = imports.gi.GLib;
const Mainloop = imports.mainloop;
const Meta = imports.gi.Meta;
const Util = imports.misc.util;
const ExtensionUtils = imports.misc.extensionUtils;
const Me = ExtensionUtils.getCurrentExtension();
const Utils = Me.imports.util;
function LOG(message) {
// log("[pixel-saver]: " + message);
}
function WARN(message) {
log("[pixel-saver]: " + message);
}
/**
* Guesses the X ID of a window.
*
* It is often in the window's title, being `"0x%x %10s".format(XID, window.title)`.
* (See `mutter/src/core/window-props.c`).
*
* If we couldn't find it there, we use `win`'s actor, `win.get_compositor_private()`.
* The actor's `x-window` property is the X ID of the window *actor*'s frame
* (as opposed to the window itself).
*
* However, the child window of the window actor is the window itself, so by
* using `xwininfo -children -id [actor's XID]` we can attempt to deduce the
* window's X ID.
*
* It is not always foolproof, but works good enough for now.
*
* @param {Meta.Window} win - the window to guess the XID of. You wil get better
* success if the window's actor (`win.get_compositor_private()`) exists.
*/
function guessWindowXID(win) {
// We cache the result so we don't need to redetect.
if (win._pixelSaverWindowID) {
return win._pixelSaverWindowID;
}
/**
* If window title has non-utf8 characters, get_description() complains
* "Failed to convert UTF-8 string to JS string: Invalid byte sequence in conversion input",
* event though get_title() works.
*/
try {
let m = win.get_description().match(/0x[0-9a-f]+/);
if (m && m[0]) {
return win._pixelSaverWindowID = m[0];
}
} catch (err) { }
// use xwininfo, take first child.
let act = win.get_compositor_private();
let xwindow = act && act['x-window'];
if (xwindow) {
let xwininfo = GLib.spawn_command_line_sync('xwininfo -children -id 0x%x'.format(xwindow));
if (xwininfo[0]) {
let str = xwininfo[1].toString();
/**
* The X ID of the window is the one preceding the target window's title.
* This is to handle cases where the window has no frame and so
* act['x-window'] is actually the X ID we want, not the child.
*/
let regexp = new RegExp('(0x[0-9a-f]+) +"%s"'.format(win.title));
let m = str.match(regexp);
if (m && m[1]) {
return win._pixelSaverWindowID = m[1];
}
// Otherwise, just grab the child and hope for the best
m = str.split(/child(?:ren)?:/)[1].match(/0x[0-9a-f]+/);
if (m && m[0]) {
return win._pixelSaverWindowID = m[0];
}
}
}
// Try enumerating all available windows and match the title. Note that this
// may be necessary if the title contains special characters and `x-window`
// is not available.
let result = GLib.spawn_command_line_sync('xprop -root _NET_CLIENT_LIST');
LOG('xprop -root _NET_CLIENT_LIST')
if (result[0]) {
let str = result[1].toString();
// Get the list of window IDs.
let windowList = str.match(/0x[0-9a-f]+/g);
// For each window ID, check if the title matches the desired title.
for (var i = 0; i < windowList.length; ++i) {
let cmd = 'xprop -id "' + windowList[i] + '" _NET_WM_NAME _PIXEL_SAVER_ORIGINAL_STATE';
let result = GLib.spawn_command_line_sync(cmd);
LOG(cmd);
if (result[0]) {
let output = result[1].toString();
let isManaged = output.indexOf("_PIXEL_SAVER_ORIGINAL_STATE(CARDINAL)") > -1;
if (isManaged) {
continue;
}
let title = output.match(/_NET_WM_NAME(\(\w+\))? = "(([^\\"]|\\"|\\\\)*)"/);
LOG("Title of XID %s is \"%s\".".format(windowList[i], title[2]));
// Is this our guy?
if (title && title[2] == win.title) {
return windowList[i];
}
}
}
}
// debugging for when people find bugs..
WARN("Could not find XID for window with title %s".format(win.title));
return null;
}
const WindowState = {
DEFAULT: 'default',
HIDE_TITLEBAR: 'hide_titlebar',
UNDECORATED: 'undecorated',
UNKNOWN: 'unknown'
}
/**
* Get the value of _MOTIF_WM_HINTS before
* pixel saver did its magic.
*
* @param {Meta.Window} win - the window to check the property
*/
function getOriginalState(win) {
if (win._pixelSaverOriginalState !== undefined) {
return win._pixelSaverOriginalState;
}
if (!win.decorated) {
return win._pixelSaverOriginalState = WindowState.UNDECORATED;
}
let id = guessWindowXID(win);
let cmd = 'xprop -id ' + id;
LOG(cmd);
let xprops = GLib.spawn_command_line_sync(cmd);
if (!xprops[0]) {
WARN("xprop failed for " + win.title + " with id " + id);
return win._pixelSaverOriginalState = State.UNKNOWN;
}
let str = imports.byteArray.toString(xprops[1]);
let m = str.match(/^_PIXEL_SAVER_ORIGINAL_STATE\(CARDINAL\) = ([0-9]+)$/m);
log(m);
if (m) {
return win._pixelSaverOriginalState = !!m[1]
? WindowState.HIDE_TITLEBAR
: WindowState.DEFAULT;
}
m = str.match(/^_MOTIF_WM_HINTS(\(CARDINAL\))? = [0-9], [0-9]$/m);
log(m);
if (m) {
let state = !!m[1];
cmd = ['xprop', '-id', id,
'-f', '_PIXEL_SAVER_ORIGINAL_STATE', '32c',
'-set', '_PIXEL_SAVER_ORIGINAL_STATE',
(state ? '0x1' : '0x0')];
LOG(cmd.join(' '));
Util.spawn(cmd);
return win._pixelSaverOriginalState = state
? WindowState.HIDE_TITLEBAR
: WindowState.DEFAULT;
}
WARN("Can't find original state for " + win.title + " with id " + id);
// GTK uses the _MOTIF_WM_HINTS atom to indicate that the
// title bar should be hidden when maximized. If we can't find this atom, the
// window uses the default behavior
return win._pixelSaverOriginalState = WindowState.DEFAULT;
}
/**
* Tells the window manager to hide the titlebar on maximised windows.
*
* Does this by setting the _MOTIF_WM_HINTS hint - means
* I can do it once and forget about it, rather than tracking maximize/unmaximize
* events.
*
* **Caveat**: doesn't work with Ubuntu's Ambiance and Radiance window themes -
* my guess is they don't respect or implement this property.
*
* I don't know how to read the initial value, so I'm not sure how to resore it.
*
* @param {Meta.Window} win - window to set the HIDE_TITLEBAR_WHEN_MAXIMIZED property of.
* @param {boolean} hide - whether to hide the titlebar or not.
*/
function setHideTitlebar(win, hide) {
LOG('setHideTitlebar: ' + win.get_title() + ': ' + hide);
// Make sure we save the state before altering it.
getOriginalState(win);
/**
* Undecorate with xprop. Use _MOTIF_WM_HINTS instead of _GTK_HIDE_TITLEBAR_WHEN_MAXIMIZED because
* mutter deprecated it in 3.32 (https://gitlab.gnome.org/GNOME/mutter/merge_requests/221)
*/
let cmd = ['xprop', '-id', guessWindowXID(win),
'-f', '_MOTIF_WM_HINTS', '32c',
'-set', '_MOTIF_WM_HINTS',
(hide ? '0x2, 0x0, 0x0, 0x0, 0x0' : '0x2, 0x0, 0x1, 0x0, 0x0')];
LOG(cmd.join(' '));
// Run xprop
let success, pid;
[success, pid] = GLib.spawn_async(
null,
cmd,
null,
GLib.SpawnFlags.SEARCH_PATH | GLib.SpawnFlags.DO_NOT_REAP_CHILD,
null);
// After xprop completes, unmaximize and remaximize any window
// that is already maximized. It seems that setting the xprop on
// a window that is already maximized doesn't actually take
// effect immediately but it needs a focuse change or other
// action to force a relayout. Doing unmaximize and maximize
// here seems to be an uninvasive way to handle this. This needs
// to happen _after_ xprop completes.
GLib.child_watch_add(GLib.PRIORITY_DEFAULT, pid, function () {
const MAXIMIZED = Meta.MaximizeFlags.BOTH;
let flags = win.get_maximized();
if (flags == MAXIMIZED) {
win.unmaximize(MAXIMIZED);
win.maximize(MAXIMIZED);
}
});
}
/**** Callbacks ****/
/**
* Callback when a window is added in any of the workspaces.
* This includes a window switching to another workspace.
*
* If it is a window we already know about, we do nothing.
*
* Otherwise, we activate the hide title on maximize feature.
*
* @param {Meta.Window} win - the window that was added.
*
* @see undecorate
*/
function onWindowAdded(ws, win, retry) {
if (win.window_type === Meta.WindowType.DESKTOP) {
return false;
}
// If the window is simply switching workspaces, it will trigger a
// window-added signal. We don't want to reprocess it then because we already
// have.
if (win._pixelSaverOriginalState !== undefined) {
return false;
}
/**
* Newly-created windows are added to the workspace before
* the compositor knows about them: get_compositor_private() is null.
* Additionally things like .get_maximized() aren't properly done yet.
* (see workspace.js _doAddWindow)
*/
if (!win.get_compositor_private()) {
retry = (retry !== undefined) ? retry : 0;
if (retry > 3) {
return false;
}
Mainloop.idle_add(function () {
onWindowAdded(ws, win, retry + 1);
return false;
});
return false;
}
retry = 3;
Mainloop.idle_add(function () {
let id = guessWindowXID(win);
if (!id) {
if (--retry) {
return true;
}
WARN("Finding XID for window %s failed".format(win.title));
return false;
}
LOG('onWindowAdded: ' + win.get_title());
changeTitleBar(win);
return false;
});
return false;
}
let workspaces = [];
/**
* Call if when a window is changed.
* If the window is maximized, hide the title bar, otherwise show it.
*
* @param {Meta.Window} win the window that changed
*/
function onWindowChanged(win) {
if (win.window_type === Meta.WindowType.DESKTOP) {
return false;
}
changeTitleBar(win);
return false;
}
/**
* Callback for whenever focus changes.
*/
function onChangeFocus() {
LOG('Focus changed');
let focusWindow = global.display.focus_window;
if(!focusWindow) return;
Mainloop.idle_add(function () { return onWindowChanged(focusWindow); });
}
/**
* Callback whenever window changes.
*/
function onChangeWindowSize() {
LOG('Window size changed');
let focusWindow = global.display.focus_window;
if(!focusWindow) return;
Mainloop.idle_add(function () { return onWindowChanged(focusWindow); });
}
/**
* Callback whenever the number of workspaces changes.
*
* We ensure that we are listening to the 'window-added' signal on each of
* the workspaces.
*
* @see onWindowAdded
*/
function onChangeNWorkspaces() {
cleanWorkspaces();
let i = Utils.DisplayWrapper.getWorkspaceManager().n_workspaces;
while (i--) {
let ws = Utils.DisplayWrapper.getWorkspaceManager().get_workspace_by_index(i);
workspaces.push(ws);
// we need to add a Mainloop.idle_add, or else in onWindowAdded the
// window's maximized state is not correct yet.
ws._pixelSaverWindowAddedId = ws.connect('window-added', function (ws, win) {
Mainloop.idle_add(function () { return onWindowAdded(ws, win); });
});
}
return false;
}
/**
* Utilities
*/
function cleanWorkspaces() {
// disconnect window-added from workspaces
workspaces.forEach(function(ws) {
ws.disconnect(ws._pixelSaverWindowAddedId);
delete ws._pixelSaverWindowAddedId;
});
workspaces = [];
}
function forEachWindow(callback) {
global.get_window_actors()
.map(function (w) { return w.meta_window; })
.filter(function(w) { return w.window_type !== Meta.WindowType.DESKTOP; })
.forEach(callback);
}
function changeTitleBar(win) {
if (ignoreWindow(win)) return;
if (win.get_maximized()) {
LOG('Hiding titlebar');
hideTitlebar(win);
} else {
LOG('Showing titlebar');
showTitlebar(win);
}
}
function showTitlebar(win) {
if (!win._decorationOFF) return;
win._decorationOFF = false;
setHideTitlebar(win, false);
}
function hideTitlebar(win) {
if (win._decorationOFF) return;
win._decorationOFF = true;
setHideTitlebar(win, true);
}
/**
* Decides if the window should be ignored.
* It should be ignored if the window didn't have a titlebar in the first place.
* @param {Meta.Window} win The window to check
*/
function ignoreWindow(win) {
let state = getOriginalState(win);
let ignore = (state !== WindowState.DEFAULT)
return ignore;
}
/**
* Subextension hooks
*/
function init() {}
let changeWorkspaceID = 0;
let globWindowManagerID = 0;
let globDisplayID = 0;
function enable() {
// Connect events
changeWorkspaceID = Utils.DisplayWrapper.getWorkspaceManager().connect('notify::n-workspaces', onChangeNWorkspaces);
globWindowManagerID = Utils.DisplayWrapper.getWindowManager().connect('size-change', onChangeWindowSize);
globDisplayID = Utils.DisplayWrapper.getDisplay().connect('notify::focus-window', onChangeFocus);
/**
* Go through already-maximised windows & undecorate.
* This needs a delay as the window list is not yet loaded
* when the extension is loaded.
* Also, connect up the 'window-added' event.
* Note that we do not connect this before the onMaximise loop
* because when one restarts the gnome-shell, window-added gets
* fired for every currently-existing window, and then
* these windows will have onMaximise called twice on them.
*/
Mainloop.idle_add(function () {
forEachWindow(function(win) {
onWindowAdded(null, win);
onWindowChanged(win);
});
onChangeNWorkspaces();
return false;
});
}
function disable() {
if (changeWorkspaceID) {
Utils.DisplayWrapper.getWorkspaceManager().disconnect(changeWorkspaceID);
changeWorkspaceID = 0;
}
if(globWindowManagerID) {
Utils.DisplayWrapper.getWindowManager().disconnect(globWindowManagerID);
globWindowManagerID = 0;
}
if(globDisplayID) {
Utils.DisplayWrapper.getDisplay().disconnect(globDisplayID);
globDisplayID = 0;
}
cleanWorkspaces();
forEachWindow(function(win) {
let state = getOriginalState(win);
LOG('stopUndecorating: ' + win.title + ' original=' + state);
if (state == WindowState.DEFAULT) {
setHideTitlebar(win, false);
}
delete win._pixelSaverOriginalState;
delete win._decorationOFF;
});
}
pixel-saver-1.20/pixel-saver@deadalnix.me/extension.js 0000664 0000000 0000000 00000005074 13572142376 0023076 0 ustar 00root root 0000000 0000000 /**
* @overview
* Pixel Saver v1.0
* Amaury SECHET
* Other contributors:
* - Amy Chan
* Sept-- 2013.
*
* ## Help! It didn't work/I found a bug!
*
* This extension is based on work by Amy Chan, namely maximus[1] and Window Buttons[2].
*
* 1. Make sure you can *reproduce* the bug reliably.
* 2. Do 'Ctrl + F2' and 'lg' and see if there are any errors produced by Maximus,
* both in the 'Errors' window *and* the 'Extensions' > 'Maximus' > 'Show Errors'
* tab (the 'Show Errors' is in GNOME 3.4+ only I think).
* 3. Disable all your extensions except Maximus and see if you can still reproduce
* the bug. If so, mention this.
* 4. If you can't reproduce th bug with all extensions but Maximus disabled, then
* gradually enable your extensions one-by-one until you work out which one(s)
* together cause the bug, and mention these.
* 5. Open a new issue at [4].
* 6. Include how you can reproduce the bug and any relevant information from 2--4.
* 7. Also include:
* - your version of the extension (in metadata.json)
* - list of all your installed extensions (including disabled ones, as
* this is no guarantee they won't interfere with other extensions)
* - your version of GNOME-shell (gnome-shell --version).
* 8. I'll try get back to you with a fix.
* (Brownie points: open a terminal, do `gnome-shell --replace` and reproduce the
* bug. Include any errors that pop up in this terminal.)
*
* ## Note:
* It's actually possible to get the undecorate-on-maximise behaviour without
* needing this extension. See the link [5] and in particular, the bit on editing
* your metacity theme metacity-theme-3.xml. ("Method 2: editing the theme").
*
* ## References:
* [1]:https://launchpad.net/maximus
* [2]:https://extensions.gnome.org/extension/59/status-title-bar/
* [3]:https://bitbucket.org/mathematicalcoffee/window-options-gnome-shell-extension
* [4]:https://bitbucket.org/mathematicalcoffee/maximus-gnome-shell-extension/issues
* [5]:http://www.webupd8.org/2011/05/how-to-remove-maximized-windows.html
*
*/
const ExtensionUtils = imports.misc.extensionUtils;
const Me = ExtensionUtils.getCurrentExtension();
const Decoration = Me.imports.decoration;
const Buttons = Me.imports.buttons;
const AppMenu = Me.imports.app_menu;
function init(extensionMeta) {
Buttons.init(extensionMeta);
Decoration.init(extensionMeta);
AppMenu.init(extensionMeta);
}
function enable() {
Buttons.enable();
Decoration.enable();
AppMenu.enable();
}
function disable() {
AppMenu.disable();
Decoration.disable();
Buttons.disable();
}
pixel-saver-1.20/pixel-saver@deadalnix.me/metadata.json 0000664 0000000 0000000 00000000467 13572142376 0023200 0 ustar 00root root 0000000 0000000 {
"uuid": "pixel-saver@deadalnix.me",
"name": "Pixel Saver",
"description": "Pixel Saver is designed to save pixel by fusing activity bar and title bar in a natural way",
"url": "https://github.com/deadalnix/pixel-saver",
"shell-version": ["3.18", "3.20", "3.22", "3.24", "3.26", "3.28", "3.30", "3.32"]
}
pixel-saver-1.20/pixel-saver@deadalnix.me/themes/ 0000775 0000000 0000000 00000000000 13572142376 0022003 5 ustar 00root root 0000000 0000000 pixel-saver-1.20/pixel-saver@deadalnix.me/themes/Ambiance/ 0000775 0000000 0000000 00000000000 13572142376 0023502 5 ustar 00root root 0000000 0000000 pixel-saver-1.20/pixel-saver@deadalnix.me/themes/Ambiance/ABOUT 0000664 0000000 0000000 00000000225 13572142376 0024276 0 ustar 00root root 0000000 0000000 Ambiance elements are from Ubuntu themes.
Copyright: 2004-2012, Canonical Ltd.
License: CC-BY-SA-3.0
Upstream: https://launchpad.net/ubuntu-themes
pixel-saver-1.20/pixel-saver@deadalnix.me/themes/Ambiance/close_focused_normal.png 0000664 0000000 0000000 00000001565 13572142376 0030404 0 ustar 00root root 0000000 0000000 PNG
IHDR E PLTE$#!}<;7<;7<;7<;7<;7<;7<;7<;7<;7<;7<;7<;7<;7<;7<;7<;7<;7<;7<;7<;7<;7<;7<;7<;7<;7<;7<;7<;7<;7984985:95;95;:6<85<:6<;7@ H;2K;2L%L&L&N&N&N'S<0UE