EDIT
In the original answer, further below, the countdown window appeared after an arbitrary idle time. Re- reading your question, you might want it permanently. The permanent version is below (which is simpler), the original answer further down.
1a. Version, permanently showing countdown time
The solution is a background script, showing a semi- transparent countdown window. The window behaves like a notification: it is always visible on top, but (of course) you can work in your other windows as usual:
The initial time is the idle- time before suspend should be activated. The time is reset on mouse- keyboard events.
As the image shows, the script comes with different pre- set color options (see further below).
How to setup
The script needs xprintidle
:
sudo apt-get install xprintidle
- Copy the script below into an empty file, save it as
countdown.py
Run it with the idle time as argument:
python3 /path/to/countdown.py <idle_time>
e.g.
python3 /path/to/countdown.py 300
to enter suspend after 5 minutes.
If all works fine, add it to startup applications: Dash > Startup Applications > Add. Add the command:
/path/to/runner.py <idle_time>
The script
#!/usr/bin/env python3
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk, Gdk, GObject, Pango
from threading import Thread
import subprocess
import time
import signal
import sys
import os
# --- set the color (index) below (1 is the first)
color = 1
# ---
textcolors = ["grey", "orange", "green", "blue", "white"]
# --- don't change anything below
txtcolor = textcolors[color-1]
countdown = int(sys.argv[1])
susp = os.path.dirname(os.path.realpath(__file__))+"/susp.sh"
class CountDown(Gtk.Window):
def __init__(self):
Gtk.Window.__init__(self)
maingrid = Gtk.Grid()
self.add(maingrid)
maingrid.set_border_width(40)
# set initial text for the spash window
self.label = Gtk.Label(convert_seconds(countdown))
self.label.modify_font(Pango.FontDescription('Ubuntu 22'))
self.label.set_width_chars(10)
maingrid.attach(self.label, 0, 0, 1, 1)
self.update = Thread(target=self.start_countdown, args=[countdown])
# daemonize the thread
self.update.setDaemon(True)
self.update.start()
def start_countdown(self, countdown):
idle1 = idletime()
t = countdown
while True:
time.sleep(1)
idle2 = idletime()
if idle2 < idle1:
t = countdown
else:
t -= 1
if t <= 0:
subprocess.Popen(["systemctl", "suspend"])
GObject.idle_add(self.label.set_text, convert_seconds(t),
priority=GObject.PRIORITY_DEFAULT)
idle1 = idle2
def stop(self):
Gtk.main_quit()
def get_screen():
scr = [s.split("x") for s in subprocess.check_output([
"xrandr"]).decode("utf-8").split() if "+0+0" in s][0]
return int(scr[0]) - 300
def convert_seconds(sec):
timedisplay = [
str(int(sec/3600)),
str(int((sec % 3600)/60)),
str(int(sec % 60)),
]
for i, n in enumerate(timedisplay):
if len(n) == 1:
timedisplay[i] = "0"+n
return ":".join(timedisplay)
def idletime():
return int(subprocess.check_output(
"xprintidle"
).decode("utf-8").strip())/1000
def splashwindow():
window = CountDown()
window.set_decorated(False)
window.set_resizable(False)
window.override_background_color(Gtk.StateType.NORMAL, Gdk.RGBA(0,0,0,1))
window.modify_fg(Gtk.StateFlags.NORMAL, Gdk.color_parse(txtcolor))
window.set_opacity(0.6)
window.move(get_screen(), 80)
window.set_keep_above(True)
window.show_all()
Gtk.main()
GObject.threads_init()
splashwindow()
Note
The text color can be changed, as explained at the very bottom of the second version of the answer.
1b. As requested in a comment: luxury version of the same script: text color changes to yellow if half the time passed, to red 30 seconds before suspend.
> >
Use it exactly as 1a
.
The script
#!/usr/bin/env python3
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk, Gdk, GObject, Pango
from threading import Thread
import subprocess
import time
import signal
import sys
import os
# --- set the color (index) below (1 is the first)
color = 1
# ---
textcolors = ["grey", "orange", "green", "blue", "white", "yellow", "red"]
# --- don't change anything below
txtcolor = textcolors[color-1]
al_cl1 = textcolors[5]; al_cl2 = textcolors[6]
countdown = int(sys.argv[1])
alarm1 = int(countdown/2)
alarm2 = 30
class CountDown(Gtk.Window):
def __init__(self):
Gtk.Window.__init__(self)
maingrid = Gtk.Grid()
self.add(maingrid)
maingrid.set_border_width(40)
# set initial text for the spash window
self.label = Gtk.Label(convert_seconds(countdown))
self.label.modify_font(Pango.FontDescription('Ubuntu 22'))
self.label.modify_fg(Gtk.StateFlags.NORMAL, Gdk.color_parse(txtcolor))
self.label.set_width_chars(10)
maingrid.attach(self.label, 0, 0, 1, 1)
self.update = Thread(target=self.start_countdown, args=[countdown])
# daemonize the thread
self.update.setDaemon(True)
self.update.start()
def mod_color(self, color):
self.label.modify_fg(Gtk.StateFlags.NORMAL, Gdk.color_parse(color))
def start_countdown(self, countdown):
idle1 = idletime()
t1 = countdown
t2 = countdown
while True:
time.sleep(1)
idle2 = idletime()
if idle2 < idle1:
t2 = countdown
if t1 <= alarm1:
# print("change textcolor default")
GObject.idle_add(self.mod_color, txtcolor,
priority=GObject.PRIORITY_DEFAULT)
else:
t2 -= 1
if all([t2 <= alarm2, t1 > alarm2]):
# print("change textcolor red")
GObject.idle_add(self.mod_color, al_cl2,
priority=GObject.PRIORITY_DEFAULT)
elif all([t2 <= alarm1, t1 > alarm1]):
# print("change textcolor yellow")
GObject.idle_add(self.mod_color, al_cl1,
priority=GObject.PRIORITY_DEFAULT)
if t2 <= 0:
subprocess.Popen(["systemctl", "suspend"])
GObject.idle_add(self.label.set_text, convert_seconds(t2),
priority=GObject.PRIORITY_DEFAULT)
idle1 = idle2
t1 = t2
def stop(self):
Gtk.main_quit()
def get_screen():
scr = [s.split("x") for s in subprocess.check_output([
"xrandr"]).decode("utf-8").split() if "+0+0" in s][0]
return int(scr[0]) - 300
def convert_seconds(sec):
timedisplay = [
str(int(sec/3600)),
str(int((sec % 3600)/60)),
str(int(sec % 60)),
]
for i, n in enumerate(timedisplay):
if len(n) == 1:
timedisplay[i] = "0"+n
return ":".join(timedisplay)
def idletime():
return int(subprocess.check_output(
"xprintidle"
).decode("utf-8").strip())/1000
def splashwindow():
window = CountDown()
window.set_decorated(False)
window.set_resizable(False)
window.override_background_color(Gtk.StateType.NORMAL, Gdk.RGBA(0,0,0,1))
window.set_opacity(0.6)
window.move(get_screen(), 80)
window.set_keep_above(True)
window.show_all()
Gtk.main()
GObject.threads_init()
splashwindow()
2. Original answer: version, showing countdown time after x idle time
The setup below will show a countdown (during an arbitrary time length) to the next suspend:
The window is always on top of all other windows, exactly like the notification bubbles.
The setup replaces the "normal" suspend settings, which means you need to disable suspend from system settings.
About the solution
The command to suspend in the script is:
systemctl suspend
which doesn't require sudo
. The consequence is that you will need at least 15.04
to use this solution.
The script was written and tested on Ubuntu
(Unity) 15.10
, but there is no specific code in it that should be Unity specific. I assume it works fine on all default Ubuntu versions > 15.04
How it works
To setup (detailed version further below), simply copy the three scripts involved into one and the same directory, exactly named as indicated. To run, simply run the main script (running the time- check).
- If the idle time exceeds a certain limit, the countdown window is called.
- If during countdown the computer becomes un- idle (mouse- or keyboard event) the window is closed (its pid is killed).
- If the timer has ended its count down, it runs a simple script to suspend
How to setup
The script needs xprintidle
:
sudo apt-get install xprintidle
Copy the three scripts below into separate empty files, save the in one and the same directory, exactly named as indicated:
A. save (exactly) as win.py
:
#!/usr/bin/env python3
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk, Gdk, GObject, Pango
from threading import Thread
import subprocess
import time
import signal
import sys
import os
# --- set the color (index) below (1 is the first)
color = 1
# ---
textcolors = ["grey", "orange", "green", "blue", "white"]
# --- don't change anything below
txtcolor = textcolors[color-1]
countdown = int(sys.argv[1])
susp = os.path.dirname(os.path.realpath(__file__))+"/susp.sh"
class CountDown(Gtk.Window):
def __init__(self):
Gtk.Window.__init__(self)
maingrid = Gtk.Grid()
self.add(maingrid)
maingrid.set_border_width(40)
# set initial text for the spash window
self.label = Gtk.Label(convert_seconds(countdown))
self.label.modify_font(Pango.FontDescription('Ubuntu 22'))
self.label.set_width_chars(10)
maingrid.attach(self.label, 0, 0, 1, 1)
self.update = Thread(target=self.start_countdown, args=[countdown])
# daemonize the thread
self.update.setDaemon(True)
self.update.start()
def start_countdown(self, countdown):
t = countdown
while t > 0:
time.sleep(1)
t -= 1
GObject.idle_add(self.label.set_text, convert_seconds(t),
priority=GObject.PRIORITY_DEFAULT)
print(t)
subprocess.Popen(["/bin/bash", susp])
self.stop()
def stop(self):
Gtk.main_quit()
def get_screen():
scr = [s.split("x") for s in subprocess.check_output([
"xrandr"]).decode("utf-8").split() if "+0+0" in s][0]
return int(scr[0]) - 300
def convert_seconds(sec):
timedisplay = [
str(int(sec/3600)),
str(int((sec % 3600)/60)),
str(int(sec % 60)),
]
for i, n in enumerate(timedisplay):
if len(n) == 1:
timedisplay[i] = "0"+n
return ":".join(timedisplay)
def splashwindow():
window = CountDown()
window.set_decorated(False)
window.set_resizable(False)
window.override_background_color(Gtk.StateType.NORMAL, Gdk.RGBA(0,0,0,1))
window.modify_fg(Gtk.StateFlags.NORMAL, Gdk.color_parse(txtcolor))
window.set_opacity(0.6)
window.move(get_screen(), 80)
window.set_keep_above(True)
window.show_all()
Gtk.main()
GObject.threads_init()
splashwindow()
B. Save exactly as runner.py
:
#!/usr/bin/env python3
import subprocess
import time
import os
import sys
window_mod = os.path.dirname(os.path.realpath(__file__))+"/win.py"
suspend = int(sys.argv[1])
countdown = int(sys.argv[2])
w = False
while True:
time.sleep(1)
idletime = int(subprocess.check_output(
"xprintidle"
).decode("utf-8").strip())/1000
if all([idletime > suspend-countdown, w == False]):
subprocess.Popen(["python3", window_mod, str(countdown)])
w = True
elif all([idletime < suspend-countdown, w == True]):
try:
procdata = subprocess.check_output([
"pgrep", "-f", window_mod
]).decode("utf-8").strip()
procs = procdata.splitlines()
except subprocess.CalledProcessError:
pass
else:
for p in procs:
subprocess.Popen(["kill", p])
w = False
C. Save (exactly) as susp.sh
:
#!/bin/bash
sleep 3
systemctl suspend
Make all three scripts executable, and again, make sure they are in one and the same directory.
You're practically done.
- Disable your "usual" suspend settings
Test- run the script the suspend time (idle time before suspend should be applied), and the count down time (in seconds) as arguments, e.g.:
/path/to/runner.py 600 300
to set idle time to 10 minutes, counter starts 5 minutes before suspend.
If all works fine, add it to startup applications: Dash > Startup Applications > Add. Add the command:
/path/to/runner.py <idle_time> <countdown_time>
Notes
In the head section of the win.py
, you can set different colors for the displayed text:
# --- set the color (index) below (1 is the first)
color = 1
# ---
textcolors = ["grey", "orange", "green", "blue", "white"]
Playing with the values in the lines:
maingrid.set_border_width(10)
and
return int(scr[0]) - 200
(from the function get_screen
, where 200 is the distance of the left side of the window to the right side of the screen), and
window.move(get_screen(), 35)
(where 35 is the distance between the window and the top of the screen), you can easily change the geometry of the window, e.g.:
Have fun :)
After reviewing all the information I can find, I've come to the conclusion that you are affected by this kernel bug. which has affected numerous systems including Asus, Dell, HP, Lenovo, etc. This bug which will be marked for expiration in 47 days if no further activity occurs, is extremely hot, scoring 412 on the bug heat score and is reported as affecting 86 people at this moment. I recommend that you subscribe to it and provide any kernel related info that you can to help clarify the issue so that it remains open and the devs can squash it.
A likely workaround is upgrading/reverting to kernel 4.4.8 as noted here and confirmed here and here.
Work has been requested to reverse bisect the kernel but the bug is currently expired so it looks like the devs didn't get all the help they needed in determining the root cause of this behavior. Near as I can tell from perusing the bug report this problem appeared in kernel 4.4.0, was resolved in 4.4.8 and then reappeared sometime around 4.5.2 but I won't claim to be certain.
Another possible kernel upgrade workaround was reported here although I haven't found confirmation on that one.
If your using a kernel from the repository, it could be helpful if you were to launch ubuntu-bug linux
from the terminal to create a new bug report providing specific information regarding your kernel/issue.
You might wish to rule out out-of-order-execution as a culprit as well. You should be able to accomplish this by disabling all but CPU0 like:
for x in /sys/devices/system/cpu/cpu*/online; do
echo 0 >"$x"
done
You might also find this information useful in tracking down the root cause of this issue.
Sources:
https://bugs.launchpad.net/ubuntu/+source/linux/+bug/1574125/
https://unix.stackexchange.com/questions/145645/disabling-cpu-cores-on-quad-core-processor-on-linux
Best Answer
Based of from this thread, you can disable the sleep process through the duration of your program by calling to idle inhibition via DBus:
org.freedesktop.ScreenSaver.Inhibit
Or you can ping it periodically:
org.freedesktop.ScreenSaver.SimulateUserActivity
If you need to prevent the computer to sleep at all, you can disable temporally (again through dBus) the behavior and then reinstate it. This will kick the screensaver but won't let the computer sleep: