diff --git a/app_config.json b/app_config.json new file mode 100644 index 0000000..a842cd3 --- /dev/null +++ b/app_config.json @@ -0,0 +1,4 @@ +{ + "licenser-server": "https://tnsoehasuhaons.beta.exd.sergiotarxz.me", + "debug": true +} diff --git a/exd-logo.png b/exd-logo.png new file mode 100644 index 0000000..76ef86a Binary files /dev/null and b/exd-logo.png differ diff --git a/exd.svg b/exd.svg index ac50b58..eeeeafe 100644 --- a/exd.svg +++ b/exd.svg @@ -7,10 +7,11 @@ viewBox="0 0 512 512" version="1.1" id="svg1" - inkscape:version="1.4 (e7c3feb100, 2024-10-09)" - sodipodi:docname="exd.svg" + sodipodi:docname="exd-final.svg" + inkscape:version="1.3.2 (091e20ef0f, 2023-11-25)" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:xlink="http://www.w3.org/1999/xlink" xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg"> - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + id="path132" + style="fill:#378c6d;fill-opacity:1;stroke-width:114.401;stroke-linecap:round;stroke-linejoin:round;paint-order:markers stroke fill" + d="m 161.18607,272.63285 -7.90117,8.87913 -5.7407,-6.45092 c -1.33434,1.44066 -2.16044,3.4001 -2.16044,5.57675 v 16.74012 c 0,4.43679 3.40156,8.00497 7.6311,8.00497 h 205.97018 c 4.22954,0 7.63109,-3.56818 7.63109,-8.00497 v -16.74012 c 0,-2.17665 -0.82615,-4.13609 -2.16049,-5.57675 l -5.74067,6.45092 -7.90114,-8.87913 -7.90116,8.87913 -7.90115,-8.87913 -7.90117,8.87913 -7.90114,-8.87913 -7.90117,8.87913 -7.90115,-8.87913 -7.90116,8.87913 -7.90115,-8.87913 -7.90116,8.87913 -7.90115,-8.87913 -7.90117,8.87913 -7.90114,-8.87913 -7.90117,8.87913 -7.90115,-8.87913 -7.90116,8.87913 -7.90115,-8.87913 -7.90117,8.87913 -7.90114,-8.87913 -7.90117,8.87913 -7.90114,-8.87913 -7.90117,8.87913 -7.90115,-8.87913 -7.90116,8.87913 z" + sodipodi:nodetypes="cccssssssccccccccccccccccccccccccccc" /> + id="path133" + style="fill:#136246;fill-opacity:1;stroke-width:114.401;stroke-linecap:round;stroke-linejoin:round;paint-order:markers stroke fill" + d="m 161.18607,276.74258 -7.90117,8.87912 -5.7407,-6.45091 c -1.33434,1.44067 -2.16044,3.40014 -2.16044,5.57676 v 15.96472 c 0,4.43673 3.40156,8.00496 7.6311,8.00496 h 205.97018 c 4.22954,0 7.63109,-3.56823 7.63109,-8.00496 v -15.96472 c 0,-2.17662 -0.82615,-4.13609 -2.16049,-5.57676 l -5.74067,6.45091 -7.90114,-8.87912 -7.90116,8.87912 -7.90115,-8.87912 -7.90117,8.87912 -7.90114,-8.87912 -7.90117,8.87912 -7.90115,-8.87912 -7.90116,8.87912 -7.90115,-8.87912 -7.90116,8.87912 -7.90115,-8.87912 -7.90117,8.87912 -7.90114,-8.87912 -7.90117,8.87912 -7.90115,-8.87912 -7.90116,8.87912 -7.90115,-8.87912 -7.90117,8.87912 -7.90114,-8.87912 -7.90117,8.87912 -7.90114,-8.87912 -7.90117,8.87912 -7.90115,-8.87912 -7.90116,8.87912 z" + sodipodi:nodetypes="cccssssssccccccccccccccccccccccccccc" /> + style="fill:url(#linearGradient144);fill-opacity:1;stroke:none;stroke-width:0.182682;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;paint-order:markers stroke fill" + d="m 9.5988037,20.108333 c 0,-1.286464 0.3197629,-2.116666 0.7756213,-2.116666 h 1.513594 v 2.116666 z" + id="path134" + sodipodi:nodetypes="ccccc" + inkscape:transform-center-x="1.4581046" + inkscape:transform-center-y="-4.3858528" + clip-path="url(#clipPath29)" + transform="matrix(14.931319,0,0,15.662846,3.1629451,10.340015)" /> + id="path135" + style="fill:url(#linearGradient143);fill-opacity:1;stroke:none;stroke-width:2.79371;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;paint-order:markers stroke fill" + inkscape:transform-center-x="-11.49783" + inkscape:transform-center-y="-2.1929348" + d="m 158.06883,292.14071 c 6.80652,0 11.01839,13.19126 11.01839,33.15303 v 99.45902 l 7.90116,-8.2882 7.90115,8.2882 7.90118,-8.2882 7.90114,8.2882 7.90117,-8.2882 7.90114,8.2882 7.90117,-8.2882 7.90115,8.2882 7.90116,-8.2882 7.90115,8.2882 7.90117,-8.2882 7.90114,8.2882 7.90117,-8.2882 7.90115,8.2882 7.90116,-8.2882 7.90114,8.2882 7.90116,-8.2882 7.90115,8.2882 7.90117,-8.2882 7.90114,8.2882 7.90117,-8.2882 7.90115,8.2882 7.90116,-8.2882 7.90115,8.2882 7.90116,-8.2882 7.90115,8.2882 v -99.45902 c 0,-19.96177 -4.21186,-33.15303 -11.0184,-33.15303 z" + sodipodi:nodetypes="cscccccccccccccccccccccccccccssc" /> + EXD - - - + style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:66.4819px;font-family:Cantarell;-inkscape-font-specification:'Cantarell Bold';text-align:justify;opacity:0.349358;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1.66206;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;paint-order:markers stroke fill" + x="240.62625" + y="390.69879" + id="text136" + transform="scale(0.97636847,1.0242035)">EXD + style="opacity:0.962206;fill:#959492;fill-opacity:1;stroke:none;stroke-width:15.5961;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1;paint-order:markers stroke fill" + d="m 319.2092,333.58203 v 8.28825 h 39.50577 v -8.28825 z" + id="path137" /> + + + + + + + + + + + + diff --git a/lib/Exd.pm b/lib/Exd.pm new file mode 100644 index 0000000..1e66197 --- /dev/null +++ b/lib/Exd.pm @@ -0,0 +1,72 @@ +package Exd; + +use v5.40.0; + +use strict; +use warnings; +use utf8; + +use Moo; +use JSON; +use Path::Tiny; +use UUID::URandom qw/create_uuid_string/; + +has config => ( is => 'lazy' ); + +sub _build_config($self) { + return JSON::from_json( $self->_config_file_name->slurp_utf8 ); +} + +sub _config_file_name($self) { + return path(__FILE__)->parent->parent->child('app_config.json'); +} + +sub licenser_server($self) { + return $self->config->{'licenser-server'}; +} + +sub debug($self) { + return $self->config->{debug}; +} + +sub uuid($self) { + require Exd::DB; + my $dbh = Exd::DB->connect; + my $key = 'licenser-uuid'; + my $option = + $dbh->selectrow_hashref( 'SELECT value FROM options WHERE name = ?;', + {}, $key ); + my $uuid; + if (defined $option) { + $uuid = $option->{value}; + } + if ( !defined $uuid ) { + $uuid = create_uuid_string(); + $dbh->do( 'INSERT OR IGNORE INTO options (name, value) VALUES (?, ?);', + {}, $key, $uuid ); + } + return $uuid; +} + +{ + my $key = 'activated'; + sub is_activated_license($self) { + require Exd::DB; + my $dbh = Exd::DB->connect; + my $option = + $dbh->selectrow_hashref( 'SELECT value FROM options WHERE name = ?;', + {}, $key ); + if (!defined $option) { + return 0; + } + return $option->{value}; + } + + sub activate_license($self) { + require Exd::DB; + my $dbh = Exd::DB->connect; + $dbh->do( 'INSERT OR IGNORE INTO options (name, value) VALUES (?, ?);', + {}, $key, 1 ); + } +} +1; diff --git a/lib/Exd/DeviceToCatPrinter.pm b/lib/Exd/DeviceToCatPrinter.pm index 31d2155..e55a711 100644 --- a/lib/Exd/DeviceToCatPrinter.pm +++ b/lib/Exd/DeviceToCatPrinter.pm @@ -56,7 +56,6 @@ sub _build__gatt($self) { sub try_to_connect($self) { my $device_api = $self->_device; - say $device_api->Connected; return if $device_api->Connected; $device_api->Connect; diff --git a/lib/Exd/Gui.pm b/lib/Exd/Gui.pm index 3be0a4b..31bdd16 100644 --- a/lib/Exd/Gui.pm +++ b/lib/Exd/Gui.pm @@ -15,6 +15,7 @@ use Glib::IO; use Glib::Object::Introspection; use Path::Tiny; use GD::Image; +use Mojo::UserAgent; use IO::Select; use Capture::Tiny qw/capture/; @@ -25,6 +26,7 @@ use Exd::FileFormat; use Exd::Gui::PrinterConfigure; use Exd::Gui::FontGallery; use Exd::Gui::Instance; +use Exd; use JSON; use Net::DBus; @@ -53,6 +55,12 @@ Glib::Object::Introspection->setup( package => 'Gtk4::Source', ); +Glib::Object::Introspection->setup( + basename => 'Adw', + version => '1', + package => 'Adw', +); + has _app => ( is => 'rw', ); has _select => ( is => 'lazy', ); @@ -74,10 +82,28 @@ has _first_time => ( is => 'rw', default => sub { 1 } ); has _last_instance_id => ( is => 'rw', default => sub { 0 } ); has parent_pid => ( is => 'rw' ); has instances => ( is => 'rw', default => sub { return {} } ); +has _gresources_path => ( is => 'lazy', ); +has _exd => ( is => 'lazy' ); -sub start( $self ) { - my $app = Gtk4::Application->new( 'me.sergiotarxz.Exd', [qw/default-flags handles-command-line handles-open/] ); - my $bus = Net::DBus->session; +sub _build__gresources_path($self) { + my $root = path(__FILE__)->parent->parent->parent; + my $gresources = $root->child('resources.gresource'); + 0 == system( 'which', 'glib-compile-resources' ) + && system( 'glib-compile-resources', $root->child('resources.xml') ); + if ( !-e $gresources ) { + { + die 'No gresources'; + } + } + return $gresources; +} + +sub start($self) { + Glib::IO::resources_register( + Glib::IO::Resource::load( $self->_gresources_path ) ); + my $app = Adw::Application->new( 'me.sergiotarxz.Exd', + [qw/default-flags handles-command-line handles-open/] ); + my $bus = Net::DBus->session; my $started = 0; eval { $bus->get_service('me.sergiotarxz.Exd'); @@ -87,7 +113,7 @@ sub start( $self ) { $self->_app($app); my $arguments = []; $app->signal_connect( - command_line => sub($app, $command_line) { + command_line => sub( $app, $command_line ) { $arguments = $command_line->get_arguments; my $exd_file = shift @$arguments; $self->_activate($exd_file); @@ -105,11 +131,9 @@ sub start( $self ) { } ); say @ARGV; - $app->run(\@ARGV); + $app->run( \@ARGV ); } - - sub _startup($self) { my $app = $self->_app; } @@ -120,6 +144,15 @@ sub send_packet_to_daemon( $self, $instance, $packet ) { $self->_write_to_script->flush; } +sub send_packet_check_if_activated( $self, $instance, $uuid) { + $self->send_packet_to_daemon($instance, { + type => 'check-activated', + data => { + uuid => $uuid, + } + }); +} + sub _daemon_runner($self) { my ( $read_from_script, $write_to_parent ); my ( $read_from_parent, $write_to_script ); @@ -165,12 +198,51 @@ sub _daemon_runner($self) { if ( $packet->{type} eq 'font-scan' ) { next; } + if ( $packet->{type} eq 'check-activated') { + $self->_on_check_activated($instance_id, $packet->{data}); + next; + } + warn 'Packet not recognized: '. Data::Dumper::Dumper $packet; } exit; } close $read_from_parent; } +sub _build__exd($self) { + require Exd; + return Exd->new; +} + +sub _on_check_activated($self, $instance_id, $data) { + my $pid = fork; + if (!$pid) { + while (1) { + eval { + my $url = $self->_exd->licenser_server.'/get_paid/'.$self->_exd->uuid; + my $ua = Mojo::UserAgent->new; + my $activated = $ua->get($url)->result->json; + if ($activated) { + say 'Program sucessfully activated, thank you!'; + $self->_exd->activate_license; + $self->send_packet_to_window($instance_id, { + type => 'activated!', + }); + exit; + } + }; + if ($@) { + warn $@; + } + sleep 5; + if (!kill 0, $self->parent_pid) { + exit; + } + } + exit; + } +} + sub send_packet_to_window( $self, $instance_id, $packet ) { $packet->{instance_id} = $instance_id; $self->_write_to_parent->say( JSON::to_json($packet) ); @@ -196,8 +268,8 @@ sub _on_save_packet( $self, $instance_id, $json ) { $self->_save( $instance_id, $exd, $dest_file ); }; if ($@) { - $self->_log($instance_id, $@); - $self->_send_safe_to_exit($instance_id, 1); + $self->_log( $instance_id, $@ ); + $self->_send_safe_to_exit( $instance_id, 1 ); } exit; } @@ -222,7 +294,7 @@ sub _on_run_script_packet( $self, $instance_id, $json ) { $json->@{ 'exd_dir', 'device', 'verbose' }; $device = $self->device_hash_to_object($device); - my ($read, $write); + my ( $read, $write ); pipe $read, $write; my $new_pid = fork; @@ -239,12 +311,12 @@ sub _on_run_script_packet( $self, $instance_id, $json ) { close $write; if ($verbose) { my $log_getter_pid = fork; - if (!$log_getter_pid) { - $self->_log($instance_id, '### STARTED EXECUTION ###'); - while (my $line = <$read>) { - $self->_log($instance_id, $line); + if ( !$log_getter_pid ) { + $self->_log( $instance_id, '### STARTED EXECUTION ###' ); + while ( my $line = <$read> ) { + $self->_log( $instance_id, $line ); } - $self->_log($instance_id, '### TERMINATED EXECUTION ###'); + $self->_log( $instance_id, '### TERMINATED EXECUTION ###' ); exit; } } @@ -353,12 +425,13 @@ sub _log( $self, $instance_id, $message ) { } ); } + sub _save( $self, $instance_id, $exd, $dest_file ) { $self->_log( $instance_id, "Pending save to $dest_file." ); - $self->_send_safe_to_exit($instance_id, 0); + $self->_send_safe_to_exit( $instance_id, 0 ); my $zip = $exd->to_zip; $zip->writeToFileNamed( '' . $dest_file ); - $self->_send_safe_to_exit($instance_id, 1); + $self->_send_safe_to_exit( $instance_id, 1 ); $self->_log( $instance_id, "Save completed to $dest_file." ); } diff --git a/lib/Exd/Gui/Instance.pm b/lib/Exd/Gui/Instance.pm index 1fef9bf..f30940f 100644 --- a/lib/Exd/Gui/Instance.pm +++ b/lib/Exd/Gui/Instance.pm @@ -12,13 +12,18 @@ use Glib; use Glib::IO; use Glib::Object::Introspection; use Path::Tiny; +use Mojo::UserAgent; -has app => ( is => 'rw', required => 1, weak_ref => 1 ); -has instance_id => ( is => 'rw', required => 1 ); -has window => ( is => 'rw' ); -has _editor => ( is => 'rw', ); -has _safe_to_exit => ( is => 'rw', default => sub { 1 } ); -has device => ( is => 'rw' ); +has app => ( is => 'rw', required => 1, weak_ref => 1 ); +has instance_id => ( is => 'rw', required => 1 ); +has window => ( is => 'rw' ); +has _editor => ( is => 'rw', ); +has _safe_to_exit => ( is => 'rw', default => sub { 1 } ); +has _transparent_avoid_input => ( is => 'rw' ); +has _paywall => ( is => 'rw' ); +has device => ( is => 'rw' ); +has _pay_url => ( is => 'lazy' ); +has _activated => ( is => 'rw' ); has file_format => ( is => 'rw', default => sub { @@ -46,7 +51,13 @@ has _tempdir_previews_guts => ( is => 'rw' ); has _can_open_printer_configure => ( is => 'rw', default => sub { 1 } ); has _preview_number => ( is => 'rw', default => sub { 0 } ); has _save_path => ( is => 'rw' ); -has _want_to_close => ( is => 'rw' ); +has _want_to_close => ( is => 'rw' ); +has _exd => ( is => 'lazy' ); +has _overlay => ( is => 'rw' ); + +sub _build__exd($self) { + return Exd->new; +} sub start( $self, $exd_file ) { my $win = $self->app->create_application_window; @@ -116,7 +127,7 @@ sub start( $self, $exd_file ) { $execute_log->set_editable(0); $execute_log->set_cursor_visible(0); - $win->set_title('Exd (Thermal Printer)'); + $win->set_title('Hiperthermia (Thermal Printer) ' . ($self->_exd->debug ? 'DEBUG' : '') ); $win->set_default_size( 1200, 900 ); $execute_log->set_hexpand(1); @@ -150,20 +161,192 @@ sub start( $self, $exd_file ) { ); $execute_log_window->set_child($execute_log); $box_vertical->append($execute_log_window); - $win->set_child($box_vertical); + my $overlay = Gtk4::Overlay->new; + $self->_overlay($overlay); + $overlay->set_child($box_vertical); + $self->_activated( $self->_exd->is_activated_license ); + if ( !$self->_activated ) { + my $uuid = $self->_exd->uuid; + my $instance_id = $self->instance_id; + $self->_add_timeout_paywall($overlay); + $self->app->send_packet_check_if_activated( $self, $uuid ); + } + $win->set_child($overlay); $win->present; } +sub _show_about_dialog($self) { + my $window = Gtk4::Window->new; + $window->set_default_size( 650, 650 ); + $window->set_transient_for( $self->window ); + $window->set_title('About Hiperthermia'); + my $picture = Gtk4::Picture->new; + $picture->set_property('width-request', 256); + $picture->set_property('height-request', 256); + $picture->set_filename(path(__FILE__)->parent->parent->parent->parent->child('exd-logo.png').''); + my $label = Gtk4::Label->new(undef); + $label->set_markup(<<"EOF"); +Welcome to Hiperthermia. + +This is a paid program but fully open source for everyone, +have a copy of the source code at our source repo under the AGPLv3 license. + +NO WARRANTY TO THE EXTENT ALLOWED BY LAW, but fell free to drop +\@sergiotarxz a message if you find bugs, issues or need support +to use this program. + +Our amazing contributors: + +Sergiotarxz (Developer): + Gitea + \@sergiotarxz in Telegram. + \@sergiotarxz\@owlcode.tech in XMPP. +(Open to freelance work and full time employement, +accepts ETH, BTC and Bank transfers) + +Lucidraws (Logo creator): + lintr.ee +(Open to graphic design comissions, +accepts ETH, BTC and Mercado Pago payments) +EOF + my $box = Gtk4::Box->new( 'vertical', 10 ); + $box->set_hexpand(1); + $box->set_vexpand(1); + $box->set_halign('center'); + $box->set_valign('center'); + $box->append($picture); + $box->append($label); + my $scroll = Gtk4::ScrolledWindow->new; + $scroll->set_vexpand(1); + $scroll->set_hexpand(1); + $scroll->set_child($box); + $window->set_child($scroll); + $window->present; +} + +sub _add_timeout_paywall( $self, $overlay ) { + if ( $self->_activated ) { + return 0; + } + if ( defined $self->_pay_url ) { + Glib::Timeout->add( + $self->_exd->debug ? 1_000 : 20_000, + sub { + my $box = Gtk4::Box->new( 'vertical', 10 ); + $self->_transparent_avoid_input($box); + $box->set_opacity(0.7); + $box->add_css_class('avoid-main-input'); + $box->set_halign('fill'); + $box->set_valign('fill'); + my $paywall = Gtk4::Box->new( 'vertical', 10 ); + $self->_paywall($paywall); + + $paywall->set_property( 'width-request', 800 ); + $paywall->set_property( 'height-request', 800 ); + $paywall->add_css_class('paywall'); + my $inner_paywall_box = Gtk4::Box->new( 'vertical', 10 ); + my $title = Gtk4::Label->new('This program is not activated'); + my $activate = Gtk4::Button->new_with_label('Pay and activate'); + my $about = Gtk4::Button->new_with_label('More about the program'); + my $remind_me_later = + Gtk4::Button->new_with_label('Remind me later'); + $about->signal_connect( + clicked => sub { + $self->_show_about_dialog; + } + ); + $activate->signal_connect( + clicked => sub { + my $launcher = + Gtk4::UriLauncher->new( $self->_pay_url ); + $launcher->launch( $self->window, undef, undef ); + } + ); + $remind_me_later->signal_connect( + 'clicked' => sub { + $overlay->remove_overlay($paywall); + $overlay->remove_overlay($box); + $self->_paywall(undef); + $self->_transparent_avoid_input(undef); + $self->_add_timeout_paywall($overlay); + } + ); + my $picture = Gtk4::Image->new_from_file(path(__FILE__)->parent->parent->parent->parent->child('exd-logo.png').''); + $picture->set_pixel_size(256); + $inner_paywall_box->append($picture); + $inner_paywall_box->append($title); + if ($self->_exd->debug) { + my $debug_message = Gtk4::Label->new('This is debug compilation, do not use your real payment data but a Stripe test card'); + $inner_paywall_box->append($debug_message); + } + $inner_paywall_box->append($activate); + $inner_paywall_box->append($remind_me_later); + $inner_paywall_box->append($about); + $inner_paywall_box->set_valign('center'); + $inner_paywall_box->set_vexpand(1); + $paywall->set_vexpand(1); + $inner_paywall_box->set_halign('center'); + $paywall->set_halign('center'); + $paywall->set_valign('fill'); + $overlay->add_overlay($box); + $overlay->add_overlay($paywall); + $paywall->append($inner_paywall_box); + Glib::Timeout->add( + 1_000, + sub { + return 0 if !defined $self->_transparent_avoid_input; + if ($self->_activated) { + if ( defined $self->_paywall ) { + $self->_overlay->remove_overlay( $self->_paywall ); + } + if ( defined $self->_transparent_avoid_input ) { + $self->_overlay->remove_overlay( + $self->_transparent_avoid_input ); + } + return 0; + } + return 1; + } + ); + return 0; + } + ); + } +} + +sub _build__pay_url($self) { + my $url; + eval { + my $ua = Mojo::UserAgent->new; + $url = $ua->get( + $self->_exd->licenser_server . '/get_pay_url/' . $self->_exd->uuid ) + ->result->json->{url}; + }; + if ($@) { + warn $@; + } + return $url; +} + sub _create_popover_menu( $self, $box ) { my $menu_model = Glib::IO::Menu->new; my $file_menu = Glib::IO::Menu->new; + my $help_menu = Glib::IO::Menu->new; my $open_action = Glib::IO::SimpleAction->new( 'open.' . $self->instance_id, undef ); my $save_action = Glib::IO::SimpleAction->new( 'save.' . $self->instance_id, undef ); my $save_as_action = Glib::IO::SimpleAction->new( 'save-as.' . $self->instance_id, undef ); + my $about_action = + Glib::IO::SimpleAction->new( 'about.' . $self->instance_id, undef ); my $app = $self->app->_app; + $about_action->signal_connect( + 'activate', + sub { + $self->_show_about_dialog; + } + ); $open_action->signal_connect( 'activate', sub { @@ -185,10 +368,13 @@ sub _create_popover_menu( $self, $box ) { $app->add_action($open_action); $app->add_action($save_action); $app->add_action($save_as_action); + $app->add_action($about_action); $file_menu->append( 'Open', 'app.open.' . $self->instance_id ); $file_menu->append( 'Save', 'app.save.' . $self->instance_id ); $file_menu->append( 'Save as', 'app.save-as.' . $self->instance_id ); + $help_menu->append( 'About', 'app.about.' . $self->instance_id); $menu_model->append_submenu( 'File', $file_menu ); + $menu_model->append_submenu( 'Help', $help_menu ); my $popover = Gtk4::PopoverMenuBar->new_from_model($menu_model); $box->append($popover); } @@ -243,7 +429,7 @@ sub _populate_editor_and_preview( $self, $box_vertical ) { sub _update_editor_buffer($self) { my $editor = $self->_editor; - if (defined $editor) { + if ( defined $editor ) { my $buffer = $editor->get_buffer(); $buffer->set_text( $self->file_format->get_script, -1 ); } @@ -324,6 +510,15 @@ sub receive_packet( $self, $packet ) { } return; } + if ( $packet->{type} eq 'activated!' ) { + $self->mark_activated; + return; + } + warn 'Packet not recognized ' . Data::Dumper::Dumper $packet; +} + +sub mark_activated($self) { + $self->_activated(1); } sub _add_to_log( $self, $text ) { @@ -562,7 +757,7 @@ sub _open_file( $self, $file ) { $self->_save_path($file); my $window = $self->window; $window->set_title( - "Exd (Thermal Printer) " . path( $self->_save_path )->basename ); + "Hiperthermia (Thermal Printer) " . ($self->_exd->debug ? 'DEBUG' : '') . path( $self->_save_path )->basename ); $self->file_format( Exd::FileFormat->from_zip_file($file) ); $self->_update_editor_buffer; } diff --git a/lib/Exd/Gui/PrinterConfigure.pm b/lib/Exd/Gui/PrinterConfigure.pm index 366a4a3..6cbcac7 100644 --- a/lib/Exd/Gui/PrinterConfigure.pm +++ b/lib/Exd/Gui/PrinterConfigure.pm @@ -17,18 +17,18 @@ use Exd::DeviceToBluetooth; use Exd::DeviceToRawFile; use Exd::DeviceToCatPrinter; -has app => (is => 'ro', required => 1); -has instance => (is => 'ro', required => 1); -has on_close => ( is => 'ro', required => 1 ); -has _pid_daemon => (is => 'rw'); -has _read_from_daemon => (is => 'rw'); -has _write_to_app => (is => 'rw'); -has _select => ( is => 'lazy', ); -has _window => ( is => 'rw' ); +has app => ( is => 'ro', required => 1 ); +has instance => ( is => 'ro', required => 1 ); +has on_close => ( is => 'ro', required => 1 ); +has _pid_daemon => ( is => 'rw' ); +has _read_from_daemon => ( is => 'rw' ); +has _write_to_app => ( is => 'rw' ); +has _select => ( is => 'lazy', ); +has _window => ( is => 'rw' ); has _bluetooth_printers => ( is => 'rw', default => sub { [] } ); -has _usb_printers => ( is => 'rw', default => sub { [] }); -has _cat_printers => ( is => 'rw', default => sub { [] }); -has _stop_timeout => ( is => 'rw' ); +has _usb_printers => ( is => 'rw', default => sub { [] } ); +has _cat_printers => ( is => 'rw', default => sub { [] } ); +has _stop_timeout => ( is => 'rw' ); sub _build__select($self) { return IO::Select->new; @@ -38,123 +38,208 @@ sub _read_bluetooth_printers($self) { my @fhs = $self->_select->can_read(0); for my $fh (@fhs) { $fh->blocking(0); - while (defined(my $line = <$fh>)) { - my @return = map {$self->app->device_hash_to_object($_)} @{JSON::from_json($line)}; - $self->_bluetooth_printers(\@return); - Exd::DeviceToBluetooth->cache_printers($self->_bluetooth_printers); + while ( defined( my $line = <$fh> ) ) { + my @return = map { $self->app->device_hash_to_object($_) } + @{ JSON::from_json($line) }; + $self->_bluetooth_printers( \@return ); + Exd::DeviceToBluetooth->cache_printers( + $self->_bluetooth_printers ); } } } -sub _usb_printer_print_to_box($self, $device, $box) { +sub _usb_printer_print_to_box( $self, $device, $box ) { my $window = $self->_window; - my $button = Gtk4::Button->new_with_label($device->output_file); - $button->signal_connect('clicked', sub { - $self->instance->device($device); - $window->close; - }); + my $button = Gtk4::Button->new_with_label( $device->output_file ); + $button->signal_connect( + 'clicked', + sub { + $self->instance->device($device); + $window->close; + } + ); $box->append($button); } -sub _cat_printer_print_to_box($self, $device, $box) { +sub _cat_printer_print_to_box( $self, $device, $box ) { my $window = $self->_window; - my $button = Gtk4::Button->new_with_label($device->path); - $button->signal_connect('clicked', sub { - $self->instance->device($device); - $window->close; - }); + my $button = Gtk4::Button->new_with_label( $device->path ); + $button->signal_connect( + 'clicked', + sub { + $self->instance->device($device); + $window->close; + } + ); $box->append($button); } -sub _bluetoth_printer_print_to_box($self, $device, $box) { +sub _bluetoth_printer_print_to_box( $self, $device, $box ) { my $window = $self->_window; - my $button = Gtk4::Button->new_with_label($device->name.':'.$device->address); - $button->signal_connect('clicked', sub { - $self->instance->device($device); - $window->close; - }); + my $button = + Gtk4::Button->new_with_label( $device->name . ':' . $device->address ); + $button->signal_connect( + 'clicked', + sub { + $self->instance->device($device); + $window->close; + } + ); $box->append($button); } + sub _read_usb_printers($self) { - my @printers = map { Exd::DeviceToRawFile->new(output_file => $_) } glob '/dev/usb/lp*'; - $self->_usb_printers(\@printers); + my @printers = map { Exd::DeviceToRawFile->new( output_file => $_ ) } + glob '/dev/usb/lp*'; + $self->_usb_printers( \@printers ); +} + +sub _device_api( $self, $path ) { + my $session = Net::DBus->system( private => 1 ); + my $service = $session->get_service('org.bluez'); + return $service->get_object( $path, 'org.bluez.Device1' ); +} + +has _pid_read_special_cat_printer => ( is => 'rw' ); + +sub _try_to_connect_difficult_cat_printer_devices( $self, $key, $device ) { + if ( defined $device && $device->{Name} eq 'SC05-6F4C' ) { + my $device_api = $self->_device_api($key); + if ( $device_api->Connected ) { + return; + } + $device_api->get_service->get_bus->get_connection->disconnect; + if ( defined $self->_pid_read_special_cat_printer && kill 0, + $self->_pid_read_special_cat_printer ) + { + # Ignoring if there is a fork running. + return; + } + $self->_pid_read_special_cat_printer(fork); + if ( !$self->_pid_read_special_cat_printer ) { + my $device_api = $self->_device_api($key); + eval { + $device_api->CancelPairing; + sleep 1; + }; + if ($@) { + warn $@; + } + eval { + $device_api->Pair; + sleep 1; + }; + if ($@) { + warn $@; + } + eval { + $device_api->Connect; + sleep 1; + }; + if ($@) { + warn $@; + } + $device_api->get_service->get_bus->get_connection->disconnect; + exit; + } + } } sub _read_cat_printers($self) { - my $session = Net::DBus->system; - my $service = $session->get_service('org.bluez'); - my $path = '/'; + my $session = Net::DBus->system( private => 1 ); + my $service = $session->get_service('org.bluez'); + my $path = '/'; my $object_manager_interface = 'org.freedesktop.DBus.ObjectManager'; - my $object = $service->get_object($path, $object_manager_interface); + my $object = $service->get_object( $path, $object_manager_interface ); use Data::Dumper; - my $items = $object->GetManagedObjects; + my $items = $object->GetManagedObjects; + $object->get_service->get_bus->get_connection->disconnect; my @paths_tx; - for my $key (keys %$items) { - my $item = $items->{$key}{'org.bluez.GattCharacteristic1'}; - if (!defined $item) { + + for my $key ( keys %$items ) { + my $device = $items->{$key}{'org.bluez.Device1'}; + my $item = $items->{$key}{'org.bluez.GattCharacteristic1'}; + + { + next if defined $item; + $self->_try_to_connect_difficult_cat_printer_devices( $key, + $device ); + } + if ( !defined $item ) { next; } - if ($item->{UUID} eq '0000ae01-0000-1000-8000-00805f9b34fb') { + if ( $item->{UUID} eq '0000ae01-0000-1000-8000-00805f9b34fb' ) { push @paths_tx, $key; } } - $self->_cat_printers([map { Exd::DeviceToCatPrinter->new(path => $_) } sort {$a cmp $b} @paths_tx]); + $self->_cat_printers( + [ + map { Exd::DeviceToCatPrinter->new( path => $_ ) } + sort { $a cmp $b } @paths_tx + ] + ); } sub start($self) { $self->_start_daemon_bluetooth_search; my $parent_window = $self->instance->window; - my $window = Gtk4::Window->new; + my $window = Gtk4::Window->new; $self->_window($window); $window->set_default_size( 600, 600 ); - $window->signal_connect('close-request', sub{ - $self->_on_close; - return 0; - }); - $window->set_title('Printer Selection'); - $window->set_child(Gtk4::Label->new('Searching for printers...')); - $self->_select->add($self->_read_from_daemon); - $self->_bluetooth_printers(Exd::DeviceToBluetooth->devices_from_cache); - Glib::Timeout->add(1000, sub { - return 0 if $self->_stop_timeout; - eval { - $self->_read_usb_printers; - $self->_read_cat_printers; - $self->_read_bluetooth_printers; - $self->_update_box; - }; - if ($@) { - die $@; + $window->signal_connect( + 'close-request', + sub { + $self->_on_close; + return 0; } - return 1; - }); + ); + $window->set_title('Printer Selection'); + $window->set_child( Gtk4::Label->new('Searching for printers...') ); + $self->_select->add( $self->_read_from_daemon ); + $self->_bluetooth_printers( Exd::DeviceToBluetooth->devices_from_cache ); + Glib::Timeout->add( + 1000, + sub { + return 0 if $self->_stop_timeout; + eval { + $self->_read_usb_printers; + $self->_read_cat_printers; + $self->_read_bluetooth_printers; + $self->_update_box; + }; + if ($@) { + warn $@; + } + return 1; + } + ); $window->present; - $window->set_transient_for($self->instance->window); + $window->set_transient_for( $self->instance->window ); } sub _update_box($self) { - my $window = $self->_window; - my $box = Gtk4::Box->new( 'vertical', 10 ); + my $window = $self->_window; + my $box = Gtk4::Box->new( 'vertical', 10 ); my $bluetooth_printers = $self->_bluetooth_printers; - for my $device ($bluetooth_printers->@*) { - $self->_bluetoth_printer_print_to_box($device, $box); + for my $device ( $bluetooth_printers->@* ) { + $self->_bluetoth_printer_print_to_box( $device, $box ); } - for my $device ($self->_usb_printers->@*) { - $self->_usb_printer_print_to_box($device, $box); + for my $device ( $self->_usb_printers->@* ) { + $self->_usb_printer_print_to_box( $device, $box ); } - for my $device ($self->_cat_printers->@*) { - $self->_cat_printer_print_to_box($device, $box); + for my $device ( $self->_cat_printers->@* ) { + $self->_cat_printer_print_to_box( $device, $box ); } $window->set_child($box); } sub _start_daemon_bluetooth_search($self) { - my ($read, $write); + my ( $read, $write ); pipe $read, $write; $self->_read_from_daemon($read); $self->_write_to_app($write); my $pid = fork; - if (!$pid) { + if ( !$pid ) { close $read; while (1) { $self->_daemon_bluetooth_search_iteration; @@ -166,27 +251,37 @@ sub _start_daemon_bluetooth_search($self) { } sub _daemon_bluetooth_search_iteration($self) { -# say 'Listing bluetooth devices'; - if (!kill 0, $self->app->parent_pid) { + + # say 'Listing bluetooth devices'; + if ( !kill 0, $self->app->parent_pid ) { say 'Parent died at bluetooth search iteration, exiting...'; exit 1; } my $devices = Net::Bluetooth::get_remote_devices; my @return; - for my $address (keys %$devices) { - say "Detected $devices->{$address}:$address, looking if it supports Serial Port..."; - my $search = Net::Bluetooth::sdp_search($address, "1101", ""); - if (defined $search && defined $search->{RFCOMM}) { - say "$devices->{$address}:$address supports Serial Port and may be a printer, the port is: $search->{RFCOMM}"; + for my $address ( keys %$devices ) { + next if $devices->{$address} eq 'SC05-6F4C'; + say +"Detected $devices->{$address}:$address, looking if it supports Serial Port..."; + my $search = Net::Bluetooth::sdp_search( $address, "1101", "" ); + if ( defined $search && defined $search->{RFCOMM} ) { + say +"$devices->{$address}:$address supports Serial Port and may be a printer, the port is: $search->{RFCOMM}"; say $devices->{$address}; - push @return, Exd::DeviceToBluetooth->new(name => $devices->{$address}, address => $address, port => $search->{RFCOMM}); + push @return, + Exd::DeviceToBluetooth->new( + name => $devices->{$address}, + address => $address, + port => $search->{RFCOMM} + ); } } -# say 'Finishing listing bluetooth devices'; + + # say 'Finishing listing bluetooth devices'; @return = map { $_->serialize } sort { $a->name cmp $b->name } @return; - $self->_write_to_app->say(JSON::to_json(\@return)); + $self->_write_to_app->say( JSON::to_json( \@return ) ); $self->_write_to_app->flush; - + } sub _on_close($self) { diff --git a/lib/Exd/Printer.pm b/lib/Exd/Printer.pm index c34b737..b937047 100644 --- a/lib/Exd/Printer.pm +++ b/lib/Exd/Printer.pm @@ -121,7 +121,7 @@ sub print($self) { if system( qw/convert/, $image_file_in, - qw/-resize 384x -brightness-contrast +10 -dither FloydSteinberg -remap pattern:gray50/, + qw/-resize 384x -brightness-contrast +0.8 -dither FloydSteinberg -remap pattern:gray50/, $image_file_out ); $image_final = Exd::Utils::get_gd_image( $image_file_out . '' ); diff --git a/style-dark.css b/style-dark.css new file mode 100644 index 0000000..6dd8ff1 --- /dev/null +++ b/style-dark.css @@ -0,0 +1,7 @@ +headerbar box { + background: #015544; +} + +box.paywall { + background: #010F0C; +} diff --git a/style.css b/style.css new file mode 100644 index 0000000..f81adc0 --- /dev/null +++ b/style.css @@ -0,0 +1,15 @@ +headerbar box { + background: #48E1C0; +} + +button { + min-width: 0; +} + +box.paywall { + background: white; +} + +box.avoid-main-input { + background: black; +}