2018-08-08 23:54:58 +02:00
# include "modules/clock.hpp"
2020-05-22 18:52:26 +02:00
2020-05-22 20:02:09 +02:00
# include <spdlog/spdlog.h>
2022-04-15 14:39:13 +02:00
# include <iomanip>
2022-01-08 07:25:15 +01:00
# if FMT_VERSION < 60000
# include <fmt/time.h>
# else
# include <fmt/chrono.h>
# endif
2020-05-22 18:52:26 +02:00
2022-01-08 07:25:15 +01:00
# include <ctime>
2020-01-31 17:54:41 +01:00
# include <sstream>
2020-02-05 20:02:42 +01:00
# include <type_traits>
2022-01-08 07:25:15 +01:00
2021-01-31 20:53:53 +01:00
# include "util/ustring_clen.hpp"
2022-01-08 03:09:44 +01:00
# include "util/waybar_time.hpp"
2020-02-04 01:58:18 +01:00
# ifdef HAVE_LANGINFO_1STDAY
# include <langinfo.h>
2020-02-05 20:02:42 +01:00
# include <locale.h>
2020-02-04 01:58:18 +01:00
# endif
2020-01-31 17:54:41 +01:00
2022-01-08 03:09:44 +01:00
using waybar : : waybar_time ;
2018-08-08 23:54:58 +02:00
2018-12-18 17:30:54 +01:00
waybar : : modules : : Clock : : Clock ( const std : : string & id , const Json : : Value & config )
2021-10-03 18:48:21 +02:00
: ALabel ( config , " clock " , id , " {:%H:%M} " , 60 , false , false , true ) ,
current_time_zone_idx_ ( 0 ) ,
2021-10-05 11:46:52 +02:00
is_calendar_in_tooltip_ ( false ) ,
2022-04-06 08:37:19 +02:00
is_timezoned_list_in_tooltip_ ( false ) {
2021-10-03 05:27:54 +02:00
if ( config_ [ " timezones " ] . isArray ( ) & & ! config_ [ " timezones " ] . empty ( ) ) {
2022-04-06 08:37:19 +02:00
for ( const auto & zone_name : config_ [ " timezones " ] ) {
2021-10-03 18:48:21 +02:00
if ( ! zone_name . isString ( ) | | zone_name . asString ( ) . empty ( ) ) {
time_zones_ . push_back ( nullptr ) ;
continue ;
}
2022-04-06 08:37:19 +02:00
time_zones_ . push_back ( date : : locate_zone ( zone_name . asString ( ) ) ) ;
2021-10-03 18:48:21 +02:00
}
2021-10-05 12:20:06 +02:00
} else if ( config_ [ " timezone " ] . isString ( ) & & ! config_ [ " timezone " ] . asString ( ) . empty ( ) ) {
2022-04-06 08:37:19 +02:00
time_zones_ . push_back ( date : : locate_zone ( config_ [ " timezone " ] . asString ( ) ) ) ;
2021-10-03 05:27:54 +02:00
}
2021-10-03 18:48:21 +02:00
2022-04-06 08:37:19 +02:00
// If all timezones are parsed and no one is good, add nullptr to the timezones vector, to mark
// that local time should be shown.
2021-10-05 12:20:06 +02:00
if ( ! time_zones_ . size ( ) ) {
time_zones_ . push_back ( nullptr ) ;
}
2021-10-03 18:48:21 +02:00
if ( ! is_timezone_fixed ( ) ) {
2022-04-06 08:37:19 +02:00
spdlog : : warn (
" As using a timezone, some format args may be missing as the date library haven't got a "
" release since 2018. " ) ;
2020-02-05 20:02:42 +01:00
}
2020-01-31 17:54:41 +01:00
2022-04-06 08:37:19 +02:00
// Check if a particular placeholder is present in the tooltip format, to know what to calculate
// on update.
2021-10-03 18:48:21 +02:00
if ( config_ [ " tooltip-format " ] . isString ( ) ) {
2021-10-05 11:55:30 +02:00
std : : string trimmed_format = config_ [ " tooltip-format " ] . asString ( ) ;
2022-04-06 08:37:19 +02:00
trimmed_format . erase ( std : : remove_if ( trimmed_format . begin ( ) , trimmed_format . end ( ) ,
[ ] ( unsigned char x ) { return std : : isspace ( x ) ; } ) ,
trimmed_format . end ( ) ) ;
2021-10-05 11:55:30 +02:00
if ( trimmed_format . find ( " { " + kCalendarPlaceholder + " } " ) ! = std : : string : : npos ) {
2021-10-03 18:48:21 +02:00
is_calendar_in_tooltip_ = true ;
}
2021-10-05 11:46:52 +02:00
if ( trimmed_format . find ( " { " + KTimezonedTimeListPlaceholder + " } " ) ! = std : : string : : npos ) {
is_timezoned_list_in_tooltip_ = true ;
}
2021-10-03 18:48:21 +02:00
}
2022-05-13 11:54:18 +02:00
const char * applyLocale { config_ [ " locale " ] . isString ( ) ? config_ [ " locale " ] . asCString ( ) : getenv ( " LC_TIME " ) } ;
try {
locale_ = applyLocale ? std : : locale ( applyLocale ) : std : : locale ( " " ) ;
} catch ( std : : runtime_error const & localeE ) {
spdlog : : warn ( " Clock module. Wrong \" locale \" or LC_TIME variable(make sure locale is presented in /etc/locale.gen and generated by the locale-gen). Trace: {0} " , localeE . what ( ) ) ;
locale_ = std : : locale ( ) ;
2020-02-05 20:02:42 +01:00
}
2020-01-31 17:54:41 +01:00
2018-11-23 11:57:37 +01:00
thread_ = [ this ] {
2018-08-20 14:50:45 +02:00
dp . emit ( ) ;
2022-03-31 17:14:29 +02:00
auto now = std : : chrono : : system_clock : : now ( ) ;
/* difference with projected wakeup time */
auto diff = now . time_since_epoch ( ) % interval_ ;
/* sleep until the next projected time */
thread_ . sleep_for ( interval_ - diff ) ;
2018-08-08 23:54:58 +02:00
} ;
2018-08-18 11:43:48 +02:00
}
2018-08-08 23:54:58 +02:00
2021-10-03 18:48:21 +02:00
const date : : time_zone * waybar : : modules : : Clock : : current_timezone ( ) {
2022-04-06 08:37:19 +02:00
return time_zones_ [ current_time_zone_idx_ ] ? time_zones_ [ current_time_zone_idx_ ]
: date : : current_zone ( ) ;
2021-10-03 18:48:21 +02:00
}
2020-05-22 18:52:26 +02:00
2021-10-03 18:48:21 +02:00
bool waybar : : modules : : Clock : : is_timezone_fixed ( ) {
return time_zones_ [ current_time_zone_idx_ ] ! = nullptr ;
}
2020-01-31 17:54:41 +01:00
2021-10-03 18:48:21 +02:00
auto waybar : : modules : : Clock : : update ( ) - > void {
auto time_zone = current_timezone ( ) ;
auto now = std : : chrono : : system_clock : : now ( ) ;
waybar_time wtime = { locale_ ,
date : : make_zoned ( time_zone , date : : floor < std : : chrono : : seconds > ( now ) ) } ;
std : : string text = " " ;
if ( ! is_timezone_fixed ( ) ) {
2020-05-22 18:52:26 +02:00
// As date dep is not fully compatible, prefer fmt
tzset ( ) ;
auto localtime = fmt : : localtime ( std : : chrono : : system_clock : : to_time_t ( now ) ) ;
2022-01-14 19:36:24 +01:00
text = fmt : : format ( locale_ , format_ , localtime ) ;
2020-05-22 18:52:26 +02:00
} else {
2020-05-22 18:56:32 +02:00
text = fmt : : format ( format_ , wtime ) ;
2020-05-22 18:52:26 +02:00
}
2021-10-03 18:48:21 +02:00
label_ . set_markup ( text ) ;
2019-04-18 17:52:00 +02:00
2019-02-24 09:25:34 +01:00
if ( tooltipEnabled ( ) ) {
if ( config_ [ " tooltip-format " ] . isString ( ) ) {
2021-10-05 11:55:30 +02:00
std : : string calendar_lines = " " ;
2021-10-05 11:46:52 +02:00
std : : string timezoned_time_lines = " " ;
2021-10-03 18:48:21 +02:00
if ( is_calendar_in_tooltip_ ) {
2021-10-05 11:55:30 +02:00
calendar_lines = calendar_text ( wtime ) ;
2021-10-03 18:48:21 +02:00
}
2021-10-05 11:46:52 +02:00
if ( is_timezoned_list_in_tooltip_ ) {
timezoned_time_lines = timezones_text ( & now ) ;
}
2021-10-03 18:48:21 +02:00
auto tooltip_format = config_ [ " tooltip-format " ] . asString ( ) ;
2022-04-06 08:37:19 +02:00
text =
fmt : : format ( tooltip_format , wtime , fmt : : arg ( kCalendarPlaceholder . c_str ( ) , calendar_lines ) ,
fmt : : arg ( KTimezonedTimeListPlaceholder . c_str ( ) , timezoned_time_lines ) ) ;
2022-02-02 12:41:06 +01:00
label_ . set_tooltip_markup ( text ) ;
2019-02-24 09:25:34 +01:00
}
}
2021-10-03 18:48:21 +02:00
2020-04-12 18:30:21 +02:00
// Call parent update
ALabel : : update ( ) ;
2018-08-09 12:05:48 +02:00
}
2020-02-02 23:44:26 +01:00
2022-04-06 08:37:19 +02:00
bool waybar : : modules : : Clock : : handleScroll ( GdkEventScroll * e ) {
2020-08-13 04:46:51 +02:00
// defer to user commands if set
if ( config_ [ " on-scroll-up " ] . isString ( ) | | config_ [ " on-scroll-down " ] . isString ( ) ) {
return AModule : : handleScroll ( e ) ;
}
auto dir = AModule : : getScrollDir ( e ) ;
if ( dir ! = SCROLL_DIR : : UP & & dir ! = SCROLL_DIR : : DOWN ) {
return true ;
}
2021-10-03 18:48:21 +02:00
if ( time_zones_ . size ( ) = = 1 ) {
2020-08-13 04:46:51 +02:00
return true ;
}
2021-10-03 18:48:21 +02:00
auto nr_zones = time_zones_ . size ( ) ;
2020-12-01 03:07:22 +01:00
if ( dir = = SCROLL_DIR : : UP ) {
2021-10-03 18:48:21 +02:00
size_t new_idx = current_time_zone_idx_ + 1 ;
current_time_zone_idx_ = new_idx = = nr_zones ? 0 : new_idx ;
2020-08-13 04:46:51 +02:00
} else {
2022-04-06 08:37:19 +02:00
current_time_zone_idx_ =
current_time_zone_idx_ = = 0 ? nr_zones - 1 : current_time_zone_idx_ - 1 ;
2020-08-13 04:46:51 +02:00
}
2021-10-03 18:48:21 +02:00
2020-08-13 04:46:51 +02:00
update ( ) ;
return true ;
}
2020-02-05 20:02:42 +01:00
auto waybar : : modules : : Clock : : calendar_text ( const waybar_time & wtime ) - > std : : string {
2020-01-31 17:54:41 +01:00
const auto daypoint = date : : floor < date : : days > ( wtime . ztime . get_local_time ( ) ) ;
const auto ymd = date : : year_month_day ( daypoint ) ;
2020-02-05 20:02:42 +01:00
if ( cached_calendar_ymd_ = = ymd ) {
return cached_calendar_text_ ;
2020-02-02 23:44:26 +01:00
}
2020-01-31 17:54:41 +01:00
const date : : year_month ym ( ymd . year ( ) , ymd . month ( ) ) ;
2022-04-06 08:37:19 +02:00
const auto curr_day = ymd . day ( ) ;
2020-01-31 17:54:41 +01:00
std : : stringstream os ;
2022-04-10 11:54:50 +02:00
2022-04-06 08:37:19 +02:00
const auto first_dow = first_day_of_week ( ) ;
2022-04-15 14:39:13 +02:00
int ws { 0 } ; // weeks-pos: side(1 - left, 2 - right)
int wn { 0 } ; // weeknumber
2022-03-26 21:33:15 +01:00
if ( config_ [ " calendar-weeks-pos " ] . isString ( ) ) {
2022-04-15 14:39:13 +02:00
wn = ( date : : sys_days { date : : year_month_day { ym / 1 } } -
date : : sys_days { date : : year_month_day { ymd . year ( ) / 1 / 1 } } )
. count ( ) /
7 +
1 ;
2022-03-26 21:33:15 +01:00
if ( config_ [ " calendar-weeks-pos " ] . asString ( ) = = " left " ) {
2022-03-24 13:41:50 +01:00
ws = 1 ;
// Add paddings before the header
os < < std : : string ( 4 , ' ' ) ;
2022-03-26 21:33:15 +01:00
} else if ( config_ [ " calendar-weeks-pos " ] . asString ( ) = = " right " ) {
2022-03-24 13:41:50 +01:00
ws = 2 ;
}
}
2022-04-15 14:39:13 +02:00
2020-02-05 20:02:42 +01:00
weekdays_header ( first_dow , os ) ;
2022-03-25 14:12:11 +01:00
/* Print weeknumber on the left for the first row*/
if ( ws = = 1 ) {
print_iso_weeknum ( os , wn ) ;
os < < ' ' ;
+ + wn ;
}
2020-01-31 17:54:41 +01:00
// First week prefixed with spaces if needed.
2020-05-22 18:52:26 +02:00
auto wd = date : : weekday ( ym / 1 ) ;
2020-01-31 17:54:41 +01:00
auto empty_days = ( wd - first_dow ) . count ( ) ;
if ( empty_days > 0 ) {
os < < std : : string ( empty_days * 3 - 1 , ' ' ) ;
}
2020-05-22 18:52:26 +02:00
auto last_day = ( ym / date : : literals : : last ) . day ( ) ;
2020-02-02 23:55:37 +01:00
for ( auto d = date : : day ( 1 ) ; d < = last_day ; + + d , + + wd ) {
if ( wd ! = first_dow ) {
os < < ' ' ;
} else if ( unsigned ( d ) ! = 1 ) {
2022-03-24 13:41:50 +01:00
if ( ws = = 2 ) {
os < < ' ' ;
print_iso_weeknum ( os , wn ) ;
+ + wn ;
}
2020-02-02 23:55:37 +01:00
os < < ' \n ' ;
2022-03-24 13:41:50 +01:00
if ( ws = = 1 ) {
print_iso_weeknum ( os , wn ) ;
os < < ' ' ;
+ + wn ;
}
2020-02-02 23:55:37 +01:00
}
2020-01-31 17:54:41 +01:00
if ( d = = curr_day ) {
2020-08-06 02:31:36 +02:00
if ( config_ [ " today-format " ] . isString ( ) ) {
auto today_format = config_ [ " today-format " ] . asString ( ) ;
os < < fmt : : format ( today_format , date : : format ( " %e " , d ) ) ;
} else {
os < < " <b><u> " < < date : : format ( " %e " , d ) < < " </u></b> " ;
}
2022-03-26 21:33:15 +01:00
} else if ( config_ [ " format-calendar " ] . isString ( ) ) {
os < < fmt : : format ( config_ [ " format-calendar " ] . asString ( ) , date : : format ( " %e " , d ) ) ;
2022-04-15 14:39:13 +02:00
} else
os < < date : : format ( " %e " , d ) ;
2022-03-24 13:41:50 +01:00
/*Print weeks on the right when the endings with spaces*/
2022-05-13 17:51:32 +02:00
if ( ws = = 2 & & d = = last_day ) {
empty_days = 6 - ( wd . c_encoding ( ) - first_dow . c_encoding ( ) ) ;
if ( empty_days > 0 ) {
os < < std : : string ( empty_days * 3 + 1 , ' ' ) ;
print_iso_weeknum ( os , wn ) ;
}
2022-03-24 13:41:50 +01:00
}
2020-01-31 17:54:41 +01:00
}
2020-02-02 23:44:26 +01:00
auto result = os . str ( ) ;
2020-02-05 20:02:42 +01:00
cached_calendar_ymd_ = ymd ;
cached_calendar_text_ = result ;
2020-02-02 23:44:26 +01:00
return result ;
2020-01-31 17:54:41 +01:00
}
2020-05-22 18:52:26 +02:00
auto waybar : : modules : : Clock : : weekdays_header ( const date : : weekday & first_dow , std : : ostream & os )
- > void {
2022-03-26 21:33:15 +01:00
std : : stringstream res ;
2020-02-05 20:02:42 +01:00
auto wd = first_dow ;
do {
2022-03-26 21:33:15 +01:00
if ( wd ! = first_dow ) res < < ' ' ;
2020-02-05 20:02:42 +01:00
Glib : : ustring wd_ustring ( date : : format ( locale_ , " %a " , wd ) ) ;
2021-01-31 20:53:53 +01:00
auto clen = ustring_clen ( wd_ustring ) ;
auto wd_len = wd_ustring . length ( ) ;
while ( clen > 2 ) {
2022-04-06 08:37:19 +02:00
wd_ustring = wd_ustring . substr ( 0 , wd_len - 1 ) ;
2021-01-31 20:53:53 +01:00
wd_len - - ;
clen = ustring_clen ( wd_ustring ) ;
2020-02-05 20:02:42 +01:00
}
2021-01-31 20:53:53 +01:00
const std : : string pad ( 2 - clen , ' ' ) ;
2022-03-26 21:33:15 +01:00
res < < pad < < wd_ustring ;
2020-02-05 20:02:42 +01:00
} while ( + + wd ! = first_dow ) ;
2022-03-26 21:33:15 +01:00
res < < " \n " ;
if ( config_ [ " format-calendar-weekdays " ] . isString ( ) ) {
os < < fmt : : format ( config_ [ " format-calendar-weekdays " ] . asString ( ) , res . str ( ) ) ;
2022-04-15 14:39:13 +02:00
} else
os < < res . str ( ) ;
2020-01-31 17:54:41 +01:00
}
2018-08-08 23:54:58 +02:00
2022-04-06 08:37:19 +02:00
auto waybar : : modules : : Clock : : timezones_text ( std : : chrono : : system_clock : : time_point * now )
- > std : : string {
2021-10-05 11:46:52 +02:00
if ( time_zones_ . size ( ) = = 1 ) {
return " " ;
}
std : : stringstream os ;
waybar_time wtime ;
for ( size_t time_zone_idx = 0 ; time_zone_idx < time_zones_ . size ( ) ; + + time_zone_idx ) {
if ( static_cast < int > ( time_zone_idx ) = = current_time_zone_idx_ ) {
continue ;
}
const date : : time_zone * timezone = time_zones_ [ time_zone_idx ] ;
if ( ! timezone ) {
timezone = date : : current_zone ( ) ;
}
wtime = { locale_ , date : : make_zoned ( timezone , date : : floor < std : : chrono : : seconds > ( * now ) ) } ;
os < < fmt : : format ( format_ , wtime ) < < " \n " ;
}
return os . str ( ) ;
}
2022-04-15 14:39:13 +02:00
auto waybar : : modules : : Clock : : print_iso_weeknum ( std : : ostream & os , int weeknum ) - > void {
2022-03-24 13:41:50 +01:00
std : : stringstream res ;
2022-05-13 17:51:32 +02:00
res < < std : : setfill ( ' 0 ' ) < < std : : setw ( 2 ) < < weeknum ;
2022-03-24 13:41:50 +01:00
2022-03-26 21:33:15 +01:00
if ( config_ [ " format-calendar-weeks " ] . isString ( ) ) {
os < < fmt : : format ( config_ [ " format-calendar-weeks " ] . asString ( ) , res . str ( ) ) ;
2022-04-15 14:39:13 +02:00
} else
os < < res . str ( ) ;
2022-03-24 13:41:50 +01:00
}
2020-02-05 20:02:42 +01:00
# ifdef HAVE_LANGINFO_1STDAY
template < auto fn >
using deleter_from_fn = std : : integral_constant < decltype ( fn ) , fn > ;
2020-01-21 23:48:16 +01:00
2020-02-05 20:02:42 +01:00
template < typename T , auto fn >
using deleting_unique_ptr = std : : unique_ptr < T , deleter_from_fn < fn > > ;
# endif
2019-04-18 17:52:00 +02:00
2020-02-05 20:02:42 +01:00
// Computations done similarly to Linux cal utility.
auto waybar : : modules : : Clock : : first_day_of_week ( ) - > date : : weekday {
# ifdef HAVE_LANGINFO_1STDAY
2020-05-22 18:52:26 +02:00
deleting_unique_ptr < std : : remove_pointer < locale_t > : : type , freelocale > posix_locale {
newlocale ( LC_ALL , locale_ . name ( ) . c_str ( ) , nullptr ) } ;
2020-02-05 20:02:42 +01:00
if ( posix_locale ) {
2020-05-22 18:52:26 +02:00
const int i = ( std : : intptr_t ) nl_langinfo_l ( _NL_TIME_WEEK_1STDAY , posix_locale . get ( ) ) ;
2022-04-06 08:37:19 +02:00
auto ymd = date : : year ( i / 10000 ) / ( i / 100 % 100 ) / ( i % 100 ) ;
auto wd = date : : weekday ( ymd ) ;
uint8_t j = * nl_langinfo_l ( _NL_TIME_FIRST_WEEKDAY , posix_locale . get ( ) ) ;
2020-02-05 20:02:42 +01:00
return wd + date : : days ( j - 1 ) ;
2019-02-24 09:25:34 +01:00
}
2020-02-05 20:02:42 +01:00
# endif
return date : : Sunday ;
2018-08-09 12:05:48 +02:00
}