aboutsummaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
Diffstat (limited to 'lib')
-rw-r--r--lib/Arctica/Telekinesis/Application/Gtk3.pm524
1 files changed, 524 insertions, 0 deletions
diff --git a/lib/Arctica/Telekinesis/Application/Gtk3.pm b/lib/Arctica/Telekinesis/Application/Gtk3.pm
new file mode 100644
index 0000000..e3fb3b8
--- /dev/null
+++ b/lib/Arctica/Telekinesis/Application/Gtk3.pm
@@ -0,0 +1,524 @@
+################################################################################
+# _____ _
+# |_ _| |_ ___
+# | | | ' \/ -_)
+# |_| |_||_\___|
+# _ _ ____ _ _
+# / \ _ __ ___| |_(_) ___ __ _ | _ \ _ __ ___ (_) ___ ___| |_
+# / _ \ | '__/ __| __| |/ __/ _` | | |_) | '__/ _ \| |/ _ \/ __| __|
+# / ___ \| | | (__| |_| | (_| (_| | | __/| | | (_) | | __/ (__| |_
+# /_/ \_\_| \___|\__|_|\___\__,_| |_| |_| \___// |\___|\___|\__|
+# |__/
+# The Arctica Modular Remote Computing Framework
+#
+################################################################################
+#
+# Copyright (C) 2015-2016 The Arctica Project
+# http://arctica-project.org/
+#
+# This code is dual licensed: strictly GPL-2 or AGPL-3+
+#
+# GPL-2
+# -----
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; version 2 of the License.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the
+# Free Software Foundation, Inc.,
+#
+# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# AGPL-3+
+# -------
+# This programm is free software; you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This programm is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program; if not, write to the
+# Free Software Foundation, Inc.,
+# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# Copyright (C) 2015-2016 Guangzhou Nianguan Electronics Technology Co.Ltd.
+# <opensource@gznianguan.com>
+# Copyright (C) 2015-2016 Mike Gabriel <mike.gabriel@das-netzwerkteam.de>
+#
+################################################################################
+package Arctica::Telekinesis::Core::AppGtk3;
+use strict;
+use Exporter qw(import);
+use Data::Dumper;
+use Arctica::Core::JABus::Socket;
+use Arctica::Core::eventInit qw( genARandom BugOUT );
+use Gtk3 -init;
+use Glib::Object::Introspection;
+Glib::Object::Introspection->setup(
+ basename => "GdkX11",
+ version => "3.0",
+ package => "Gtk3::Gdk");
+# Be very selective about what (if any) gets exported by default:
+our @EXPORT = qw();
+# And be mindfull of what we lett the caller request here too:
+our @EXPORT_OK = qw();
+
+my $arctica_core_object;
+# Remote types: (NOTE TO $SELF)
+# 1. ULTIMATE X (track nothing we're remotely X embedded!!)
+# 2. FLOAT
+# 2.1. Float_embed (We're embedded in the remote session window but need to dodge other applications in the session)
+# 2.2. Float_free (Free floating natively in the client OS... need to also track session window states)
+# 3. fullscreen (For platforms where fullscreen is the only way to get things done... So do we need to track anything?)
+# 4. fallback_ss (serverside rendering X embedded loop back carrier window on the server side)
+my $TEKIUNO = 0;
+
+sub new {
+ BugOUT(9,"TeKi AppGtk3 new->ENTER");
+ if ($TEKIUNO eq 1) {die("Yeah... you probably don't want two of these in the same application!?!");} else {$TEKIUNO = 1;}
+ my $class_name = $_[0];
+ $arctica_core_object = $_[1];
+ my $conf = $_[2];
+ my $the_tmpdir = $arctica_core_object->{'a_dirs'}{'tmp_adir'};
+ my $teki_tmpdir = "$the_tmpdir/teki";
+
+ unless (-d $teki_tmpdir) {
+ die("TeKi AppGtk3 unable to locate dir $teki_tmpdir ($!)");
+ }
+
+ my $self = {
+ tmpdir => $teki_tmpdir,
+ isArctica => 1, # Declare that this is a Arctica "something"
+ aobject_name => "telekinesis_appcore",
+ };
+ bless($self, $class_name);
+
+ if ($conf->{'services'}) {
+ foreach my $key (keys $conf->{'services'}) {
+ if ($key =~ /^([a-z]{4,24})$/) {
+ my $service_name = $1;
+ if ($conf->{'services'}{$service_name}) {
+ $self->{'services'}{$service_name} = $conf->{'services'}{$service_name}
+ }
+ }
+ }
+ } else {
+ die("TeKi AppGtk3 new: at least one service must be requested!");
+ }
+
+
+ $self->{'application_id'} = genARandom('id');
+ $self->{'state'}{'active'} = 0;
+ $self->{'teki_socket_id'} = $self->_get_tmp_local_socket_id;
+
+ $self->{'teki_socket'} = Arctica::Core::JABus::Socket->new($arctica_core_object,{
+ type => "unix",
+ destination => "local",
+ is_client => 1,
+ connect_to => $self->{'teki_socket_id'},
+ handle_in_dispatch => {
+ appinit => sub {$self->_app_init();},
+# appcom => \&my_Own_Sub1,
+# appreg => \&my_Own_Sub1,
+# tekictrl => \&my_Own_Sub1,
+ },
+ hooks => {
+ on_ready => sub {$self->_appside_app_reg();},
+ },
+ });
+
+ $arctica_core_object->{'aobj'}{'telekinesis_appcore'} = \$self;
+
+ return $self;
+ BugOUT(9,"TeKi AppGtk3 new->DONE");
+}
+
+
+
+sub _appside_app_reg {
+ BugOUT(9,"TeKi AppGtk3 Registration->ENTER");
+ my $self = $_[0];
+ $self->{'teki_socket'}->client_send('appreg',{
+ service_req => $self->{'services'},
+ });
+ BugOUT(9,"TeKi AppGtk3 Registration->DONE");
+}
+
+################################################################################
+# GTK3 RELATED STUFF
+sub _app_init {
+ BugOUT(8,"Arctica::Telekinesis::Core::Gtk3 app_init->START");
+ my $self = $_[0];
+ my $init_data = {
+ action => 'app_init',
+ };
+ if ($self->{'windows'}) {
+# print "GOT WINDERS!\n";
+ foreach my $twid (keys $self->{'windows'}) {
+# print "W:\t$twid\n";
+
+ $init_data->{'windows'}{$twid}{'state'} = $self->{'windows'}{$twid}{'state'};
+# $init_data->{'windows'}{$twid}{'targets'} = $self->{'windows'}{$twid}{'targets'};
+ }
+ }
+
+ if ($self->{'targets'}) {
+# print "GOT TARGETS!\n";
+ foreach my $ttid (keys $self->{'targets'}) {
+# print "T:\t$ttid\n";
+ $init_data->{'targets'}{$ttid}{'state'} = $self->{'targets'}{$ttid}{'state'};
+ $init_data->{'targets'}{$ttid}{'window'} = $self->{'targets'}{$ttid}{'window'};
+ $init_data->{'targets'}{$ttid}{'service'} = $self->{'targets'}{$ttid}{'service'};
+# $targets->{};
+ }
+ }
+
+ #print "HOLLA YOLLA:\n\n",Dumper($tekiappco);
+# $self->{'teki_socket'}->client_send('appreg',{
+# service_req => "blablabla"
+# });
+ $self->{'teki_socket'}->client_send('appctrl',$init_data);
+
+ BugOUT(9,"Arctica::Telekinesis::Core::Gtk3 app_init->DONE");
+}
+
+sub _cinit_window {
+ my $self = $_[0];
+ my $twid = $_[1];
+ $self->{'teki_socket'}->client_send('appcom',{
+ action => 'init_w',
+ wstate => $self->{'windows'}{$twid}{'state'},
+ });
+}
+
+sub _cinit_target {
+ my $self = $_[0];
+ my $ttid = $_[1];
+ $self->{'teki_socket'}->client_send('appcom',{
+ action => 'init_t',
+ tstate => $self->{'targets'}{$ttid}{'state'},
+ });
+}
+
+
+sub check_n_send {
+ my $self = $_[0];
+ my %to_send;
+ if ($self->{'_tmp'}) {
+ print "SND\t",time,"\t:\n";
+ print Dumper($self->{'_tmp'});
+ if (1 eq 1) {
+ my $MODIFIED = 0;
+ if ($self->{'_tmp'}{'w'}) {
+ foreach my $id (keys $self->{'_tmp'}{'w'}) {
+ print "\tID: $id\n";
+ foreach my $val (keys $self->{'_tmp'}{'w'}{$id}) {
+ print "\t\tVAL: $val\n";
+ if ($self->{'_tmp'}{'w'}{$id}{$val} eq $self->{'_sent'}{'w'}{$id}{$val}) {
+ delete $self->{'_tmp'}{'w'}{$id}{$val};
+
+ print "\t\t>> REMOVED W $id $val\n";
+ my $keycnt = keys $self->{'_tmp'}{'w'}{$id};
+ print "\t\t\t>>>KEYS: $keycnt\n";
+ if ($keycnt < 1) {
+ delete $self->{'_tmp'}{'w'}{$id};
+ }
+ } else {
+ $self->{'_sent'}{'w'}{$id}{$val} = $self->{'_tmp'}{'w'}{$id}{$val};
+ }
+ }
+
+ }
+ my $keycnt = keys $self->{'_tmp'}{'w'};
+ print "\t\t\t>>>KEYS: $keycnt\n";
+ if ($keycnt < 1) {
+ delete $self->{'_tmp'}{'w'};
+ } else {$MODIFIED = 1;}
+ }
+ if ($self->{'_tmp'}{'t'}) {
+ foreach my $id (keys $self->{'_tmp'}{'t'}) {
+ print "\tID: $id\n";
+ foreach my $val (keys $self->{'_tmp'}{'t'}{$id}) {
+ print "\t\tVAL: $val\n";
+ if ($self->{'_tmp'}{'t'}{$id}{$val} eq $self->{'_sent'}{'t'}{$id}{$val}) {
+ delete $self->{'_tmp'}{'t'}{$id}{$val};
+
+ print "\t\t>> REMOVED T $id $val\n";
+ my $keycnt = keys $self->{'_tmp'}{'t'}{$id};
+ print "\t\t\t>>>KEYS: $keycnt\n";
+ if ($keycnt < 1) {
+ delete $self->{'_tmp'}{'t'}{$id};
+ }
+ } else {
+ $self->{'_sent'}{'t'}{$id}{$val} = $self->{'_tmp'}{'t'}{$id}{$val};
+ }
+ }
+
+ }
+ my $keycnt = keys $self->{'_tmp'}{'t'};
+ print "\t\t\t>>>KEYS: $keycnt\n";
+ if ($keycnt < 1) {
+ delete $self->{'_tmp'}{'t'};
+ } else {$MODIFIED = 1;}
+ }
+ if ($MODIFIED eq 1) {
+ print "\tMODIFIED!\n";
+ print Dumper($self->{'_tmp'});
+ $self->{'teki_socket'}->client_send('appctrl',{
+ action => 'state_change',
+ data => $self->{'_tmp'},
+ });
+ } else {
+ print "\tNOT MODIFIED!\n";
+ }
+
+ }
+ $self->{'_tmp'} = undef;
+ }
+ my $cnt = keys %to_send;
+# print "KEY CNT:\t$cnt\n";
+ if ($self->{'windows'}) {
+# print "GOT WINDERS!\n";
+ foreach my $twid (keys $self->{'windows'}) {
+# print "W:\t$twid\n";
+# if ($self->{'windows'})
+ }
+ }
+ if ($self->{'targets'}) {
+# print "GOT TARGETS!\n";
+ foreach my $ttid (keys $self->{'targets'}) {
+# print "T:\t$ttid\n";
+ }
+ }
+}
+
+sub add_window {
+ my $self = $_[0];
+ my $new_window = $_[1];
+ $new_window->set_title("TeKi TRACKED");
+ my $new_id = genARandom('id');
+ $self->{'windows'}{$new_id}{'thewindow'} = $new_window;
+ $new_window->signal_connect(event => sub {$self->_handle_window_event($_[0],$_[1],$new_id);return 0;});
+ return $new_id;
+}
+
+sub rm_window {
+ my $self = $_[0];
+ my $rm_wid = $_[1];
+ if ($self->{'windows'}{$rm_wid}) {
+ delete($self->{'windows'}{$rm_wid}) or warn("Unable to untrack window [$rm_wid] ($!)");
+ }
+}
+
+sub new_target {
+ my $self = $_[0];
+ my $wid = $_[1];
+ my $target_service = lc($_[2]);# FIX ME! Do something smart to check if we requested a valid and supported service type.
+ if ($self->{'windows'}{$wid}) {
+ my $new_tid = genARandom('id');
+ $self->{'windows'}{$wid}{'targets'}{$new_tid} = 1;
+ $self->{'targets'}{$new_tid}{'window'} = $wid;
+ $self->{'targets'}{$new_tid}{'widget'} = Gtk3::Socket->new;
+ $self->{'targets'}{$new_tid}{'widget'}->signal_connect(realize => sub {print "REALIZED!!!",Dumper(@_),"XID:",
+ $self->{'targets'}{$new_tid}{'widget'}->get_id,"\n";$self->{'targets'}{$new_tid}{'realized'} = 1;return 0;});
+ $self->{'targets'}{$new_tid}{'widget'}->signal_connect(unrealize => sub {print "UN-REALIZED WTF MAN!!!",Dumper(@_),"\n";$self->{'targets'}{$new_tid}{'realized'} = 0;return 0;});
+ $self->{'targets'}{$new_tid}{'widget'}->signal_connect(visibility_notify_event => sub {$self->_handle_target_viz_event($new_tid,$_[1]);return 0;});
+ $self->{'targets'}{$new_tid}{'widget'}->signal_connect(size_allocate => sub {$self->_handle_target_geometry_event($new_tid,@_);return 0;});
+ $self->{'targets'}{$new_tid}{'service'} = $target_service;
+# $self->{'targets'}{$new_tid}{'widget'}->signal_connect(event => sub {$self->_handle_target_event($_[0],$_[1],$new_tid);return 0;});
+ return $new_tid;
+ } else {
+ die("You need to provide the TeKi WID of a predeclared application window");
+ }
+}
+
+sub get_widget {
+ my $self = $_[0];
+ if ($self->{'targets'}{$_[1]}{'widget'}) {
+ return $self->{'targets'}{$_[1]}{'widget'};
+ } else {
+ die("WTF!!");
+ }
+}
+
+sub _set_tmp {
+ my $self = $_[0];
+ my $w_or_t = $_[1];
+ my $id = $_[2];
+ my $what = $_[3];
+ my $value = $_[4];
+ if ($w_or_t eq "w") {
+# print "SET TMP 'W'!!!\n";
+ if ($self->{'windows'}{$id}{'thewindow'}) {
+ if ($what eq 'geometry') {
+# print "GEOMETRY!!!\n";
+ my ($os_x,$os_y) = $self->{'windows'}{$id}{'thewindow'}->get_position;
+ my ($of_x,$of_y,$width,$height) = $self->{'windows'}{$id}{'thewindow'}->get_window->get_geometry;
+# os = on screen position
+# of = offset (accounting for window decoration stuffs).
+ # Experimental if this is all we need to transmit remove the os+of stuff
+ $self->{'_tmp'}{'w'}{$id}{'x'} = ($os_x + $of_x);
+ $self->{'_tmp'}{'w'}{$id}{'y'} = ($os_y + $of_y);
+
+ # First stick the values into "_tmp"
+# $self->{'_tmp'}{'w'}{$id}{'os_x'} = $os_x;
+# $self->{'_tmp'}{'w'}{$id}{'os_y'} = $os_y;
+# $self->{'_tmp'}{'w'}{$id}{'of_x'} = $of_x;
+# $self->{'_tmp'}{'w'}{$id}{'of_y'} = $of_y;
+
+ # Then put them somewhere more "permanent" (mostly for debug etc...)
+ $self->{'windows'}{$id}{'state'}{'os_x'} = $os_x;
+ $self->{'windows'}{$id}{'state'}{'os_y'} = $os_y;
+ $self->{'windows'}{$id}{'state'}{'of_x'} = $of_x;
+ $self->{'windows'}{$id}{'state'}{'of_y'} = $of_y;
+ # Do we even care about the window W&H?
+# $self->{'windows'}{$id}{'state'}{'width'} = $width;
+# $self->{'windows'}{$id}{'state'}{'height'} = $height;
+ } elsif ($what eq 'map') {
+ $self->{'windows'}{$id}{'state'}{'map'} = $value;
+ $self->{'_tmp'}{'w'}{$id}{'map'} = $value;
+ } elsif ($what eq 'maximized') {
+ $self->{'windows'}{$id}{'state'}{'max'} = $value;
+ $self->{'_tmp'}{'w'}{$id}{'max'} = $value;
+ } elsif ($what eq 'focused') {
+ $self->{'windows'}{$id}{'state'}{'focus'} = $value;
+ $self->{'_tmp'}{'w'}{$id}{'focus'} = $value;
+ }
+ }
+ } elsif ($w_or_t eq "t") {
+ if ($self->{'targets'}{$id}{'widget'}) {
+ if ($what eq 'viz') {
+ $self->{'targets'}{$id}{'state'}{'viz'} = $value;
+ $self->{'_tmp'}{'t'}{$id}{'viz'} = $value;
+ } elsif ($what eq 'geometry') {
+ my $talloc = $self->{'targets'}{$id}{'widget'}->get_allocation;
+ my ($aX,$aY) = $self->_get_absolute_target_pos($self->{'targets'}{$id}{'widget'});
+ $self->{'_tmp'}{'t'}{$id}{'x'} = $aX;
+ $self->{'_tmp'}{'t'}{$id}{'y'} = $aY;
+ $self->{'_tmp'}{'t'}{$id}{'w'} = $talloc->{'width'};
+ $self->{'_tmp'}{'t'}{$id}{'h'} = $talloc->{'height'};
+ $self->{'targets'}{$id}{'state'}{'x'} = $aX;
+ $self->{'targets'}{$id}{'state'}{'y'} = $aY;
+ $self->{'targets'}{$id}{'state'}{'w'} = $talloc->{'width'};
+ $self->{'targets'}{$id}{'state'}{'h'} = $talloc->{'height'};
+ }
+ }
+ } else {
+ die("Someone screwed up pretty f-ing bad... somewhere!!!");
+ }
+}
+
+
+sub _handle_window_event {
+# Stuff that happen here should never be logged other than when explicitly doing dev work.
+ my ($self,$the_window, $event,$wid) = @_;
+# print "TeKiPM MAINWIN\tE:\t$the_window\t",$event->type,"[TeKiEID:\t$new_id]\n";
+ if ($event->type eq "configure") {#Size or position change detection.
+ $self->_set_tmp('w',$wid,'geometry');
+ } elsif ($event->type eq "visibility-notify") {
+ # WE DON'T CARE.... TRACKING THIS INDIVIDUALY FOR EACH TARGET!
+# print "\t\t[",$event->state,"]\n";# Change this line to use BugOUT but leave it commented out..... !!!!
+ } elsif ($event->type eq "map") {
+ $self->_set_tmp('w',$wid,'map',1);
+ $self->_set_tmp('w',$wid,'geometry');
+# ($self->{'windows'}{$new_id}{'state'}{'x'},$self->{'windows'}{$new_id}{'state'}{'y'}) = $the_window->get_position;
+# = $self->{'windows'}{$new_id}{'state'}{'x'};
+# $self->{'_tmp'}{'w'}{$new_id}{'y'} = $self->{'windows'}{$new_id}{'state'}{'y'};
+ } elsif ($event->type eq "unmap") {
+ $self->_set_tmp('w',$wid,'map',0);
+ } elsif ($event->type eq "window-state") {
+ if ($event->new_window_state =~ /maximized/) {
+ $self->_set_tmp('w',$wid,'maximized',1);
+ } else {
+ $self->_set_tmp('w',$wid,'maximized',0);
+ }
+ if ($event->new_window_state =~ /focused/) {
+ $self->_set_tmp('w',$wid,'focused',1);
+ } else {
+ $self->_set_tmp('w',$wid,'focused',0);
+ }
+# $self->_set_tmp('w',$wid,'wstate',"$maximized,$focused");
+ } elsif ($event->type eq "delete") {
+ $self->rm_window($wid);
+ warn("You may now have orphans!?!");# FIXME: ADD SOME "IF GOT TARGER BLABALBA" HERE...
+ }
+# print "\n";
+ return 0;
+}
+
+sub _handle_target_viz_event {
+ my $self = $_[0];
+ my $tid = $_[1];
+ my $event = $_[2];
+
+ if ($event->state eq "unobscured") {
+ $self->_set_tmp('t',$tid,'viz',1);
+ $self->_handle_target_geometry_event($tid);
+ } else {
+ $self->_set_tmp('t',$tid,'viz',0);
+ }
+ return 0;
+}
+
+sub _handle_target_geometry_event {
+ my $self = $_[0];
+ my $tid = $_[1];
+
+ if (($self->{'targets'}{$tid}{'state'}{'viz'} ne 2) and ($self->{'targets'}{$tid}{'realized'} eq 1)) {
+ $self->_set_tmp('t',$tid,'geometry');
+ }
+
+ return 0;
+}
+
+sub _get_absolute_target_pos {
+ my $chkwin = $_[1];
+ my $absolute_x = 0;
+ my $absolute_y = 0;
+
+ while ($chkwin->get_window->get_window_type !~ /^toplevel|popup$/) {
+ my ($pwin_x, $pwin_y) = $chkwin->get_window->get_position;
+ $absolute_x += $pwin_x;
+ $absolute_y += $pwin_y;
+ $chkwin = $chkwin->get_parent or die("WTF We should have stopped by now...");
+ }
+
+ return ($absolute_x,$absolute_y);
+}
+
+
+################################################################################
+# FIX ME! TMP STUFF, REMOVE IN FINAL VERSION!
+sub _get_tmp_local_socket_id {
+ my $self = $_[0];
+ if (-f "$self->{'tmpdir'}/server_sockets.info") {
+ open(SIF,"$self->{'tmpdir'}/server_sockets.info");
+ my ($local_line,undef) = <SIF>;
+ close(SIF);
+ print "LL1:\t$local_line\n";
+ $local_line =~ s/[\n\s]//g;
+ print "LL2:\t$local_line\n";
+ if ($local_line =~ /^local\:([0-9a-zA-Z]*)$/) {
+ my $sock_id = $1;
+ return $sock_id;
+ } else {
+ die("TOTAL FAILURE! BUHUHUHHUUUUUUUUUUU!");
+ }
+ } else {
+ die("TOTAL FAILURE! BUHUHUHHUUUUUUUUUUU!");
+ }
+}
+
+
+1;