Не помню, где первый раз увидел, но был очарован заставкой DropClock, о которой уже упоминалось на Хабре.
Но вот беда: авторы собрали её только для Win и Mac. Несмотря на это, желание было сильнее ограничений, и я решил во что бы то ни стало собрать собственную реализацию.
Первая мысль: попытаться реализовать программный рендер воды и брызги.
Так как моё основное направление — веб, я начал искать реализации webGL и эффектов воды. В результате изысканий пришёл к WebGL Water.
Почитал немного документации, попробовал helloworld'ы, но это не моё.
Беглое чтение исходников и попытки правок не привнесли ясности в предмет исследований. Было принято решение посмотреть, а из чего же состоит оригинал.
Скачал с торрентов Win версию, на вайне не пошла, поставил на виртуалку.
На деле оказалось, что это флеш со вшитыми роликами: 10 с цифрами на чёрном фоне и 10 на белом, каждый длится минуту.
Распаковщики swf их не потянули, но мне помог конвертер SWF to Video (win). Дальше уже в родной системе перекодировал в mp4/x264 ffmpeg-ом.
Далее дело оставалось за малым — заставить это работать в браузере. Для разнообразия ещё добавил подгрузку погоды с OpenWeatherMap.
Получилась вот такая разметка:
<div class="all">
<div class="d d0"><video src="dropclock_media/b0.mp4" autoplay="autoplay" loop="loop" id="d0" data-d="0"></video></div>
<div class="d d1"><video src="dropclock_media/b0.mp4" autoplay="autoplay" loop="loop" id="d1" data-d="1"></video></div>
<div class="d s"><div class="sep"></div></div>
<div class="d d2"><video src="dropclock_media/b0.mp4" autoplay="autoplay" loop="loop" id="d2" data-d="2"></video></div>
<div class="d d3"><video src="dropclock_media/b0.mp4" autoplay="autoplay" loop="loop" id="d3" data-d="3"></video></div>
<div class="inspector">
<div class="time" id="info"></div>
<div class="weather"></div>
</div>
</div>
html,body{height:100%;width:100%;margin:0;padding:0;overflow:hidden;}
body{text-align:center;background-color:#000;color:#fff;font-family:Arial;}
body.save{cursor:none;}
.all{
width: 100%;
max-width: 1375px;
height: calc(100% - 100px);
display:inline-block;white-space: nowrap;text-align:center;
position: relative;
padding: 0; margin:0 auto;
}
.d{
display:inline-block;line-height:0;/*width:312px;*/max-width:calc((100% - 115px) / 4 - 2px);height:100%;
vertical-align:top;overflow:hidden;border:1px solid #000;/*background:#fff;*/
}
.d:after{display:block;width:100%;height:2px;content:"";background:#000;margin-top:-2px;position:relative;z-index:100;}
.s{width:115px;overflow:visible;}
.d .sep{height:100%;animation: blick 1s ease infinite;}
.r .sep{-webkit-animation:rotate 60s ease infinite;}
.sep{position:relative;overflow:visible;}
.sep:after,.sep:before{
position:absolute;top:40%;left:39%;z-index:1000;content:"";
display:block;width:22%;height:20%;
-webkit-animation:blick 1s ease infinite;
/*background-color: red;*/
background-image: url("data:image/gif;base64,R0lGODlhAQABAIABAMzMzP///yH+EUNyZWF0ZWQgd2l0aCBHSU1QACwAAAAAAQABAAACAkQBADs=");
background-repeat:no-repeat;background-size:contain;
}
.sep:after{margin-top:94%;}
.sep:before{}
video{height:100%;max-height:700px;width:calc(100% + 2px);max-width:315px;margin:-2px -1px 0;position:relative;z-index:50;}
#info{/*display:none;*/text-align:right;}
@-webkit-keyframes blick{
0% {opacity:1;}
50% {opacity:.7;}
70% {opacity:.3;}
100% {opacity:1;}
}
@-webkit-keyframes rotate{
0 %{-webkit-transform: rotate(0deg);}
0.2%{ -webkit-transform: rotate(-30deg); }
0.8%{ -webkit-transform: rotate(390deg); }
1%{ -webkit-transform: rotate(360deg); }
100% {-webkit-transform: rotate(360deg);}
}
@keyframes blick{0% {opacity:1;}50% {opacity:.7;}70% {opacity:.3;}100% {opacity:1;}}
.inspector{line-height:50px;padding:0 25px;}
.time{float:right;}
.weather{float:left;}
.weather img{vertical-align:top;}
.arrow{display:inline-block;width:20px;height:50px;}
@media screen and (min-height:775px){
.all{
top: calc((100% - 755px) / 2);
height: calc(100% - 202px);
}
}
@media screen and (max-height:775px){
.all{
top:25px;
}
}
var digits = [0,0,0,0];
var msmove = timenow();
var city = 1485357;
var lastrequesttime = 0;
function timenow(){ return new Date * 1; }
function getRandomArbitary(min, max){return Math.random() * (max - min) + min;}
function extra0(d,e){
if(e===undefined)e=1;for(var i=0; i<e; i++){if(d<Math.pow(10,(i+1))) d='0'+d;}return d;}
function step(){
var now = new Date(),hors = now.getHours(),mins = now.getMinutes(),ndig = new Array(4),secs = now.getSeconds(),mili = now.getMilliseconds();
ndig[0]=Math.floor(hors/10);
ndig[1]=hors-ndig[0]*10;
ndig[2]=Math.floor(mins/10);
ndig[3]=mins-ndig[2]*10;
//ndig[4]
// from last mouse move
var flmm = timenow() - msmove;
if(flmm > 2000)
// if(!$('body').hasClass('save'))
$('body').addClass('save');
// print info
var info = document.getElementById('info');
info.innerHTML =
// timenow()+' / '+
// msmove+' / '+
// flmm+' / '+
extra0(hors)+':'+extra0(mins)+':'+extra0(secs)+
// '.'+extra0(mili,2)+
'';
// replace
for(var i in ndig){
if(ndig[i] != digits[i]){
if(lastrequesttime + getRandomArbitary(256,2048) < timenow())
updateDigit(i,ndig[i]);
//document.getElementById('d'+i).src='b'+ndig[i]+'.mp4';
if(i==3){
updateWeather();
}
}
}
setTimeout(step,getRandomArbitary(256,768));
}
function updateDigit(i,n){
var d = $('#d'+i).clone().attr('src','dropclock_media/b'+n+'.mp4').appendTo('.d'+i);
//setTimeout(function(){
// var i = d[0].dataset.d;
$('#d'+i+':first').remove();
// },15000);
//document.getElementById('d'+i).src='b'+n+'.mp4';
lastrequesttime = timenow();
digits[i] = n;
}
function updateWeather(){
// weather
$.getJSON('http://api.openweathermap.org/data/2.5/weather?id='+city,function(data){
console.dir(data);
var icon = 'http://openweathermap.org/img/w/'+data.weather[0].icon+'.png',
temp = Math.floor(data.main.temp - 273.15),
arrst= 'transform: rotate('+data.wind.deg+'deg);-webkit-transform: rotate('+data.wind.deg+'deg);',
w_html =
// 'Погода:'+
data.name+' '+
data.sys.country+' '+
'<img src="'+icon+'">'+
data.weather[0].main+', '+
data.weather[0].description+' '+
((temp>0)?'+':'')+temp+'° '+
'; Wind '+data.wind.speed+'m/s <div class="arrow" style="'+arrst+'">↓</div>'+
'';
$('.weather').html(w_html);
});
}
$(function(){
// timer
step();
$(window).mousemove(function(){
if(document.body.classList.contains('save'))
document.body.classList.remove('save');
msmove = timenow();
});
});
В firefox видео притормаживало и дребезжало, поэтому изначально я был ориентирован на webkit.
Добавил задержку падения цифр, чтобы не исчезали все сразу.
Вроде работает, красиво, но нужно запускать это как самостоятельный файл.
Нашёл у себя в убунте системный мини-браузер webbrowser-app и, покапавшись в его параметрах, написал вот такой лаунчер:
#/bin/sh
webbrowser-app --chromeless --fullscreen /var/www/vhosts/localhost/dropclock/index.html
Но xscreensaver на него сильно ругался и я понял, что что-то делаю не так…
Шло время, желание иметь красивую заставку никак не утолялось. 13 сентября, во всероссийский День Программиста, работа была отодвинута, а внимание снова сосредоточено на достижении призрачной цели. До этого видел оконные приложения на Python и выбор пал на него, как впоследствии оказалось, не зря. Раньше никогда ничего на нём не писал. Начал разбирать базовые примеры, добавил webkit.
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import pygtk
pygtk.require('2.0')
import os, argparse, gtk, webkit
from gtk import gdk
class DropClock:
def createParser():
self.parser = argparse.ArgumentParser()
self.parser.add_argument ('-r', '--root', action='store_const', const=True, default=False)
self.namespace = self.parser.parse_args()
def delete_event(self, widget, event, data=None):
print "Вызван сигнал удаления"
return False
def destroy(self, widget, data=None):
print "Вызван сигнал уничтожения"
gtk.main_quit()
def __init__(self):
self.parser = argparse.ArgumentParser()
self.parser.add_argument ('-r', '--root', action='store_const', const=True, default=False)
self.parser.add_argument ('-w', '-window-id', action='store_const', const=True, default='')
self.namespace = self.parser.parse_args()
url = 'file://' + os.path.realpath('./index.html')
self.window = gtk.Window(gtk.WINDOW_TOPLEVEL)
self.window.set_default_size(1600, 768)
self.window.set_position(gtk.WIN_POS_CENTER)
self.window.connect("delete_event", self.delete_event)
self.window.connect("destroy", self.destroy)
self.browser = webkit.WebView()
self.browser.props.settings.props.enable_default_context_menu = False
self.browser.load_uri(url)
self.window.add(self.browser)
self.window.show_all()
# if self.namespace.root:
# self.window.fullscreen();
def main(self):
gtk.main()
# self.createParser();
if __name__ == "__main__":
hello = DropClock()
hello.main()
А вдруг можно и к xscreensaver'у его прикрутить? Был найден пример заставки на питоне, на базе которого написан свой.
#!/usr/bin/python
import os
import sys
import gtk, webkit
from gtk import gdk
# the secret sauce is to get the "window id" out of $XSCREENSAVER_WINDOW
# code comes from these two places:
# 1) http://pastebin.com/nSCiq1P3
# 2) http://stackoverflow.com/questions/4598581/python-clutter-set-display
class ScreenSaverWindow(gtk.Window):
def __init__(self):
gtk.Window.__init__(self)
pass
def delete_event(self, widget, event, data=None):
return False
def destroy(self, widget, data=None):
gtk.main_quit()
def realize(self):
if self.flags() & gtk.REALIZED:
return
ident = os.environ.get('XSCREENSAVER_WINDOW')
if not ident is None:
print 'if not ident is None:'
self.window = gtk.gdk.window_foreign_new(int(ident, 16))
self.window.set_events (gdk.EXPOSURE_MASK | gdk.STRUCTURE_MASK)
# added by aja
x, y, w, h, depth = self.window.get_geometry()
# self.size_allocate(gtk.gdk.Rectangle(x, y, w, h))
self.set_default_size(w, h)
self.move(x, y)
self.set_decorated(False)
# aja - more
self.window.set_user_data(self)
self.style.attach(self.window)
self.set_flags(self.flags() | gtk.REALIZED)
#self.window.connect("destroy", self.destroy)
if self.window == None:
print 'self.window == None:'
self.window = gdk.Window(None, 1024, 768, gdk.WINDOW_TOPLEVEL,(gdk.EXPOSURE_MASK | gdk.STRUCTURE_MASK),gdk.INPUT_OUTPUT)
# self.window.set_title("DropClock")
if self.window != None:
print 'self.window != None:'
#self.window.add_filter(lambda *args: self.filter_event(args))
self.set_flags(self.flags() | gtk.REALIZED)
self.browser = webkit.WebView()
url = 'file://' + os.path.join(os.path.dirname(__file__) + '/index.html')
self.browser.load_uri(url)
self.add(self.browser)
self.browser.show()
window = ScreenSaverWindow()
window.set_title('DropClock')
window.connect('delete-event', gtk.main_quit)
window.set_default_size(1024, 768)
window.realize()
window.modify_bg(gtk.STATE_NORMAL, gdk.color_parse("black"))
window.show()
gtk.main()
В ~/.xscreensaver под списком добавлена строка:
- "Drop Clock" /var/www/vhosts/localhost/dropclock/dropclock_xss.py n
Впоследствии ещё дорабатывал адаптивность стиля для корректного отображения в маленьком окне, получилось неплохо, мне нравится.
Можно считать, что день программиста удался.
Видео можно найти по магнет-ссылке.
Автор: tima_tey