pax_global_header00006660000000000000000000000064134136155310014514gustar00rootroot0000000000000052 comment=7fd4ba2bd6049457d33d7d8ce56a4ea9bee9f395 keyman-config-11.0.103/000077500000000000000000000000001341361553100144665ustar00rootroot00000000000000keyman-config-11.0.103/MANIFEST.in000066400000000000000000000001561341361553100162260ustar00rootroot00000000000000recursive-include keyman_config/icons * recursive-include icons * include README.md include *.bash-completion keyman-config-11.0.103/PKG-INFO000066400000000000000000000006701341361553100155660ustar00rootroot00000000000000Metadata-Version: 1.0 Name: keyman_config Version: 11.0.103 Summary: Keyman for Linux configuration Home-page: http://www.keyman.com/ Author: Daniel Glassey Author-email: wdg@debian.org License: MIT Project-URL: Bug Tracker, https://github.com/keymanapp/issues Project-URL: Source Code, https://github.com/keymanapp/keyman/linux/tree/master/linux/keyman-config Description: UNKNOWN Keywords: keyman,keyman-config,keyboard Platform: UNKNOWN keyman-config-11.0.103/README.md000066400000000000000000000054361341361553100157550ustar00rootroot00000000000000# Linux KMP installer ## Preparing to run If you are running from the repo or installing keyman-config manually rather than from a package then you will need to `sudo apt install python3-lxml python3-magic python3-numpy python3-pil python3-requests python3-requests-cache python3 python3-gi gir1.2-webkit-3.0 dconf-cli python3-setuptools` You will also need kmflcomp either from a package or built and installed locally. run the script `./createkeymandirs.sh` to create the directories for these programs to install the packages to ### Installing manually from the repo `make && sudo make install` will install locally to /usr/local `python3 setup.py --help install` will give you more install options You will need `sudo apt install python3-pip` to `make uninstall` ## Things to run ### km-config `./km-config` This shows a list of installed kmps. For each one it has buttons to `show the welcome page`, `show more information` and `uninstall`. ##### Buttons * `Refresh` - useful if you install or uninstall on the commandline while running km-config. * `Download keyboard...` - runs `DownloadKmpWindow` (see below) * `Install keyboard...` - opens a file choose dialog to choose a kmp file to install and bring up the `InstallKmpWindow` for more details and to confirm installing. ##### Download window This uses the keyman.com website to install kmps. The website doesn't know about linux yet (until after 10.0 release) so pretending to be a mac for now. Search for a language or keyboard in the search box Select a keyboard from the list In 'Downloads for your device' there will be a 'Install keyboard' button for the keyboard for macOS Click it to download the keyboard and bring up the `InstallKmpWindow` for more details and to confirm installing. Secondary-click gives you a menu including 'Back' to go back a page. ### km-package-install Command line installer for kmp `km-package-install -k ` or `km-package-install -f ` ### km-package-uninstall Command line uninstaller for kmp `km-package-uninstall ` ### km-package-list-installed `km-package-list-installed` shows name, version, id, description of each installed keyboard `km-package-list-installed -s` shows name, version, id of each installed keyboard ### km-package-get Download Keyman keyboard package to ~/Downloads `km-package-get ` ### km-kvk2ldml Convert a Keyman kvk on-screen keyboard file to an LDML file. Optionally print the details of the kvk file [-p] optionally with all keys [-k]. `km-kvk2ldml [-p] [-k] [-o LDMLFILE] ` ## Building the Debian package You will need the build dependencies as well as the runtime dependencies above `sudo apt install dh-python python3-all debhelper help2man` Run `make deb`. This will build the Debian package in the make_deb directory. keyman-config-11.0.103/icons/000077500000000000000000000000001341361553100156015ustar00rootroot00000000000000keyman-config-11.0.103/icons/128/000077500000000000000000000000001341361553100161135ustar00rootroot00000000000000keyman-config-11.0.103/icons/128/km-config.png000066400000000000000000000176011341361553100205000ustar00rootroot00000000000000PNG  IHDR>asRGBgAMA a pHYsWMtEXtSoftwarePaint.NET v3.5.11GB7IDATx^]w\SW}>!:jU@V[]]U I=pmUp dCHK@ Iw{o ??s=| 5p_&0pşIA& `& 0%I$I$_&0`UUD.L0 du`& `& 0WnaFwF]#Oo5\.n5))D6[` -U)w:P[ N +Y%L.ezN6^\n\ވ /-\9F_q͵~R%BW# $QH>\#!^:EznS ]"nK!KCq# rѫ/2^ ܼ/݌H?ĂM=E#C'; 'W%x`2*)(D7sB|(AB`bJĝ,̺[xCTu1eGtVx~;򢐠_Ioa쪝^Q"H~dwlxCPj v<8LF誠A 8-)-B3t]Tz@"v.~׃ă jny˽8>jnr׺& (x)) |VQK4`cɌm[djجړifo:ǰCߒ2v3teѴh %{y糋 mꀲBY957j(bP=^+oUHm;,{XvȃQe~I[`R5"G)! 4fW.3Ɔ}:*st6<"Xwcsؙq?%2򳏥-{_9Y g3 n.vd?ShZ G{ gȀXZZds ?V !g_|NcHAA2`d0Ni_+&1A,拴 ,fRJ(>#6p^jΝ+_◵2_ ⾛ئ 1a2U(NlĊΫ\|bUDҬGe@i6[̴`$Lsava6bNz>/eV)xwFgH<8TJۣ{2SK~-c f̕^AN%ӗ6ߣ%Һ,b*@²pB.-[U/ED71sg2vyr6+&OX.#WQG W ǎ,O8R8vmw w'݊mzI1|:z.vBO⩓X64D }<L>A4QM !b jLFOQ꿴ۛPKf(7%K$+߅̣D^Zۜ6 j0u9Xj๽mU37|ryױ\d}d5q%q*vW>G7n&`J/n+6 P)-Ws@@.-kzTK6A^q2'$d =w?mYvޤ۴vnqWJY)@aI^6-%r6S$9:U ^Ez">^U9.{yNY(e9Ss_wq#&C#{#n(cr_B#ᩌ y5@(;૷ |>`ӤQSeQ銴9W-f 8?/dy%&AQ"ds_E5]\NCS24% (G# <~{t|,QY|}FM$ѭ# ShuX w7D^xe9mV0kN/`Rf)ߡ6$% ķ׷ٞ ) gD 67&Tq]YU&5'GЪ½dd CF:~%NxsH"a )ljL.DN-^2eGAp 0cߗoa3k [ذ~k ރ Q[Sm^)8 ]1rc% FUN4$(e%R޾I.$:a1AJR f5^#Yfj ,Lvkuz M#QF=SpV*NoP[nzZn9ý DM:ZAGQOIlQ%joIY-mRqodAwb6M4fu8Ă< ]H:]%H79~Te_9f۠@je,r""nmsi>HU`${봃| tsg[ P }vt=|ux?ٷ?Rl!ݏѪcڨ0o?V0]Aj!]?m@/<; >{ 2;u!@n=m.\ʗkc<?b4!_CطslySTؙ J"rtMC8TjbYڭfb=]?!f@ޚ!I{8%^M"NЅ `i#A̘OEs7$?*$~9Cqu rn;[ߕEh26` $:74ѧ Qȡr@2E4<?0TI_Ӗ$=X;ai6(?:EZm6iq@m2_Xh?(흉אf#[Q}oS%@ S$/tLKU {xȩ*jCyo&tG5`^r ٷPv1P, M"T)[RgnQ*,^' zR7´"v:*Z&rԚ.i[n͓X.6 @+ax:K#9Te>4P.C o4~| Q]vj3mqyd:zzsGLN_᰽쁶u[f}kҝ_j7?뙫 :A KHvb>L#V쫧̶!C'56H$ p 5T}3;dbrMן*?]Ȏ"haprG]S2 T!q)G4pLVMb=zveP7C&y,<+!kKQ|֟ML0>C@.VQɲkrʲ@jLZ~T%UFk(r-^0;;'Mp :#H=~慲7Aqzg b'41,>ovGW#_D>&ӎQ8(o+8na6Y/&ܗ醂K^#r0r<BnN=t J|};U=O"P[ YD0O}G@9ip}׋} ꓓQm%\cT%ɣQ=Ad+v*~#nܐ;D>=МCN !hC:HhJ_}O;{zzբå4TC^slvKZK`|📉eI<`$> `n }7=EZل(m"$ ͊E`*JWU+d +!R~S_htޯ `%&ލ}_ꛄj>a?jTkД!+d&htOl+t~02ͿWFuUd st!lq~{~$je Tm26#ڟ. @.dG p@Mƀp, ', YE4GSڼ4PN4 wPg8%/ @<qǐ_:j'W lDH#&n3b//v‚A8ȀM/fuT Yh An!O6 ZoX";L ]B=I@Xc9XPPNXj :݀$ @bQ T]: 9Quh0z}ˤ&5POkbY,O:V&L$$0?DL*r$L$ YAY0Iؐ$I$!+?k& `}2~0I0dgM`K'npIENDB`keyman-config-11.0.103/icons/16/000077500000000000000000000000001341361553100160275ustar00rootroot00000000000000keyman-config-11.0.103/icons/16/application-x-kmp.png000066400000000000000000000015401341361553100220720ustar00rootroot00000000000000PNG  IHDRsO/sBIT|d pHYs lltEXtSoftwarewww.inkscape.org<IDAT(mRkHa=oN4ԭfa-@*nD5L(P#J2E~f? D`Fѕ-RSiljByίs#(c֜ 8Z@CR S9(p*!8Hٔ63>C=ouT)cD B"`NqCz3/H0(e,jq`h;thҍYH~= DT3<8wr~L~XwIENDB`keyman-config-11.0.103/icons/24/000077500000000000000000000000001341361553100160265ustar00rootroot00000000000000keyman-config-11.0.103/icons/24/application-x-kmp.png000066400000000000000000000025651341361553100221010ustar00rootroot00000000000000PNG  IHDR!-sBIT|d pHYs^^lʾtEXtSoftwarewww.inkscape.org<IDATHUkLg~z@tX ^.3)m0w1Y~d,3 YYQanr2 @)zC)mAο}$) x\ц!k)m=Z#q@ 6=.l `uTN{c5^:2`$΁C7 L [d8sQҎ9s>*0VC'<ָ'1w`+B>/QY]u!bĢ2ENLE$`8bѕ?J(B>J;vcGOSva6!^N`=44E|CIB=clWi(2݆T g^\fb6AڏÐVl(v3p"Ngz_kESҡU aXHy_&P'R\|XM'KRV2l@&D N1&CE{DrPD6iڀYh,$9S nn FLv GIQQOydē@z(||SQO gt-L%5aܠp)A'*'"_QC%LKWiSl=;aa)Bͫ1g=8]IUʰ0OE;]f8,#4#}5KZu>"BʐJ(ݱk)1NoϿq%B7,J郔!]\x+5/AZRJj]-UECziY i0Xu:9WoXbS%f2s>1fX0,PoǦ3U?& a_ ع7d\I bPG{[U<]h-F6,MVZgIcNQ( x)^/<{Nƭ[.H[Y%Y}ǢKyGaRq凲k*$nBw}Cd""X~A{!CB򎢾$Kv=clB@%)DJlMbC",A zM*/H=-1b=DT7.2o]Hz 3,i{*<MsJGš3׍ϪEU<S$ӜL M^(?Z7x_%Ppt>Di-#4vR2qtij9x>rFcyh ȥ/1 t}xjԡh6~⶚~ĠACEϑ`)/On/6{6U'E?;& K0 cmkرglIENDB`keyman-config-11.0.103/icons/32/000077500000000000000000000000001341361553100160255ustar00rootroot00000000000000keyman-config-11.0.103/icons/32/application-x-kmp.png000066400000000000000000000035231341361553100220730ustar00rootroot00000000000000PNG  IHDR M )sBIT|d pHYs((oitEXtSoftwarewww.inkscape.org<IDATHW{PT}we} ,<+Rߩ"hWSi!ilI3N&N?&FgjLqŘ <cyڅ}޽f "9={T.)V+bKAbsrx|ZsJ`~m}@gևg۪gf8nmC)Ki4@.̑k>x;dž.Yb;KNyE91{Dc㤹mh5{S3ϳH}?T$$[/g4tzoJڂ~%SgM:tgԸV!ޫ |ə&o+^(^ؗ>ʂW.Xz;D*0"XVycW2~LPadldDD"HX(9TG%/ QMXN8o1vrO8>"d콛D언m >D!~eiCѿiKcgΛS3'z*ya-I+Zu<iYx+Q|l÷kŶKeY^]H3gmSGԅp`lB2!S~}-ߪdEﰙhpsM1lcEfm7n1 4#&|i*!5} *'jٲy"_z>`A,.6杦4 w_ex3u>Kөs!}p5)4 2r[¥8d4P˰& $*B``Ri:Lo2%WA)Ov[K(h cءbO|KMcrlc6(?cTșxӤ=&žlaB$47%RdzӬreɥx;F˥G )"#< T`BG*DҬ \KIENDB`keyman-config-11.0.103/icons/32/km-config.png000066400000000000000000000034041341361553100204060ustar00rootroot00000000000000PNG  IHDR szzgAMA abKGD pHYsWtIME 7Y?PIDATX}lU?s^oo{OX҂/,Y#"!̮ Vad liu5e&-17v'eթ-EZ(moi{ ml?9y}~|dScۦoD\+x}Ezx eTg7?WRZ4e> q-ջ4 25̶ F<_IBQoƶ 2ưL+$G+iZY~϶SP`u6fMm7[Jf,ːF! q/q[QyrfSaH9 {(*[`ROmh5@W<)&֭H XX'%ˎJَm (֌ou5WE#X[7YBwj\Vl-XVW-/04?|aL(UhpS#-]iի>w}%nW>%X灻*qKg/~,),GGW~R$jxK-:oν$^ܞbvhڢh /Okݯ욛YDc8'It+cbx6J} s/ -#S:G]Uwh׮_/PV2H]#vב2&0C7M'SpOH!dPtrnuWc]r)[i":J!B99J'Hfyw#&7+Swxq08G:52 dXCS{ZۑNtjܭ@)$ *ҔH%"? EA>>Zk |)T`( LE3Mg)߱}95 #ZkB< xއᖗ1l`%7iBTg(tk{6s1 y򓛑S0lZKli}A%/6x ]ՠFЍR+:gTz=4y0n>QUhکq;n`vz_鵟<ljh  ):5Ќr;IoׯӁOK`k9ԸO )}DΌHӁ_X>~Ը-O_d7sLȿJIENDB`keyman-config-11.0.103/icons/48/000077500000000000000000000000001341361553100160345ustar00rootroot00000000000000keyman-config-11.0.103/icons/48/application-x-kmp.png000066400000000000000000000056001341361553100221000ustar00rootroot00000000000000PNG  IHDR0-JsBIT|d pHYsntEXtSoftwarewww.inkscape.org< IDAThZ{Te|7 (^6-5RMϦI"e[;:m{̵4]#AiGT E0\ۻpf@|<< KXT̸Hy̮uB҈fiJz-:58# hn]4-s;0Udx'_//Q'%<>gCtf dGlReщcӲ :_UU{Cu1 b3k'(G7ViOF%8x,tB}$mT6qn0K'|FoW]`=Z$.z%-ɑ~0S4E;j~i;_rڗX3$p֖f!T]A? Ka3U6s;j37 D=6 io,,n64 B7=Nֈ`5@uy^H\'G;%T} W푭6&4q9$n2{f7oM ux>?z¹SH#$$R=&<1b¹ T$@ J[,6 Ԣ0HNiѴQaUڳLdOaE!vJگՐY&tk' {r$Pe-*BVL}C&gpph!ս.0*K.JIKނ!<#JQ{]g劯?8$Q!BYj]R-@w=ˀ6?n{WڙDz׀wN~'˕PAU5<Æ6 &A?2^#,9%Dm`R,}7PI !GPq1mRqhޏ٢LuPQMc~=}cлMARfhw-D`5ѰǙY,ˁRT|1ZNI>?;:){e:LEpnЅDZN0]z- MT){e€;<3P_VyGX(zPJ|y\I >^{i1AAɉI^{R׀` Ūt+py`!:s:Oz.  Q%n\с\t6J#c HF2[4u'2{?`Rv:ťu jķ?/± i\rhqڱZ*PA0/(2yTϠO%. ^,S'b npY@ByJy"(n )yٙ-e2)`P T v"cJ.Z7=dZO8 BO;  M#NAzcDŽ[CFQMq!HCwE$Sc Cw{T"ۨ0xfvz]>Cb*\BFj!Sc>%5TN |J.- y!sFmT~bKg@7?]Y IFX82xESRa x=u;2p Wx#&N8ճ3M(OA 5ޯv]1p_QDêC"v/d#oW/& ^^ (6oޯY E\A=Z0IENDB`keyman-config-11.0.103/icons/48/km-config.png000066400000000000000000000047511341361553100204230ustar00rootroot00000000000000PNG  IHDR00WsRGBgAMA a pHYs5qtEXtSoftwarePaint.NET v3.5.11GB7 XIDAThCyTSWc̙9=sv IUOXMPөKpXNZu4$ZhmQ#PƺAD#@%o / i{}~{ AүţKM@TDu4=[͒LX;i aU$BGZ0V0c (wnĻJT,:rL6 TDgΖ5I -nv-,{i O Ejŧ*mimfmuXуN#U<`ɨ8T8py׹ݷ[Q˟kP oJksA ISW%GPO}nff"+)9qGY.j iZ$*np뀴*@NĥY1 ۊʅbhjtHU_>Vk)m;^TfhUϋTlXvP-9sΜߋ{!h ɞ FN_V2$'UU.(SE*ἲK"}IvO;35$;w&ߗF_;? 'jm2#U+_>o.Ul޼waa #- 7q6sV53saKiKdEW%\уF~Jy$%K>왏Pʦ\b =wh0%ppb>i<thfOEMEzh} :lҽ9E88lf#QjP -KB$mW~XK 7Bn@TjFkVtx<87]c> $+IrKboKky"yމGUQ86^3wɞϛJS~7n ,04!'[8'r+v7ۦzK{Nܦؗ^xՐu=hyK7zp'FEF"3rɷQhrz|v[`Fpo{RpCim }?.Ϧ= 05P'}%p Lc??~RPƞ0-?#vv_%aZ= 3I `:<ɍPP5:Q5w%2њCcx>1Gģ) = iKہ X7Hhz *"!"o$A<:Z^UͥZ-ƀxh g`fG篬ӹq%uE"@L[}~&ܮ?Z7GoA1E 7 MY -ً[oJWlU澶:BJG2AsGXҖ[n"`7-g0 79G4uIkfrgCG]UP{OsWϺlaMتm*&drCQ 鱮п^rו`ה249~Lnڎz W<&e{jlˤxd~SL3Jq7%D$ tp #xOs _T4oA8}ڣA ɪ 9"FD6 q(ab3o,OU{+7$'Ps0TU{ˆ|lͤLQ2>$,7y#r`~Y}z?Zj~0 l9.%bšV: ҈Nl_D#`1l4~K{X plh8]U@CɽTC)}+̝܎hG?Y =:I`l~,UX~D'yz 2D$Y"ޘBYNqh+J}f ⎎2N .;M&|ѫ8y8m3#h+2 _Z̚bWڜŅ](\%fh{)<$pL`\B nrҖp aW.a;9.aM[<9J /` I(t%h뺡G0,>Aɮ 7{bx V=R<5F0]M"hj n 7N Mlƙ/lޏQ p+*ޏѩYH:P[4gδ_]mrtf?|3tx((v }k8g̅riD_K{~Ȍ# ՘{G8kwU{3t]S 9ky'Yږ%c]K:A1.G蚊CdhnW'$bʃ].ӎ^QI_pC9k(v[Ǩ`z.Y^PMT?_l {6·["Q2f7ޕW޼qD,㇑-NV%aR1jyi;ܭM8k(aƔ^ɷ̮ z[wo#\<IezhHv 6pVF}IH. o@BTBQ48s!BG6͗oU- `/7` .lYDQ&%) d F@'"@ܔ;?e$gMq^܆ bX! A$=c"Mxjii77x;;8X0 iGLf&El'"Zo q)n+JP~h*b" p#:0ul Yߙ ?FX3Ca X4O"2d3pNSTԞ9GS-"Â4u3XdH 7݉1r8/uηdS@0e)(IÇ@`T$YUD*q+%4 A3 `˜Xض9jfk~4O@d`HL[O |aEVǝhaiR Z!sJyYfC:dMV ,u5[pk uPXs A!AhELk+ /6'PtInYMٻ[+bͿ@G x91,1Oш,`(\0eYob8Y "JXor:ΙLI<(LYQrQ@02 D3T%:0w(&jﯭ$X( x葡d YV5G˙H%m];$/ yDñCv{L4uwl3j,^%xAgL1F_Iru448'KҞ_쳹mh$yq`P|\(/e} @Cʜq@7I9QF8[ú v05c-qڰ}1fo5 DM|`ІKhY"dpus0?R1-IENDB`keyman-config-11.0.103/icons/64/km-config.png000066400000000000000000000067641341361553100204270ustar00rootroot00000000000000PNG  IHDR@@iqsRGBgAMA a pHYstEXtSoftwarePaint.NET v3.5.11GB7 cIDATx^yTSWiNL̴VθLgZ%UюijANN["! $AmjnZuS a ّ}!ߋ%d{2=s@~щKFB`$$G |~.d*HR򏘙RHȏ#l4i^>'=* fJ!g +aO=, M w8 mGb3C@H_A33p"ELtpQ[YZHڳ;_^JTV6߫SU/r"qH//p!VVϚs>~L7M²*pR/,F8^dx hBH2 -b,=^R%6Yn{#*)Y }xZ/‰7CRֻaXAr EA.gK@Vؼ;B|p|5q>xacwي{?H:󕻏9H^I{nn*{ᢢᢢvɿ^X`&Rgc#lbn[tKkvX|wd໊̼c\#*FA0Dr5@LP{៕NNzL[bNHuX4xMwQ3J <$`#h'Nj=ܬI.Ԙoh 8'ʪ1rʴNjշHk UddӦ/6f&>{~" /DOc0Wcz4s~]ywknD֨MiPPENzX|[^ٜ*`gno,W'\?g=(ܮG5sy*s:eoPCR48  D;v|NX{ʆo6;ht,[Plw~ w^RuTy%򨃦-ǐ̐}^TEB#kN[Ғ<!4k<Czi<̼}Lg]'k~pIg RgOJjf˕.dU1qx9G  %o,"*@|g9yX3D72z.OJ jI^͍}hSJỘp few.Nq A0vh8w-Yu@ڬ>sa֐jJgԲPa7h1n??7D:#Bu q!s)/i9V<h>11k>F>KDѠqE>*G8$5`e=oJATFє&7 >/lt/sc] ݩ7p8bkyHpW Ǯo܎L ]3 EYd xH]`i[_}c{4gk/UO *p(@ԔG ?EJur!)\.핍HS$ >9G<ʘPFBc _J} @"SA\1$D7.â5ͤgSW+5Tc>C Z?"t*Y%u 8B +{ c/&*޼78f@9=^ j/[}#aPGyI\‰s:_[R}-$B_Zc)s&D EBx\(;NGҴ%R5~pG jB}vX<;d?m*"PalNtEyq=g erx(uQ+X#)pku^(U:ܗn[Qäy]*vBN6G?=K?A47cZb t0N5)* b߳"r/r2ݪɘ<߬ʔX ")mo%z/u$gD+:QVoVk$RR%c/ u(.B wwwB/j)+ׇ617?FXW ҽ,*KsQ-u0] ?@xqZ8z'QBɟշĽLNv"6 —_0|:i Qrڹ/^!BZuI_.p"=c+DE?P[ EAxeu [e )*x͘d_.%nd{~V}\A!_ >U2 160 and colour == (0, 0, 0, 0): logging.info("checkandsaveico:" + icofile + " mostly black so changing black to white") im2 = changeblacktowhite(im) im2.save(icofile + ".bmp") im3 = Image.open(icofile + ".bmp") im4 = im3.resize((64, 64), Image.ANTIALIAS) im4.save(icofile + ".png") os.remove(icofile + ".bmp") def main(argv): if len(sys.argv) != 2: logging.error("convertico.py ") sys.exit(2) logging.basicConfig(level=logging.INFO) checkandsaveico(sys.argv[1]) if __name__ == "__main__": main(sys.argv[1:]) keyman-config-11.0.103/keyman_config/downloadkeyboard.py000077500000000000000000000071571341361553100232160ustar00rootroot00000000000000#!/usr/bin/python3 import logging import os.path import urllib.parse import pathlib import subprocess import webbrowser import gi gi.require_version('Gtk', '3.0') gi.require_version('WebKit2', '4.0') from gi.repository import Gtk, WebKit2 from keyman_config.get_kmp import get_download_folder, download_kmp_file from keyman_config.install_window import InstallKmpWindow from keyman_config.accelerators import bind_accelerator, init_accel from keyman_config.get_info import GetInfo from keyman_config import __majorversion__ class DownloadKmpWindow(Gtk.Window): def __init__(self, view=None): self.accelerators = None Gtk.Window.__init__(self, title="Download Keyman keyboards") self.endonclose = False self.viewwindow = view init_accel(self) vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) s = Gtk.ScrolledWindow() webview = WebKit2.WebView() webview.connect("decide-policy", self.keyman_policy) webview.load_uri("https://keyman.com/go/linux/"+__majorversion__+"/download-keyboards") s.add(webview) vbox.pack_start(s, True, True, 0) bbox = Gtk.ButtonBox(spacing=12, orientation=Gtk.Orientation.HORIZONTAL) button = Gtk.Button.new_with_mnemonic("_Close") button.connect("clicked", self.on_close_clicked) bbox.pack_end(button, False, False, 12) bind_accelerator(self.accelerators, button, 'w') vbox.pack_start(bbox, False, False, 12) self.add(vbox) self.getinfo = GetInfo(self.viewwindow.incomplete_kmp) def process_kmp(self, url, downloadfile): logging.info("Downloading kmp file to %s", downloadfile) if download_kmp_file(url, downloadfile): logging.info("File downloaded") w = InstallKmpWindow(downloadfile, online=True, viewkmp=self.viewwindow, downloadwindow=self) if w.checkcontinue: w.show_all() return True return False def keyman_policy(self, web_view, decision, decision_type): logging.info("Checking policy") logging.debug("received policy decision request of type: {0}".format(decision_type.value_name)) if decision_type == WebKit2.PolicyDecisionType.NAVIGATION_ACTION: nav_action = decision.get_navigation_action() request = nav_action.get_request() uri = request.get_uri() logging.debug("nav request is for uri %s", uri) parsed = urllib.parse.urlparse(uri) if parsed.scheme == "keyman": logging.debug("using keyman scheme") if parsed.path == "download": qs = urllib.parse.parse_qs(parsed.query) downloadfile = os.path.join(get_download_folder(), qs['filename'][0]) if self.process_kmp(qs['url'][0], downloadfile): decision.ignore() return True elif parsed.path == "link": qs = urllib.parse.parse_qs(parsed.query) webbrowser.open(qs['url'][0]) decision.ignore() return True return False def on_close_clicked(self, button): logging.debug("Closing download window") if self.endonclose: Gtk.main_quit() else: self.close() def connectdestroy(self): self.connect("destroy", Gtk.main_quit) self.endonclose = True if __name__ == '__main__': logging.basicConfig(level=logging.INFO) w = DownloadKmpWindow() w.connectdestroy() w.resize(800, 450) w.show_all() Gtk.main() keyman-config-11.0.103/keyman_config/get_info.py000066400000000000000000000024631341361553100214500ustar00rootroot00000000000000#!/usr/bin/python3 import os import threading from keyman_config.install_kmp import process_keyboard_data from keyman_config.kmpmetadata import parsemetadata class GetInfo(object): """ Get information about kmp packages and keyboards The run() method will be started and it will run in the background. """ def __init__(self, kmp_list): """ Constructor :type kmp_list: list :param kmp_list: List of keyboards to get info for """ self.kmp_list = kmp_list thread = threading.Thread(target=self.run, args=()) thread.daemon = True # Daemonize thread thread.start() # Start the execution def run(self): """ Method that gets info for installed kmp that were installed manually rather then from the download window. """ for kmp in self.kmp_list: packageDir = os.path.join(kmp['areapath'], kmp['packageID']) process_keyboard_data(kmp['packageID'], packageDir) info, system, options, keyboards, files = parsemetadata(packageDir, "kmp.json") if keyboards: for kb in keyboards: if kb['id'] != kmp['packageID']: process_keyboard_data(kb['id'], packageDir)keyman-config-11.0.103/keyman_config/get_kmp.py000077500000000000000000000123461341361553100213100ustar00rootroot00000000000000#!/usr/bin/python3 import sys import datetime import time import json import logging import requests import requests_cache import os from pathlib import Path def get_package_download_data(packageID, weekCache=False): """ Get package download data from keyboards download api. Args: packageID (str): package ID weekCache (bool) : cache data for 1 week, default is 1 day Returns: dict: Keyboard data """ logging.info("Getting download data for package %s", packageID) api_url = "https://downloads.keyman.com/api/keyboard/1.0/" + packageID logging.debug("At URL %s", api_url) home = str(Path.home()) cache_dir = keyman_cache_dir() current_dir = os.getcwd() if weekCache: expire_after = datetime.timedelta(days=7) else: expire_after = datetime.timedelta(days=1) os.chdir(cache_dir) requests_cache.install_cache(cache_name='keyman_cache', backend='sqlite', expire_after=expire_after) now = time.ctime(int(time.time())) response = requests.get(api_url) logging.debug("Time: {0} / Used Cache: {1}".format(now, response.from_cache)) os.chdir(current_dir) requests_cache.core.uninstall_cache() if response.status_code == 200: return response.json() else: return None def get_keyboard_data(keyboardID, weekCache=False): """ Get Keyboard or package data from web api. Args: keyboardID (str): Keyboard or package ID weekCache (bool) : cache data for 1 week, default is 1 day Returns: dict: Keyboard data """ logging.info("Getting data for keyboard %s", keyboardID) api_url = "https://api.keyman.com/keyboard/" + keyboardID logging.debug("At URL %s", api_url) home = str(Path.home()) cache_dir = keyman_cache_dir() current_dir = os.getcwd() if weekCache: expire_after = datetime.timedelta(days=7) else: expire_after = datetime.timedelta(days=1) os.chdir(cache_dir) requests_cache.install_cache(cache_name='keyman_cache', backend='sqlite', expire_after=expire_after) now = time.ctime(int(time.time())) response = requests.get(api_url) logging.debug("Time: {0} / Used Cache: {1}".format(now, response.from_cache)) os.chdir(current_dir) requests_cache.core.uninstall_cache() if response.status_code == 200: return response.json() else: return None def get_download_folder(): """ Folder where downloaded files will be saved. Returns: str: path where downloaded files will be saved """ return keyman_cache_dir() def keyman_cache_dir(): """ User keyman cache folder It will be created if it doesn't already exist Returns: str: path of user keyman cache folder """ home = os.path.expanduser("~") cachebase = os.environ.get("XDG_CACHE_HOME", os.path.join(home, ".cache")) km_cache=os.path.join(cachebase, "keyman") if not os.path.isdir(km_cache): os.mkdir(km_cache) return km_cache def user_keyman_dir(): home = os.path.expanduser("~") datahome = os.environ.get("XDG_DATA_HOME", os.path.join(home, ".local", "share")) return os.path.join(datahome, "keyman") def user_keyman_font_dir(): home = os.path.expanduser("~") datahome = os.environ.get("XDG_DATA_HOME", os.path.join(home, ".local", "share")) return os.path.join(datahome, "fonts", "keyman") def user_keyboard_dir(keyboardid): return os.path.join(user_keyman_dir(), keyboardid) def get_kmp_file(downloaddata, cache=False): """ Get info from keyboard data to download kmp then download it. Args: downloaddata (dict): Package download data cache (bool): Whether to cache the kmp file web request Returns: str: path where kmp file has been downloaded """ if 'kmp' not in downloaddata: logging.info("get_kmp.py: Package does not have a kmp file available") return None downloadfile = os.path.join(get_download_folder(), os.path.basename(downloaddata['kmp'])) return download_kmp_file(downloaddata['kmp'], downloadfile, cache) def download_kmp_file(url, kmpfile, cache=False): """ Download kmp file. Args: url (str): URL to download the kmp file from. kmpfile (str): Where to save the kmp file. currently it does no checks on this location assumes that is in users keyman cache dir cache(bool): Whether to cache the kmp file web request for a week Returns: str: path where kmp file has been downloaded """ logging.info("Download URL: %s", url) downloadfile = None if cache: cache_dir = keyman_cache_dir() current_dir = os.getcwd() expire_after = datetime.timedelta(days=7) if not os.path.isdir(cache_dir): os.makedirs(cache_dir) os.chdir(cache_dir) requests_cache.install_cache(cache_name='keyman_kmp_cache', backend='sqlite', expire_after=expire_after) now = time.ctime(int(time.time())) response = requests.get(url) #, stream=True) if cache: logging.debug("Time: {0} / Used Cache: {1}".format(now, response.from_cache)) os.chdir(current_dir) requests_cache.core.uninstall_cache() if response.status_code == 200: with open(kmpfile, 'wb') as f: f.write(response.content) downloadfile = kmpfile return downloadfile def get_kmp(packageID): """ Download a kmp file given a package id. Args: packageID (str): package ID Returns: str: path where kmp file has been downloaded """ downloaddata = get_package_download_data(packageID) if (downloaddata): return get_kmp_file(downloaddata) else: logging.warning("get_kmp.py: Could not get download information about keyboard package.") return None return keyman-config-11.0.103/keyman_config/ibus_util.py000066400000000000000000000063541341361553100216600ustar00rootroot00000000000000#!/usr/bin/python3 import gi import logging import subprocess gi.require_version('IBus', '1.0') from gi.repository import IBus, Gio def get_ibus_bus(): try: for i in range(10000): bus = IBus.Bus() if bus.is_connected() and bus.is_global_engine_enabled(): return bus bus.destroy() except Exception as e: logging.warning("Failed get bus") logging.warning(e) logging.warning("could not find connected IBus.Bus") return None def install_to_ibus(bus, keyboard_id): try: # keyboard_id = "%s:%s" % (lang, kmx_file) # logging.debug("getting bus") # bus = IBus.Bus() logging.debug("installing to ibus") ibus_settings = Gio.Settings.new("org.freedesktop.ibus.general") preload_engines = ibus_settings.get_strv("preload-engines") logging.debug(preload_engines) if keyboard_id not in preload_engines: preload_engines.append(keyboard_id) logging.debug(preload_engines) ibus_settings.set_strv("preload-engines", preload_engines) bus.preload_engines(preload_engines) except Exception as e: logging.warning("Failed to set up install %s to IBus", keyboard_id) logging.warning(e) def uninstall_from_ibus(bus, keyboard_id): # need to uninstall for all installed langs try: # logging.debug("getting bus") # bus = IBus.Bus() ibus_settings = Gio.Settings.new("org.freedesktop.ibus.general") preload_engines = ibus_settings.get_strv("preload-engines") logging.debug(preload_engines) if keyboard_id in preload_engines: preload_engines.remove(keyboard_id) logging.debug(preload_engines) ibus_settings.set_strv("preload-engines", preload_engines) bus.preload_engines(preload_engines) except Exception as e: logging.warning("Failed to uninstall keyboard %s", keyboard_id) logging.warning(e) def restart_ibus_subp(): logging.info("restarting IBus by subprocess") subprocess.run(["ibus", "restart"]) def restart_ibus(bus=None): try: if not bus: bus = get_ibus_bus() logging.info("restarting IBus") bus.exit(True) bus.destroy() except Exception as e: logging.warning("Failed to restart IBus") logging.warning(e) def bus_has_engine(bus, name): engines = bus.get_engines_by_names([name]) return len(engines) def get_current_engine(bus): try: contextname = bus.current_input_context() ic = IBus.InputContext.get_input_context(contextname, bus.get_connection()) engine = ic.get_engine() if engine: return engine.get_name() except Exception as e: logging.warning("Failed to get current engine") logging.warning(e) def change_to_keyboard(bus, keyboard_id): try: contextname = bus.current_input_context() ic = IBus.InputContext.get_input_context(contextname, bus.get_connection()) if bus_has_engine(bus, name) <= 0: logging.warning("Could not find engine %s"%name) else: ic.set_engine(name) except Exception as e: logging.warning("Failed to change keyboard") logging.warning(e) keyman-config-11.0.103/keyman_config/icons/000077500000000000000000000000001341361553100204125ustar00rootroot00000000000000keyman-config-11.0.103/keyman_config/icons/cross20.png000066400000000000000000000012571341361553100224200ustar00rootroot00000000000000PNG  IHDR sBIT|d pHYsxxjtEXtSoftwarewww.inkscape.org<,IDAT8NQ{HB"QjBw.}>>G;)fneH*&f #07{GAͦ>*Qʡ&@q|5W?HJjV]."ܲ`ԅeY'UhY B|}( 8V"` B K-*y3>jKh4 paD"`=l=ɄNBZtYF 5zQ}Zk٨^JC+Zc5 mш.J)!ݧYJY4N$IcehWnQu]gkyK@3aD߆aP,_ex7cloJef3R$Lf=NZq4OߧG~>48q"TAk\ŞL@JAQ8$N?/yno )%;;x||ūޏC4j 8f?w`c@_o~ 7x_;IENDB`keyman-config-11.0.103/keyman_config/icons/defaultpackage.gif000066400000000000000000000202641341361553100240450ustar00rootroot00000000000000GIF89a58< CHN BDEFHI HJ L MNNQTQTXR#U([%X+U"\#\+[0^4]0Z'[,]2a.`)d3c:j;a4f:h;c,e1e;i4m:q?nBiAnHpDuKqL{T}ZtPxT{Xg@jCmJjEnMqAwGq@tDrMtHvNpNwHyKpNqPyS}[qPwY}Pz[~`ǁ_ȃ]ՁUـTكXڄYچ\ƒdƊiȋjǒv“|ȑs҂bԇhՋk܂d݅jډ`ًd܌dݍi֎p݌rݑkבtڗvߕpڕ{ژwۙ|jmsw{t{ÙɚמܝġɡǤȦɨʬݠޣަķɲɶ˸ĺɽɿ嗀䚃㞉⡀⢅㤃䤄䤊媍褏夐嬒櫙覑誖謚簔ⱜ貗贚긟嶣幦缫곢鶫꺡鸥뼤뼩꾱¼­ıIJƸɵ̿ɹȽ̾ǹͻп!, H*\ȰÇ Hŋ3jȱǏ CN(ɓ(S$˗0;I͒3oɓ_Ξ@)QD*]J1)ӧEBS*ի5bR+ׯ' R,ٳ͢]kQ-۷n߮+,ݺcw־~Nn4Lߛ$;s𬩠˨/ V;Ө6sfk97`AE6qL(x xE5}Cp BJOȹ*x<\xIǜs-}Cy̱vT 8sNx}G6S(#nHc#T4#8ہfwLlB5߽_'BL$Yg)D$MD-ȄxPrRF x}6mv 37E57 ?bAjPR)Q B AxΡ9*Tӗb>dm1 Tp@:RMNtNj:jcL0B k =`p@L 5Af-6Q;YL4"\L 1A D81˨tS7ˢ4jc7/ n䐈1$Ӻ9Ѽ .X2fwL4q&h߻@ dP"+=, =2b D<IE"@|Ϗ3$i $MZbJBDPl 0\?}σDa@=jd,&;ɂ0@$xpX$@0bs }L$H0)Zo1_pe|p(EQDJ4@E_0_X@Q  ~1E*BP`)/as~Ѓx1s& Ana|x }' . Hp !|G70H-_{Kȵ\ 3@2:H̐b2% =d"՛5]%J (afI1󜒤$F&@ #p?Dڧ 6M FIP##аWŶu2TOԦHn`D ] d<'>2% DHe3&8EߘӃ(@uxH &"0j7$T@fA>AfV&naBZpZ$KHP F,DwhB8^}bHp|pŠ9n%7 (}Tדjpp Rܨ$Kgmz0(ء&DDB!p@0IĖ#- EdhF0NY(x hA _*2x#p,&A ˓GpF R]Lҝ ew#EF' Kz77Q:4 WʔGD 5<-J!?v.H\SOv%6LK0=( H VwDC #K%ZDceE|= \ϫR]:HK~sFv:~o;Ž [iϗh~c 3/xͤ}F  ³a5yjePbb6'y8.L|Յ2+W$D Kz%;8h8߼B؀gYf`2:SwA g1zV!/$1C YD&#CF *e͎' eD@ Zb8 (B8`)"ؤv@(!>"`aȀ v&*a4ۄDf|p82!pS@:}M;LfQp Sc54-`+)%XF%P%|9 P $g ~w6I8MFGv+B8ߤDaPUTdxa6fd>]B4YqP7,Yb0 $P@DyA -`jw Ձ.Aig!aP3c Po#y)r0s4Ń. D2@PX `t6+0Df#UO.sGؠ&.A';KS12=KGG/LAVPU\#`JT<CwGT;a*F&kX5Lg(48bP4 3U"#:O/  e`PmH"}1v %C-Pp# ,(?V,#тB@1 SvgDR!2f2 /6{27(P@3K^X0AC.!v#1 p)c+ae("2g xI< =P:2-ij+3H~sb05[4]u#>00%@a,x}%s&;@ȔRs.Q%@PYH閇5b;CZ8@aDzQ`„ID;d^U^9 6SRy@ L1Z8#*au _ pg>Ʒ%tWE T nF>s"=xS DJ5Q N3,wl b[w> #1@FZ Xa!BF7VAm隅q* w&4hЎ`*48%b)/x bPL1nYeZ'ޠ4} *T Հq(W01P:#AxpT~a d a08Owp Pp7QQB vZ W qs@i!hj&h!Z'\ᨔzکmkZzPʪꪣ *Jjʫ꫌ *z`Ī` K *tIp Y@ yoQP1h g_ p܊: / 抮H 1cy[& 8PiAJ?q 2a a@@ ;  ۲{ i BxA p ;K` !1 Op@4@i`1@ XXgFJ@_ސOY`₡Ap_!da@X`:Kv P~а {[ uD;Pp - 3E Pz@E`b1j@ pP :%:k+%0'cq[A00 A @! l+ %{) '˅0` P< c0~ 7<pPE<+ຬ[ ~$E'`k`PZ@Ě ;!q @|aG \q0*b`@P@<`1z k20lPpѽ,ԐA Lxi@q 4  3KWL$ k@*+! ȡ@ * P€[*,{L0\,'K A Zк0;G\P<qu(5*@q` X@ &3"cpH=K Y! <|~qna, gB1 pLa@*ň, d(ȹ@JPiͶb7VmT; Z B ~<p[ ;E@{`ȝ=Y=; >[ T mO/ =z35{~aaP  Rm[@D QkW030JP m VC M[I=7` -n %` [~b )U)=} Q<wmXzk,7Z@!βw7P} L1Noa7-OP@; \Td-p %J b=.y@:nP = @1A"npE |08h@H"3p蕻v'Bɨ^]⧅1Ygm@pͽ3.d ϟ 00e?+-x*}R {4"ai^Ɂz7=(pGX~,xREʇevȞRx1 e gg zU)"e YUޢ(fЀ:|L[xnR)ȼYqp_PjC)M4 3"AẕZ܋2̈PhJ.e /,`WyD!1f<(|N[ +” Юd%YMvq] I>V)$ȂR) d4 8<yND*o oLw%&z0U8o[HBd O"("HIO f&"e}^ Pͯk I<*}*" @{@wN,*fZ(T0C4Mn =p;Rd|M,5cA!@;5Ұ- !_rlH4Pܨ*"y#D]o˫죎Ji JgĦS6NwC3crf 5!Z!a*ă pl}G ˸A~, SQOHA$BlDrQjH@~#*7c̓|5J$ ĊQ>ÁMdYH~jnZNyIDB](m(U=aȁpOPGMp.7_5@wuNE?IKf9hǵ"7?"* @jO,L / <zxg>A oAUYZbtr%X`}/"G<p7?Y{ ;C6O@.ipnb\EzD*RlsPpuDAgVXKk2 FDlJ'PȖR<%ڌ(`X$c,lIa .QH₃˦R++7)">y"m e` 9[H6X&MB x|@/R?nx\vgGc;keyman-config-11.0.103/keyman_config/icons/expand20.png000066400000000000000000000010471341361553100225430ustar00rootroot00000000000000PNG  IHDR sBIT|d pHYsxxjtEXtSoftwarewww.inkscape.org<IDAT81n0?R iJ>BZcNC8c=7{!H^=DV!vI̢(dn"G~$Gahd;sxU(˜փ^7&p<Z+m+,RjޔAeMxҲ&Aa}}-c.}ScNoBVOT CqwC ӹoǹN咻͐kWyVkXpzraZkۦ1-i+1yZysǔe u gf^,Il6;cv֏i4e^[+ܩC_VۗД# I,#2CC,#[k=p\7dO.EM փ/z@)5Ř2Vs d\_^`[@"qzIENDB`keyman-config-11.0.103/keyman_config/icons/help20.png000066400000000000000000000012511341361553100222110ustar00rootroot00000000000000PNG  IHDR sBIT|d pHYsxxjtEXtSoftwarewww.inkscape.org<&IDAT8r@,pCiaTtg;)]L~ * PxƌB(]2LWw{RTyG@ !@samq|(;zrҪFcFGQt"aR uB-T? sdFQt:Jr pd2Yndy}!R XJkK”%0t=s\7cxxx1*xUpRga:ixwS*!5/988Zoywa <-՘pA$eәF)IENDB`keyman-config-11.0.103/keyman_config/icons/icon_kmp.png000066400000000000000000000014071341361553100227210ustar00rootroot00000000000000PNG  IHDRasRGBgAMA a pHYsNtEXtSoftwarepaint.net 4.0.5e2exIDAT8Oc?>PHE>^g`i3CEP-ov7?(^c?߳ /-jeD1|uӵg\V {\n?]WƇSr)d}/~/<ؗ)'b_5di[ {}}L|vkn˽n];ӹ(o-z$ 0TD}y&']pL}͕ZVbcS0 x>Oo ͿZ^8Q(Yxgf sǷ3b *4``n1c+Ws=<ڤxe-kH Ws~1?[9 Awijk^ L7 7 b0^d2WE#G3j恇4 %/43ۢd@0()_gxQx_;Ā?m3|pYY0?RCJk N3<j) ]- oNcຐx<lj0H='{kAIENDB`keyman-config-11.0.103/keyman_config/install_kmp.py000077500000000000000000000254421341361553100222000ustar00rootroot00000000000000#!/usr/bin/python3 import argparse import json import logging import os.path import subprocess import sys import tempfile import zipfile from os import listdir, makedirs from shutil import copy2, rmtree # from ast import literal_eval from enum import Enum import requests from keyman_config.get_kmp import get_keyboard_data, get_kmp, user_keyboard_dir, user_keyman_dir, user_keyman_font_dir from keyman_config.kmpmetadata import parseinfdata, parsemetadata, get_metadata, infmetadata_to_json, KMFileTypes from keyman_config.uninstall_kmp import uninstall_kmp from keyman_config.convertico import checkandsaveico from keyman_config.kvk2ldml import convert_kvk_to_ldml, output_ldml from keyman_config.ibus_util import install_to_ibus, restart_ibus, get_ibus_bus #TODO userdir install # special processing for kmn if needed #TODO optionally standardise throughout on variable names # packageID for kmps and keyboardID for keyboards # see https://docs.google.com/document/d/1sj7W6pCiN-_iRss5iRdib1aHaSTmYoLIueQSKJeNy8Q/edit#heading=h.mq0rc28mf031 class InstallStatus(Enum): Continue = 0 Warning = 1 Abort = 2 class InstallError(Exception): """Exception raised for errors in KMP installation. Attributes: status -- InstallStatus for what to do when the error occurrs message -- explanation of the error """ def __init__(self, status, message): self.status = status self.message = message def list_files(directory, extension): return (f for f in listdir(directory) if f.endswith('.' + extension)) def extract_kmp(kmpfile, directory): with zipfile.ZipFile(kmpfile,"r") as zip_ref: zip_ref.extractall(directory) def download_source(keyboardID, packageDir, sourcePath): # just get latest version of kmn unless there turns out to be a way to get the version of a file at a date base_url = "https://raw.github.com/keymanapp/keyboards/master/" + sourcePath kmn_url = base_url + "/source/" + keyboardID + ".kmn" response = requests.get(kmn_url) if response.status_code == 200: kmn_file = os.path.join(packageDir, keyboardID + ".kmn") with open(kmn_file, 'wb') as f: f.write(response.content) logging.info("Installing %s.kmn as keyman file", keyboardID) logging.info("Compiling kmn file") subprocess.run(["kmflcomp", kmn_file], stdout=subprocess.PIPE, stderr= subprocess.STDOUT) # kmfl_file = os.path.join(kbdir, kbid + ".kmfl") # if not os.path.isfile(kmfl_file): # message = "Could not compile %s to %s so not installing keyboard." % (kmn_file, kmfl_file) # os.remove(kmn_file) # rmtree(kbdir) # raise InstallError(InstallStatus.Abort, message) # else: # message = "install_kmp.py: warning: no kmn source file for %s so not installing keyboard." % (kbid) # rmtree(kbdir) # raise InstallError(InstallStatus.Abort, message) icodownloadfile = os.path.join(packageDir, keyboardID + ".ico") if not os.path.isfile(icodownloadfile): ico_url = base_url + "/source/" + keyboardID + ".ico" response = requests.get(ico_url) if response.status_code == 200: with open(icodownloadfile, 'wb') as f: f.write(response.content) logging.info("Installing %s.ico as keyman file", keyboardID) checkandsaveico(icodownloadfile) else: logging.warning("install_kmp.py: warning: no ico source file for %s", keyboardID) def process_keyboard_data(keyboardID, packageDir): kbdata = get_keyboard_data(keyboardID) if kbdata: if not os.path.isdir(packageDir): os.makedirs(packageDir) if 'sourcePath' in kbdata: download_source(keyboardID, packageDir, kbdata['sourcePath']) with open(os.path.join(packageDir, keyboardID + '.json'), 'w') as outfile: json.dump(kbdata, outfile) logging.info("Installing api data file %s.json as keyman file", keyboardID) # else: # message = "install_kmp.py: error: cannot download keyboard data so not installing." # rmtree(kbdir) # raise InstallError(InstallStatus.Abort, message) def check_keyman_dir(basedir, error_message): # check if keyman subdir exists keyman_dir = os.path.join(basedir, "keyman") if os.path.isdir(keyman_dir): # Check for write access of keyman dir to be able to create subdir if not os.access(keyman_dir, os.X_OK | os.W_OK): raise InstallError(InstallStatus.Abort, error_message) else: # Check for write access of basedir and create keyman subdir if we can if not os.access(basedir, os.X_OK | os.W_OK): raise InstallError(InstallStatus.Abort, error_message) os.mkdir(keyman_dir) def install_kmp_shared(inputfile, online=False): """ Install a kmp file to /usr/local/share/keyman Args: inputfile (str): path to kmp file online(bool, default=False): whether to attempt to get a source kmn and ico for the keyboard """ check_keyman_dir('/usr/local/share', "You do not have permissions to install the keyboard files to the shared area /usr/local/share/keyman") check_keyman_dir('/usr/local/share/doc', "You do not have permissions to install the documentation to the shared documentation area /usr/local/share/doc/keyman") check_keyman_dir('/usr/local/share/fonts', "You do not have permissions to install the font files to the shared font area /usr/local/share/fonts") packageID, ext = os.path.splitext(os.path.basename(inputfile)) packageDir = os.path.join('/usr/local/share/keyman', packageID) kmpdocdir = os.path.join('/usr/local/share/doc/keyman', packageID) kmpfontdir = os.path.join('/usr/local/share/fonts/keyman', packageID) if not os.path.isdir(packageDir): os.makedirs(packageDir) extract_kmp(inputfile, packageDir) #restart IBus so it knows about the keyboards being installed logging.debug("restarting IBus") restart_ibus() info, system, options, keyboards, files = get_metadata(packageDir) if keyboards: logging.info("Installing %s", info['name']['description']) if online: process_keyboard_data(packageID, packageDir) if len(keyboards) > 1: for kb in keyboards: if kb['id'] != packageID: process_keyboard_data(kb['id'], packageDir) for f in files: fpath = os.path.join(packageDir, f['name']) ftype = f['type'] if ftype == KMFileTypes.KM_DOC or ftype == KMFileTypes.KM_IMAGE: #Special handling of doc and images to hard link them into doc dir logging.info("Installing %s as documentation", f['name']) if not os.path.isdir(kmpdocdir): os.makedirs(kmpdocdir) os.link(fpath, os.path.join(kmpdocdir, f['name'])) elif ftype == KMFileTypes.KM_FONT: #Special handling of font to hard link it into font dir logging.info("Installing %s as font", f['name']) if not os.path.isdir(kmpfontdir): os.makedirs(kmpfontdir) os.link(fpath, os.path.join(kmpfontdir, f['name'])) elif ftype == KMFileTypes.KM_SOURCE: #TODO for the moment just leave it for ibus-kmfl to ignore if it doesn't load logging.info("Installing %s as keyman file", f['name']) elif ftype == KMFileTypes.KM_OSK: # Special handling to convert kvk into LDML logging.info("Converting %s to LDML and installing both as as keyman file", f['name']) ldml = convert_kvk_to_ldml(fpath) name, ext = os.path.splitext(f['name']) ldmlfile = os.path.join(packageDir, name+".ldml") output_ldml(ldmlfile, ldml) # Special handling of icon to convert to PNG elif ftype == KMFileTypes.KM_ICON: logging.info("Converting %s to PNG and installing both as keyman files", f['name']) checkandsaveico(fpath) for kb in keyboards: # install all kmx for first lang not just packageID kmx_file = os.path.join(packageDir, kb['id'] + ".kmx") install_to_ibus(lang, kmx_file) else: logging.error("install_kmp.py: error: No kmp.json or kmp.inf found in %s", inputfile) logging.info("Contents of %s:", inputfile) for o in os.listdir(packageDir): logging.info(o) rmtree(packageDir) message = "install_kmp.py: error: No kmp.json or kmp.inf found in %s" % (inputfile) raise InstallError(InstallStatus.Abort, message) def install_kmp_user(inputfile, online=False): packageID, ext = os.path.splitext(os.path.basename(inputfile)) packageDir=user_keyboard_dir(packageID) if not os.path.isdir(packageDir): os.makedirs(packageDir) extract_kmp(inputfile, packageDir) #restart IBus so it knows about the keyboards being installed restart_ibus() info, system, options, keyboards, files = get_metadata(packageDir) if keyboards: logging.info("Installing %s", info['name']['description']) if online: process_keyboard_data(packageID, packageDir) if len(keyboards) > 1: for kb in keyboards: if kb['id'] != packageID: process_keyboard_data(kb['id'], packageDir) for f in files: fpath = os.path.join(packageDir, f['name']) ftype = f['type'] if ftype == KMFileTypes.KM_FONT: #Special handling of font to hard link it into font dir fontsdir = os.path.join(user_keyman_font_dir(), packageID) if not os.path.isdir(fontsdir): os.makedirs(fontsdir) os.link(fpath, os.path.join(fontsdir, f['name'])) logging.info("Installing %s as font", f['name']) elif ftype == KMFileTypes.KM_OSK: # Special handling to convert kvk into LDML logging.info("Converting %s to LDML and installing both as as keyman file", f['name']) ldml = convert_kvk_to_ldml(fpath) name, ext = os.path.splitext(f['name']) ldmlfile = os.path.join(packageDir, name+".ldml") output_ldml(ldmlfile, ldml) elif ftype == KMFileTypes.KM_ICON: # Special handling of icon to convert to PNG logging.info("Converting %s to PNG and installing both as keyman files", f['name']) checkandsaveico(fpath) elif ftype == KMFileTypes.KM_SOURCE: #TODO for the moment just leave it for ibus-kmfl to ignore if it doesn't load pass install_keyboards_to_ibus(keyboards, packageDir) else: logging.error("install_kmp.py: error: No kmp.json or kmp.inf found in %s", inputfile) logging.info("Contents of %s:", inputfile) for o in os.listdir(packageDir): logging.info(o) rmtree(packageDir) message = "install_kmp.py: error: No kmp.json or kmp.inf found in %s" % (inputfile) raise InstallError(InstallStatus.Abort, message) def install_keyboards_to_ibus(keyboards, packageDir): bus = get_ibus_bus() if bus: # install all kmx for first lang not just packageID for kb in keyboards: kmx_file = os.path.join(packageDir, kb['id'] + ".kmx") if "languages" in kb: logging.debug(kb["languages"][0]) keyboard_id = "%s:%s" % (kb["languages"][0]['id'], kmx_file) else: keyboard_id = kmx_file install_to_ibus(bus, keyboard_id) restart_ibus(bus) bus.destroy() else: logging.debug("could not install keyboards to IBus") def install_kmp(inputfile, online=False, sharedarea=False): """ Install a kmp file Args: inputfile (str): path to kmp file online(bool, default=False): whether to attempt to get a source kmn and ico for the keyboard sharedarea(bool, default=False): whether install kmp to shared area or user directory """ if sharedarea: install_kmp_shared(inputfile, online) else: install_kmp_user(inputfile, online) keyman-config-11.0.103/keyman_config/install_window.py000077500000000000000000000354471341361553100227260ustar00rootroot00000000000000#!/usr/bin/python3 # Install confirmation with details window import logging import os.path import pathlib import subprocess import sys import webbrowser import tempfile import gi gi.require_version('Gtk', '3.0') gi.require_version('WebKit2', '4.0') from gi.repository import Gtk, WebKit2 from distutils.version import StrictVersion from keyman_config.install_kmp import install_kmp, extract_kmp, get_metadata, InstallError, InstallStatus from keyman_config.list_installed_kmp import get_kmp_version from keyman_config.kmpmetadata import get_fonts from keyman_config.welcome import WelcomeView from keyman_config.uninstall_kmp import uninstall_kmp from keyman_config.get_kmp import get_download_folder, user_keyboard_dir from keyman_config.check_mime_type import check_mime_type from keyman_config.accelerators import bind_accelerator, init_accel def find_keyman_image(image_file): img_path = os.path.join("/usr/share/keyman/icons", image_file) if not os.path.isfile(img_path): img_path = os.path.join("/usr/local/share/keyman/icons/", image_file) if not os.path.isfile(img_path): img_path = os.path.join("keyman_config/icons/", image_file) if not os.path.isfile(img_path): img_path = image_file if not os.path.isfile(img_path): img_path = os.path.join("icons", image_file) return img_path class InstallKmpWindow(Gtk.Window): def __init__(self, kmpfile, online=False, viewkmp=None, downloadwindow=None): logging.debug("InstallKmpWindow: kmpfile: %s", kmpfile) self.kmpfile = kmpfile self.online = online self.endonclose = False self.viewwindow = viewkmp self.download = downloadwindow self.accelerators = None keyboardid = os.path.basename(os.path.splitext(kmpfile)[0]) installed_kmp_ver = get_kmp_version(keyboardid) if installed_kmp_ver: logging.info("installed kmp version %s", installed_kmp_ver) windowtitle = "Installing keyboard/package " + keyboardid Gtk.Window.__init__(self, title=windowtitle) init_accel(self) self.set_border_width(12) vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=12) mainhbox = Gtk.Box() with tempfile.TemporaryDirectory() as tmpdirname: extract_kmp(kmpfile, tmpdirname) info, system, options, keyboards, files = get_metadata(tmpdirname) self.kbname = keyboards[0]['name'] self.checkcontinue = True if installed_kmp_ver: if info['version']['description'] == installed_kmp_ver: dialog = Gtk.MessageDialog(self, 0, Gtk.MessageType.QUESTION, Gtk.ButtonsType.YES_NO, "Keyboard is installed already") dialog.format_secondary_text( "The " + self.kbname + " keyboard is already installed at version " + installed_kmp_ver + ". Do you want to uninstall then reinstall it?") response = dialog.run() dialog.destroy() if response == Gtk.ResponseType.YES: logging.debug("QUESTION dialog closed by clicking YES button") uninstall_kmp(keyboardid) elif response == Gtk.ResponseType.NO: logging.debug("QUESTION dialog closed by clicking NO button") self.checkcontinue = False else: try: logging.info("package version %s", info['version']['description']) logging.info("installed kmp version %s", installed_kmp_ver) if StrictVersion(info['version']['description']) <= StrictVersion(installed_kmp_ver): dialog = Gtk.MessageDialog(self, 0, Gtk.MessageType.QUESTION, Gtk.ButtonsType.YES_NO, "Keyboard is installed already") dialog.format_secondary_text( "The " + self.kbname + " keyboard is already installed with a newer version " + installed_kmp_ver + ". Do you want to uninstall it and install the older version" + info['version']['description'] + "?") response = dialog.run() dialog.destroy() if response == Gtk.ResponseType.YES: logging.debug("QUESTION dialog closed by clicking YES button") uninstall_kmp(keyboardid) elif response == Gtk.ResponseType.NO: logging.debug("QUESTION dialog closed by clicking NO button") self.checkcontinue = False except: logging.warning("Exception uninstalling an old kmp, continuing") pass image = Gtk.Image() if options and "graphicFile" in options: image.set_from_file(os.path.join(tmpdirname, options['graphicFile'])) else: img_default = find_keyman_image("defaultpackage.gif") image.set_from_file(img_default) mainhbox.pack_start(image, False, False, 0) self.page1 = Gtk.Box() self.page1.set_border_width(12) grid = Gtk.Grid() self.page1.add(grid) label1 = Gtk.Label() label1.set_text("Keyboard layouts: ") label1.set_halign(Gtk.Align.END) grid.add(label1) prevlabel = label1 label = Gtk.Label() keyboardlayout = "" for kb in keyboards: if keyboardlayout != "": keyboardlayout = keyboardlayout + "\n" keyboardlayout = keyboardlayout + kb['name'] label.set_text(keyboardlayout) label.set_halign(Gtk.Align.START) label.set_selectable(True) grid.attach_next_to(label, label1, Gtk.PositionType.RIGHT, 1, 1) fonts = get_fonts(files) if fonts: label2 = Gtk.Label() # Fonts are optional label2.set_text("Fonts: ") label2.set_halign(Gtk.Align.END) grid.attach_next_to(label2, prevlabel, Gtk.PositionType.BOTTOM, 1, 1) prevlabel = label2 label = Gtk.Label() fontlist = "" for font in fonts: if fontlist != "": fontlist = fontlist + "\n" if font['description'][:5] == "Font ": fontdesc = font['description'][5:] else: fontdesc = font['description'] fontlist = fontlist + fontdesc label.set_text(fontlist) label.set_halign(Gtk.Align.START) label.set_selectable(True) grid.attach_next_to(label, label2, Gtk.PositionType.RIGHT, 1, 1) label3 = Gtk.Label() label3.set_text("Package version: ") label3.set_halign(Gtk.Align.END) grid.attach_next_to(label3, prevlabel, Gtk.PositionType.BOTTOM, 1, 1) prevlabel = label3 label = Gtk.Label() label.set_text(info['version']['description']) label.set_halign(Gtk.Align.START) label.set_selectable(True) grid.attach_next_to(label, label3, Gtk.PositionType.RIGHT, 1, 1) if info and 'author' in info: label4 = Gtk.Label() label4.set_text("Author: ") label4.set_halign(Gtk.Align.END) grid.attach_next_to(label4, prevlabel, Gtk.PositionType.BOTTOM, 1, 1) prevlabel = label4 label = Gtk.Label() if 'url' in info['author']: label.set_markup("" + info['author']['description'] + "") else: label.set_text(info['author']['description']) label.set_halign(Gtk.Align.START) label.set_selectable(True) grid.attach_next_to(label, label4, Gtk.PositionType.RIGHT, 1, 1) if info and 'website' in info: label5 = Gtk.Label() # Website is optional and may be a mailto for the author label5.set_text("Website: ") label5.set_halign(Gtk.Align.END) grid.attach_next_to(label5, prevlabel, Gtk.PositionType.BOTTOM, 1, 1) prevlabel = label5 label = Gtk.Label() label.set_markup("" + info['website']['description'] + "") label.set_halign(Gtk.Align.START) label.set_selectable(True) grid.attach_next_to(label, label5, Gtk.PositionType.RIGHT, 1, 1) if info and 'copyright' in info: label6 = Gtk.Label() label6.set_text("Copyright: ") label6.set_halign(Gtk.Align.END) grid.attach_next_to(label6, prevlabel, Gtk.PositionType.BOTTOM, 1, 1) label = Gtk.Label() label.set_text(info['copyright']['description']) label.set_halign(Gtk.Align.START) label.set_selectable(True) grid.attach_next_to(label, label6, Gtk.PositionType.RIGHT, 1, 1) self.page2 = Gtk.Box() webview = WebKit2.WebView() webview.connect("decide-policy", self.doc_policy) if options and "readmeFile" in options: self.readme = options['readmeFile'] else: self.readme = "noreadme" readme_file = os.path.join(tmpdirname, self.readme) if os.path.isfile(readme_file): with open(readme_file, "r") as read_file: readme_data = read_file.read() readme_uri = pathlib.Path(readme_file).as_uri() logging.debug(readme_data) webview.load_html(readme_data, readme_uri) s = Gtk.ScrolledWindow() s.add(webview) self.page2.pack_start(s, True, True, 0) self.notebook = Gtk.Notebook() self.notebook.set_tab_pos(Gtk.PositionType.BOTTOM) mainhbox.pack_start(self.notebook, True, True, 0) self.notebook.append_page( self.page1, Gtk.Label('Details')) self.notebook.append_page( self.page2, Gtk.Label('README')) else: mainhbox.pack_start(self.page1, True, True, 0) vbox.pack_start(mainhbox, True, True, 0) hbox = Gtk.Box(spacing=6) vbox.pack_start(hbox, False, False, 0) button = Gtk.Button.new_with_mnemonic("_Install") button.connect("clicked", self.on_install_clicked) hbox.pack_start(button, False, False, 0) button = Gtk.Button.new_with_mnemonic("_Cancel") button.connect("clicked", self.on_cancel_clicked) hbox.pack_end(button, False, False, 0) bind_accelerator(self.accelerators, button, 'w') self.add(vbox) self.resize(635, 270) def doc_policy(self, web_view, decision, decision_type): logging.info("Checking policy") logging.debug("received policy decision request of type: {0}".format(decision_type.value_name)) if decision_type == WebKit2.PolicyDecisionType.NAVIGATION_ACTION: nav_action = decision.get_navigation_action() request = nav_action.get_request() uri = request.get_uri() logging.debug("nav request is for uri %s", uri) if not self.readme in uri: logging.debug("opening uri %s in webbrowser") webbrowser.open(uri) decision.ignore() return True return False def on_install_clicked(self, button): logging.info("Installing keyboard") try: install_kmp(self.kmpfile, self.online) if self.viewwindow: self.viewwindow.refresh_installed_kmp() if self.download: self.download.close() keyboardid = os.path.basename(os.path.splitext(self.kmpfile)[0]) welcome_file = os.path.join(user_keyboard_dir(keyboardid), "welcome.htm") if os.path.isfile(welcome_file): uri_path = pathlib.Path(welcome_file).as_uri() logging.debug(uri_path) w = WelcomeView(uri_path, self.kbname) w.resize(800, 600) w.show_all() else: dialog = Gtk.MessageDialog(self, 0, Gtk.MessageType.INFO, Gtk.ButtonsType.OK, "Keyboard " + self.kbname + " installed") dialog.run() dialog.destroy() except InstallError as e: if e.status == InstallStatus.Abort: message = "Keyboard " + self.kbname + " could not be installed.\n\nError Message:\n%s" % (e.message) logging.error(message) message_type = Gtk.MessageType.ERROR else: message = "Keyboard " + self.kbname + " could not be installed fully.\n\nWarning Message:\n%s" % (e.message) logging.warning(message) message_type = Gtk.MessageType.WARNING dialog = Gtk.MessageDialog(self, 0, message_type, Gtk.ButtonsType.OK, message) dialog.run() dialog.destroy() if not self.endonclose: self.close() def on_cancel_clicked(self, button): logging.info("Cancel install keyboard") if self.endonclose: Gtk.main_quit() else: self.close() def connectdestroy(self): self.connect("destroy", Gtk.main_quit) self.endonclose = True def main(argv): if len(sys.argv) != 2: logging.error("install_window.py ") sys.exit(2) name, ext = os.path.splitext(sys.argv[1]) if ext != ".kmp": logging.error("install_window.py Input file", sys.argv[1], "is not a kmp file.") logging.error("install_window.py ") sys.exit(2) if not os.path.isfile(sys.argv[1]): logging.error("install_window.py Keyman kmp file", sys.argv[1], "not found.") logging.error("install_window.py ") sys.exit(2) w = InstallKmpWindow(sys.argv[1]) w.connectdestroy() w.resize(800, 450) if w.checkcontinue: w.show_all() Gtk.main() if __name__ == "__main__": main(sys.argv[1:]) keyman-config-11.0.103/keyman_config/keyboard_details.py000066400000000000000000000315671341361553100231720ustar00rootroot00000000000000#!/usr/bin/python3 # Keyboard details window import json import logging import os.path import gi gi.require_version('Gtk', '3.0') from gi.repository import Gtk from keyman_config.accelerators import bind_accelerator, init_accel from keyman_config.get_kmp import get_keyboard_data from keyman_config.kmpmetadata import parsemetadata # basics: keyboard name, package version, description # other things: filename (of kmx), , # OSK availability, documentation availability, package copyright # also: supported languages, fonts # from kmx?: keyboard version, encoding, layout type # there is data in kmp.inf/kmp.json # there is possibly data in kbid.json (downloaded from api) class KeyboardDetailsView(Gtk.Window): # TODO Display all the information that is available # especially what is displayed for Keyman on Windows # TODO clean up file once have what we want def __init__(self, kmp): #kmp has name, version, packageID, area if "keyboard" in kmp["name"].lower(): wintitle = kmp["name"] else: wintitle = kmp["name"] + " keyboard" Gtk.Window.__init__(self, title=wintitle) init_accel(self) vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=12) packageDir = os.path.join(kmp['areapath'], kmp['packageID']) kmp_json = os.path.join(packageDir, "kmp.json") info, system, options, keyboards, files = parsemetadata(kmp_json) if (info == None): raise Exception("could not parse kmp.json", kmp['packageID'], packageDir, kmp_json) kbdata = None jsonfile = os.path.join(packageDir, kmp['packageID'] + ".json") if os.path.isfile(jsonfile): with open(jsonfile, "r") as read_file: kbdata = json.load(read_file) box = Gtk.Box(spacing=10) #self.add(box) grid = Gtk.Grid() #grid.set_column_homogeneous(True) box.add(grid) #self.add(grid) # kbdatapath = os.path.join("/usr/local/share/keyman", kmp["id"], kmp["id"] + ".json") # Package info lbl_pkg_name = Gtk.Label() lbl_pkg_name.set_text("Package name: ") lbl_pkg_name.set_halign(Gtk.Align.END) grid.add(lbl_pkg_name) prevlabel = lbl_pkg_name label = Gtk.Label() label.set_text(info['name']['description']) label.set_halign(Gtk.Align.START) label.set_selectable(True) grid.attach_next_to(label, lbl_pkg_name, Gtk.PositionType.RIGHT, 1, 1) lbl_pkg_id = Gtk.Label() lbl_pkg_id.set_text("Package id: ") lbl_pkg_id.set_halign(Gtk.Align.END) grid.attach_next_to(lbl_pkg_id, prevlabel, Gtk.PositionType.BOTTOM, 1, 1) prevlabel = lbl_pkg_id label = Gtk.Label() label.set_text(kmp['packageID']) label.set_halign(Gtk.Align.START) label.set_selectable(True) grid.attach_next_to(label, lbl_pkg_id, Gtk.PositionType.RIGHT, 1, 1) lbl_pkg_vrs = Gtk.Label() lbl_pkg_vrs.set_text("Package version: ") lbl_pkg_vrs.set_halign(Gtk.Align.END) grid.attach_next_to(lbl_pkg_vrs, prevlabel, Gtk.PositionType.BOTTOM, 1, 1) prevlabel = lbl_pkg_vrs label = Gtk.Label() label.set_text(info['version']['description']) label.set_halign(Gtk.Align.START) label.set_selectable(True) grid.attach_next_to(label, lbl_pkg_vrs, Gtk.PositionType.RIGHT, 1, 1) if kbdata: lbl_pkg_desc = Gtk.Label() lbl_pkg_desc.set_text("Package description: ") lbl_pkg_desc.set_halign(Gtk.Align.END) grid.attach_next_to(lbl_pkg_desc, prevlabel, Gtk.PositionType.BOTTOM, 1, 1) prevlabel = lbl_pkg_desc label = Gtk.Label() label.set_text(kbdata['description']) label.set_halign(Gtk.Align.START) label.set_selectable(True) label.set_line_wrap(80) grid.attach_next_to(label, lbl_pkg_desc, Gtk.PositionType.RIGHT, 1, 1) if "author" in info: lbl_pkg_auth = Gtk.Label() lbl_pkg_auth.set_text("Package author: ") lbl_pkg_auth.set_halign(Gtk.Align.END) grid.attach_next_to(lbl_pkg_auth, prevlabel, Gtk.PositionType.BOTTOM, 1, 1) prevlabel = lbl_pkg_auth label = Gtk.Label() label.set_text(info['author']['description']) label.set_halign(Gtk.Align.START) label.set_selectable(True) grid.attach_next_to(label, lbl_pkg_auth, Gtk.PositionType.RIGHT, 1, 1) lbl_pkg_cpy = Gtk.Label() lbl_pkg_cpy.set_text("Package copyright: ") lbl_pkg_cpy.set_halign(Gtk.Align.END) grid.attach_next_to(lbl_pkg_cpy, prevlabel, Gtk.PositionType.BOTTOM, 1, 1) prevlabel = lbl_pkg_cpy label = Gtk.Label() label.set_text(info['copyright']['description']) label.set_halign(Gtk.Align.START) label.set_selectable(True) grid.attach_next_to(label, lbl_pkg_cpy, Gtk.PositionType.RIGHT, 1, 1) # Keyboard info for each keyboard if keyboards: for kbd in keyboards: kbdata = None jsonfile = os.path.join(packageDir, kbd['id'] + ".json") if os.path.isfile(jsonfile): with open(jsonfile, "r") as read_file: kbdata = json.load(read_file) # show the icon somewhere lbl_kbd_file = Gtk.Label() lbl_kbd_file.set_text("Keyboard filename: ") lbl_kbd_file.set_halign(Gtk.Align.END) grid.attach_next_to(lbl_kbd_file, prevlabel, Gtk.PositionType.BOTTOM, 1, 1) prevlabel = lbl_kbd_file label = Gtk.Label() label.set_text(os.path.join(packageDir, kbd['id'] + ".kmx")) label.set_halign(Gtk.Align.START) label.set_selectable(True) grid.attach_next_to(label, lbl_kbd_file, Gtk.PositionType.RIGHT, 1, 1) if kbdata: if kbdata['id'] != kmp['packageID']: lbl_kbd_name = Gtk.Label() lbl_kbd_name.set_text("Keyboard name: ") lbl_kbd_name.set_halign(Gtk.Align.END) grid.attach_next_to(lbl_kbd_name, prevlabel, Gtk.PositionType.BOTTOM, 1, 1) prevlabel = lbl_kbd_name label = Gtk.Label() label.set_text(kbdata['name']) label.set_halign(Gtk.Align.START) label.set_selectable(True) grid.attach_next_to(label, lbl_kbd_name, Gtk.PositionType.RIGHT, 1, 1) lbl_kbd_id = Gtk.Label() lbl_kbd_id.set_text("Keyboard id: ") lbl_kbd_id.set_halign(Gtk.Align.END) grid.attach_next_to(lbl_kbd_id, prevlabel, Gtk.PositionType.BOTTOM, 1, 1) prevlabel = lbl_kbd_id label = Gtk.Label() label.set_text(kbdata['id']) label.set_halign(Gtk.Align.START) label.set_selectable(True) grid.attach_next_to(label, lbl_kbd_id, Gtk.PositionType.RIGHT, 1, 1) lbl_kbd_vrs = Gtk.Label() lbl_kbd_vrs.set_text("Keyboard version: ") lbl_kbd_vrs.set_halign(Gtk.Align.END) grid.attach_next_to(lbl_kbd_vrs, prevlabel, Gtk.PositionType.BOTTOM, 1, 1) prevlabel = lbl_kbd_vrs label = Gtk.Label() label.set_text(kbdata['version']) label.set_halign(Gtk.Align.START) label.set_selectable(True) grid.attach_next_to(label, lbl_kbd_vrs, Gtk.PositionType.RIGHT, 1, 1) if "author" in info: lbl_kbd_auth = Gtk.Label() lbl_kbd_auth.set_text("Keyboard author: ") lbl_kbd_auth.set_halign(Gtk.Align.END) grid.attach_next_to(lbl_kbd_auth, prevlabel, Gtk.PositionType.BOTTOM, 1, 1) prevlabel = lbl_kbd_auth label = Gtk.Label() label.set_text(kbdata['authorName']) label.set_halign(Gtk.Align.START) label.set_selectable(True) grid.attach_next_to(label, lbl_kbd_auth, Gtk.PositionType.RIGHT, 1, 1) lbl_kbd_lic = Gtk.Label() lbl_kbd_lic.set_text("Keyboard license: ") lbl_kbd_lic.set_halign(Gtk.Align.END) grid.attach_next_to(lbl_kbd_lic, prevlabel, Gtk.PositionType.BOTTOM, 1, 1) prevlabel = lbl_kbd_lic label = Gtk.Label() label.set_text(kbdata['license']) label.set_halign(Gtk.Align.START) label.set_selectable(True) grid.attach_next_to(label, lbl_kbd_lic, Gtk.PositionType.RIGHT, 1, 1) lbl_kbd_desc = Gtk.Label() lbl_kbd_desc.set_text("Keyboard description: ") lbl_kbd_desc.set_halign(Gtk.Align.END) grid.attach_next_to(lbl_kbd_desc, prevlabel, Gtk.PositionType.BOTTOM, 1, 1) prevlabel = lbl_kbd_desc label = Gtk.Label() label.set_text(kbdata['description']) label.set_halign(Gtk.Align.START) label.set_selectable(True) label.set_line_wrap(80) grid.attach_next_to(label, lbl_kbd_desc, Gtk.PositionType.RIGHT, 1, 1) # label7 = Gtk.Label() # label7.set_text("On Screen Keyboard: ") # label7.set_halign(Gtk.Align.END) # grid.attach_next_to(label7, prevlabel, Gtk.PositionType.BOTTOM, 1, 1) # prevlabel = label7 # # label = Gtk.Label() # # label.set_text(info['version']['description']) # # label.set_halign(Gtk.Align.START) # # label.set_selectable(True) # # grid.attach_next_to(label, label7, Gtk.PositionType.RIGHT, 1, 1) # label8 = Gtk.Label() # label8.set_text("Documentation: ") # label8.set_halign(Gtk.Align.END) # grid.attach_next_to(label8, prevlabel, Gtk.PositionType.BOTTOM, 1, 1) # prevlabel = label8 # #TODO need to know which area keyboard is installed in to show this # # label = Gtk.Label() # # welcome_file = os.path.join("/usr/local/share/doc/keyman", kmp["id"], "welcome.htm") # # if os.path.isfile(welcome_file): # # label.set_text("Installed") # # else: # # label.set_text("Not installed") # # label.set_halign(Gtk.Align.START) # # label.set_selectable(True) # # grid.attach_next_to(label, label8, Gtk.PositionType.RIGHT, 1, 1) # label9 = Gtk.Label() # # stored in kmx # label9.set_text("Message: ") # label9.set_halign(Gtk.Align.END) # grid.attach_next_to(label9, prevlabel, Gtk.PositionType.BOTTOM, 1, 1) # prevlabel = label9 # label = Gtk.Label() # label.set_line_wrap(True) # label.set_text("This keyboard is distributed under the MIT license (MIT) as described somewhere") # #label.set_text(kmp["description"]) # label.set_halign(Gtk.Align.START) # label.set_selectable(True) # grid.attach_next_to(label, label9, Gtk.PositionType.RIGHT, 1, 1) vbox.pack_start(box, True, True, 0) hbox = Gtk.Box(spacing=6) vbox.pack_start(hbox, False, False, 0) button = Gtk.Button.new_with_mnemonic("_Close") button.set_tooltip_text("Close window") button.connect("clicked", self.on_close_clicked) hbox.pack_end(button, False, False, 0) bind_accelerator(self.accelerators, button, 'w') self.add(vbox) self.resize(635, 270) def on_close_clicked(self, button): logging.debug("Closing keyboard details window") self.close() keyman-config-11.0.103/keyman_config/kmpmetadata.py000077500000000000000000000416251341361553100221540ustar00rootroot00000000000000#!/usr/bin/python3 import json import configparser import logging import sys import os.path import magic from enum import Enum class KMFileTypes(Enum): KM_ICON = 1 KM_SOURCE = 2 KM_OSK_SOURCE = 3 KM_KMX = 4 KM_OSK = 5 KM_TOUCH = 6 KM_FONT = 7 KM_DOC = 8 KM_META = 9 KM_IMAGE = 10 KM_TECKIT = 11 KM_CC = 12 KM_XML = 13 KM_UNKNOWN = 99 def print_info(info): try: print("---- Info ----") print("Name: ", info['name']['description']) print("Copyright: ", info['copyright']['description']) if 'version' in info: print("Version: ", info['version']['description']) if 'author' in info: print("Author: ", info['author']['description']) if 'url' in info['author']: print("Author URL: ", info['author']['url']) if 'website' in info: print("Website description: ", info['website']['description']) if 'url' in info['website']: print("Website URL: ", info['website']['url']) except Exception as e: print(type(e)) # the exception instance print(e.args) # arguments stored in .args print(e) # __str__ allows args to be printed directly, pass pass def print_system(system): try: print("---- System ----") if 'fileVersion' in system: print("File Version: ", system['fileVersion']) if 'keymanDeveloperVersion' in system: print("Keyman Developer Version: ", system['keymanDeveloperVersion']) except Exception as e: print(type(e)) # the exception instance print(e.args) # arguments stored in .args print(e) # __str__ allows args to be printed directly, pass pass def print_options(options): try: print("---- Options ----") if 'readmeFile' in options: print("Readme File: ", options['readmeFile']) if 'graphicFile' in options: print("Graphic File: ", options['graphicFile']) except Exception as e: print(type(e)) # the exception instance print(e.args) # arguments stored in .args print(e) # __str__ allows args to be printed directly, pass pass def print_keyboards(keyboards): try: print("---- Keyboards ----") for kb in keyboards: print("Keyboard Name: ", kb['name']) print("Keyboard Id: ", kb['id']) if 'version' in kb: print("Keyboard Version: ", kb['version']) if 'oskFont' in kb: print("Keyboard On screen keyboard Font: ", kb['oskFont']) if 'oskFont' in kb: print("Keyboard Display Font: ", kb['displayFont']) print("Languages") for lang in kb['languages']: print(" Name: ", lang['name'], "Id: ", lang['id']) except Exception as e: print(type(e)) # the exception instance print(e.args) # arguments stored in .args print(e) # __str__ allows args to be printed directly, pass pass def determine_filetype(kblist, filename): """ Determine file type of a filename in a kmp from the extension Args: kblist (list): list of keyboard ids filename (str): File name Returns: KMFileTypes: Enum of file type KM_ICON: Keyboard icon KM_SOURCE: Keyboard source KM_OSK_SOURCE: Keyboard on-screen keyboard source KM_KMX: Compiled keyboard KM_OSK: Compiled on screen keyboard KM_TOUCH: JS touch keyboard KM_FONT: Font KM_DOC: Documentation KM_META: Metadata KM_IMAGE: Image KM_TECKIT: Files to use with teckit KM_CC: Consistent changes tables KM_XML: unspecified xml files KM_UNKNOWN: Unknown """ name, ext = os.path.splitext(filename) if ext.lower() == ".ico": return KMFileTypes.KM_ICON elif ext.lower() == ".kmn": return KMFileTypes.KM_SOURCE elif ext.lower() == ".kvks": return KMFileTypes.KM_OSK_SOURCE elif ext.lower() == ".kmx": return KMFileTypes.KM_KMX elif ext.lower() == ".kvk": return KMFileTypes.KM_OSK elif ext.lower() == ".ttf" or ext.lower() == ".otf": return KMFileTypes.KM_FONT elif ext.lower() == ".js": if kblist is None: return KMFileTypes.KM_UNKNOWN if name in kblist: return KMFileTypes.KM_TOUCH else: if name == "keyrenderer": # currently 2018-09-21 this is the own known non touch js file return KMFileTypes.KM_DOC else: return KMFileTypes.KM_UNKNOWN elif ext.lower() == ".txt" or ext.lower() == ".pdf" or ext.lower() == ".htm" \ or ext.lower() == ".html" or ext.lower() == ".doc" or ext.lower() == ".docx" \ or ext.lower() == ".css" or ext.lower() == ".chm" or ext.lower() == "" \ or ext.lower() == ".md" or ext.lower() == ".odt" or ext.lower() == ".rtf" \ or ext.lower() == ".dot" or ext.lower() == ".mht" or ext.lower() == ".woff" \ or ext.lower() == ".php": return KMFileTypes.KM_DOC elif ext.lower() == ".inf" or ext.lower() == ".json": return KMFileTypes.KM_META elif ext.lower() == ".png" or ext.lower() == ".jpeg" \ or ext.lower() == ".jpg" or ext.lower() == ".gif" \ or ext.lower() == ".bmp": return KMFileTypes.KM_IMAGE elif ext.lower() == ".tec" or ext.lower() == ".map": return KMFileTypes.KM_TECKIT elif ext.lower() == ".cct": return KMFileTypes.KM_CC elif ext.lower() == ".xml": return KMFileTypes.KM_XML else: return KMFileTypes.KM_UNKNOWN def print_files(files, extracted_dir): try: print("---- Files ----") for kbfile in files: print("* File name: ", kbfile['name']) print(" Description: ", kbfile['description']) print(" Type: ", kbfile['type']) file = os.path.join(extracted_dir, kbfile['name']) if os.path.isfile(file): print(" File", file, "exists") ms = magic.open(magic.MAGIC_NONE) ms.load() ftype = ms.file(file) print (" Type: ", ftype) ms.close() else: print(" File", file, "does not exist") except Exception as e: print(type(e)) # the exception instance print(e.args) # arguments stored in .args print(e) # __str__ allows args to be printed directly, pass pass def get_fonts(files): fonts = [] for kbfile in files: if kbfile['type'] == KMFileTypes.KM_FONT: fonts.append(kbfile) return fonts def parseinfdata(inffile, verbose=False): """ Parse the metadata in a kmp.inf file. Args: jsonfile (str): Path to kmp.inf verbose (bool, default False): verbose output Returns: list[5]: info, system, options, keyboards, files info (dict): name (dict): description (str): KMP name copyright (dict): description (str): KMP copyright version (dict): description (str): KMP version author (dict): description (str): KMP author url (str): contact url for the author system (dict): System info fileVersion (str): Keyman file format version keymanDeveloperVersion (str): Keyman Developer version that compiled keyboard options (dict): Keyboard options readmeFile (str) : README for the keyboard keyboards (list): Keyboards in the kmp name (str): Keyboard name id (str): Keyboard ID version (str): Keyboard version oskFont (str, optional): Recommended on screen keyboard font displayFont (str, optional): Recommended display font languages (list): Languages the keyboard is used for name (str): Language name id (str): Language ID files (list): Files in the kmp name (str): File name description (str): File description type (KMFileTypes): Keyman file type """ info = system = keyboards = files = options = nonexistent = None extracted_dir = os.path.dirname(inffile) if os.path.isfile(inffile): config = configparser.ConfigParser() config.optionxform = str logging.debug("parseinfdata: reading file:%s dir:%s", inffile, extracted_dir) with open(inffile, 'r', encoding='latin_1') as f: config.read_file(f) info = None for section in config.sections(): if section == 'Info': if not info: info = {} for item in config.items('Info'): if item[0] == 'Name' or item[0] == 'name': info['name'] = { 'description' : item[1].split("\"")[1] } elif item[0] == 'Copyright' or item[0] == 'copyright': info['copyright'] = { 'description' : item[1].split("\"")[1] } elif item[0] == 'Version': info['version'] = { 'description' : item[1].split("\"")[1] } elif item[0] == 'Author': info['author'] = { 'description' : item[1].split("\"")[1], 'url' : item[1].split("\"")[3] } elif item[0] == "WebSite": info['website'] = { 'description' : item[1].split("\"")[1], 'url' : item[1].split("\"")[3] } else: logging.warning("Unknown item in Info: %s", item[0]) if section == 'PackageInfo': if not info: info = {} info['version'] = { 'description' : "1.0" } for item in config.items('PackageInfo'): if item[0] == 'Name' or item[0] == 'name': if item[1].split("\"")[1]: info['name'] = { 'description' : item[1].split("\"")[1] } else: info['name'] = { 'description' : item[1].split("\"")[2] } elif item[0] == 'Copyright' or item[0] == 'copyright': if item[1].split("\"")[1]: info['copyright'] = { 'description' : item[1].split("\"")[1] } else: info['copyright'] = { 'description' : item[1].split("\"")[2] } elif item[0] == 'Version': info['version'] = { 'description' : item[1].split("\"")[1] } elif item[0] == 'Author': info['author'] = { 'description' : item[1].split("\"")[1], 'url' : item[1].split("\"")[3] } elif item[0] == "WebSite": if item[1].split("\"")[1]: info['website'] = { 'description' : item[1].split("\"")[1] } else: info['website'] = { 'description' : item[1].split("\"")[2] } else: logging.warning("Unknown item in Info: %s", item[0]) elif section == 'Package': system = {} if not options: options = {} for item in config.items('Package'): if item[0] == 'Version': system['fileVersion'] = item[1] elif item[0] == 'ReadMeFile': options['readmeFile'] = item[1] elif item[0] == 'GraphicFile': options['graphicFile'] = item[1] elif item[0] == 'DisableKeepFonts': options['disableKeepFonts'] = item[1] elif item[0] == 'BothVersionsIncluded': options['bothVersionsIncluded'] = item[1] elif item[0] == 'ExecuteProgram': pass else: print("Unknown item in Package:", item[0]) system['keymanDeveloperVersion'] = "" elif "Keyboard" in section: keyboards = [] keyboard = {} languages = [] for item in config.items(section): if item[0] == 'Name': keyboard['name'] = item[1] elif item[0] == 'ID': keyboard['id'] = item[1] elif item[0] == 'Version': keyboard['version'] = item[1] elif item[0] == 'OSKFont': keyboard['oskFont'] = item[1] elif item[0] == 'DisplayFont': keyboard['displayFont'] = item[1] elif item[0] == 'RTL': keyboard['RTL'] = item[1] elif "Language" in item[0]: # only split on first ',' langid, langname = item[1].split(",", 1) languages.append({ 'name' : langname, 'id' : langid }) else: logging.warning("Unknown item in keyboard: %s", item[0]) keyboard['languages'] = languages keyboards.append(keyboard) elif section == "Files": files = [] for item in config.items(section): splititem = item[1].split("\"") kbfile = { 'name' : splititem[3], 'description' : splititem[1], 'type' : determine_filetype(None, splititem[3]) } files.append(kbfile) elif section == "InstallFiles": files = [] for item in config.items(section): kbfile = { 'name' : item[0], 'description' : item[1], 'type' : determine_filetype(None, item[0]) } files.append(kbfile) elif section == 'Install': if not options: options = {} for item in config.items('Install'): if item[0] == 'ReadmeFile': options['readmeFile'] = item[1] kblist = [] if not info: info = {} if not 'version' in info: info['version'] = { 'description' : "1.0" } # inf file may not have keyboards in legacy kmps so generate it if needed if files and not keyboards: id = "unknown" keyboards = [] for kbfile in files: if kbfile['type'] == KMFileTypes.KM_KMX: id = os.path.basename(os.path.splitext(kbfile['name'])[0]) keyboards = [ { 'name' : id, 'id' : id, 'version' : info['version']['description'] } ] kblist = [] if files: for k in keyboards: kblist.append(k['id']) for kbfile in files: if kbfile['type'] == KMFileTypes.KM_UNKNOWN: kbfile['type'] = determine_filetype(kblist, kbfile['name']) logging.debug("finished parsing %s", inffile) if verbose: print_info(info) print_system(system) print_options(options) print_keyboards(keyboards) print_files(files, extracted_dir) return info, system, options, keyboards, files def parsemetadata(jsonfile, verbose=False): """ Parse the metadata in a kmp.json file. Args: jsonfile (str): Path to kmp.json verbose (bool, default False): verbose output Returns: list[5]: info, system, options, keyboards, files info (dict): name (dict): description (str): KMP name copyright (dict): description (str): KMP copyright version (dict): description (str): KMP version author (dict): description (str): KMP author url (str): contact url for the author system (dict): System info fileVersion (str): Keyman file format version keymanDeveloperVersion (str): Keyman Developer version that compiled keyboard options (dict): Keyboard options readmeFile (str) : README for the keyboard keyboards (list): Keyboards in the kmp name (str): Keyboard name id (str): Keyboard ID version (str): Keyboard version oskFont (str, optional): Recommended on screen keyboard font displayFont (str, optional): Recommended display font languages (list): Languages the keyboard is used for name (str): Language name id (str): Language ID files (list): Files in the kmp name (str): File name description (str): File description """ info = system = keyboards = files = options = nonexistent = None extracted_dir = os.path.dirname(jsonfile) logging.debug("parsemetadata: reading file:%s dir:%s", jsonfile, extracted_dir) if os.path.isfile(jsonfile): with open(jsonfile, "r") as read_file: data = json.load(read_file) for x in data: if x == 'info': info = data[x] elif x == 'system': system = data[x] elif x == 'keyboards': keyboards = data[x] elif x == 'files': files = data[x] elif x == 'options': options = data[x] elif x == 'nonexistent': nonexistent = data[x] kblist = [] for k in keyboards: kblist.append(k['id']) for kbfile in files: kbfile['type'] = determine_filetype(kblist, kbfile['name']) if nonexistent != None: logging.warning("This should not happen") if verbose: print_info(info) print_system(system) if options: print_options(options) print_keyboards(keyboards) print_files(files, extracted_dir) return info, system, options, keyboards, files def get_metadata(tmpdirname): """ Get metadata from kmp.json if it exists. If it does not exist then will return get_and_convert_infdata Args: inputfile (str): path to kmp file tmpdirname(str): temp directory to extract kmp Returns: list[5]: info, system, options, keyboards, files see kmpmetadata.parsemetadata for details """ kmpjson = os.path.join(tmpdirname, "kmp.json") if os.path.isfile(kmpjson): return parsemetadata(kmpjson, False) else: return get_and_convert_infdata(tmpdirname) def get_and_convert_infdata(tmpdirname): """ Get metadata from kmp.inf if it exists. Convert it to kmp.json if possible Args: inputfile (str): path to kmp file tmpdirname(str): temp directory to extract kmp Returns: list[5]: info, system, options, keyboards, files see kmpmetadata.parseinfdata for details """ kmpinf = os.path.join(tmpdirname, "kmp.inf") if os.path.isfile(kmpinf): info, system, options, keyboards, files = parseinfdata(kmpinf, False) j = infmetadata_to_json(info, system, options, keyboards, files) kmpjson = os.path.join(tmpdirname, "kmp.json") with open(kmpjson, "w") as write_file: print(j, file=write_file) return info, system, options, keyboards, files else: return None, None, None, None, None def infmetadata_to_json(info, system, options, keyboards, files): jsonfiles = [] for entry in files: jsonfiles.append({"name" : entry["name"], "description" : entry["description"]}) d = { "system" : system, "options" : options, "info" : info , "keyboards" : keyboards, "files" : jsonfiles } return json.dumps(d, indent=2) def main(argv): if len(sys.argv) != 2: logging.error("kmpmetadata.py or ") sys.exit(2) inputfile = sys.argv[1] if not os.path.isfile(inputfile): logging.error("kmpmetadata.py Input file ", inputfile, " not found.") logging.error("kmpmetadata.py or ") sys.exit(2) name, ext = os.path.splitext(inputfile) if ext == ".json": parsemetadata(inputfile, True) elif ext == ".inf": info, system, options, keyboards, files = parseinfdata(inputfile, True) jsontest = metadata_to_json(info, system, options, keyboards, files) print(jsontest) else: logging.error("kmpmetadata.py Input file must be json or inf.") logging.error("kmpmetadata.py or ") sys.exit(2) if __name__ == "__main__": main(sys.argv[1:]) keyman-config-11.0.103/keyman_config/kvk2ldml.py000077500000000000000000000330311341361553100214020ustar00rootroot00000000000000#!/usr/bin/python3 import argparse import logging import os.path import struct import sys from lxml import etree # .kvk file format # KVK files are variable length files with variable sized structures. # Magic 4 bytes 'KVKF' # Version 4 bytes 0x600 # Flags 1 byte bitmask: [102key?, DisplayUnderlying?, UseUnderlying?, AltGr?] kvkk102key = b'\x01' kvkkDisplayUnderlying = b'\x02' kvkkUseUnderlying = b'\x04' kvkkAltGr = b'\x08' # AssociatedKeyboard NSTRING # AnsiFont NFONT # UnicodeFont NFONT # KeyCount: DWORD # Keys: NKEY[KeyCount] class KVKData: magic = "" version = None flags = 0 key102 = False DisplayUnderlying = False UseUnderlying = False AltGr = False AssociatedKeyboard = "" AnsiFont = None UnicodeFont = None KeyCount = 0 Keys = [] # NSTRING = (Length: Word; Chars: WChar[Length]) # NFONT = (Name: NSTRING; Size: DWORD; Color: DWORD (RGBQuad)) class NFont: name = "" size = 0 red = 0 green = 0 blue = 0 resv = 0 # NKEY = ( # Flags: BYTE; // 1:kvkkBitmap, 2:kvkkUnicode kvkkBitmap = b'\x01' kvkkUnicode = b'\x02' # Shift: WORD; // See KVKS_* below # VKey: WORD; # Text: NSTRING; # Bitmap: NBITMAP # ) # NBITMAP = (BitmapSize: DWORD; Bitmap: BYTE[BitmapSize]) class NKey: number = 0 flags = 0 hasBitmap = False hasUnicode = False shiftflags = 0 Normal = False Shift = False Ctrl = False Alt = False LCtrl = False RCtrl = False LAlt = False RAlt = False VKey = 0 text = "" bitmap = None # // Note that these differ from the KMX modifier bitmasks # KVKS_NORMAL = 0; # KVKS_SHIFT = 1; # KVKS_CTRL = 2; # KVKS_ALT = 4; # KVKS_LCTRL = 8; # KVKS_RCTRL = 16; # KVKS_LALT = 32; # KVKS_RALT = 64; KVKS_NORMAL = b'\x00' KVKS_SHIFT = b'\x01' KVKS_CTRL = b'\x02' KVKS_ALT = b'\x04' KVKS_LCTRL = b'\x08' KVKS_RCTRL = b'\x10' KVKS_LALT = b'\x20' KVKS_RALT= b'\x40' # from web/source/kmwosk.ts VKey_to_Iso = { 90 : { "code": "B01", "base" : "z", "shift" : "Z" }, # Z 88 : { "code": "B02", "base" : "x", "shift" : "X" }, # X 67 : { "code": "B03", "base" : "c", "shift" : "C" }, # C 86 : { "code": "B04", "base" : "v", "shift" : "V" }, # V 66 : { "code": "B05", "base" : "b", "shift" : "B" }, # B 78 : { "code": "B06", "base" : "n", "shift" : "N" }, # N 77 : { "code": "B07", "base" : "m", "shift" : "M" }, # M 188 : { "code": "B08", "base" : ",", "shift" : "<" }, # , 190 : { "code": "B09", "base" : ".", "shift" : ">" }, # . 191 : { "code": "B10", "base" : "/", "shift" : "?" }, # / 65 : { "code": "C01", "base" : "a", "shift" : "A" }, # A 83 : { "code": "C02", "base" : "s", "shift" : "S" }, # S 68 : { "code": "C03", "base" : "d", "shift" : "D" }, # D 70 : { "code": "C04", "base" : "f", "shift" : "F" }, # F 71 : { "code": "C05", "base" : "g", "shift" : "G" }, # G 72 : { "code": "C06", "base" : "h", "shift" : "H" }, # H 74 : { "code": "C07", "base" : "j", "shift" : "J" }, # J 75 : { "code": "C08", "base" : "k", "shift" : "K" }, # K 76 : { "code": "C09", "base" : "l", "shift" : "L" }, # L 186 : { "code": "C10", "base" : ";", "shift" : ":" }, # ; 222 : { "code": "C11", "base" : "'", "shift" : '"' }, # ' 81 : { "code": "D01", "base" : "q", "shift" : "Q" }, # Q 87 : { "code": "D02", "base" : "w", "shift" : "W" }, # W 69 : { "code": "D03", "base" : "e", "shift" : "E" }, # E 82 : { "code": "D04", "base" : "r", "shift" : "R" }, # R 84 : { "code": "D05", "base" : "t", "shift" : "T" }, # T 89 : { "code": "D06", "base" : "y", "shift" : "Y" }, # Y 85 : { "code": "D07", "base" : "u", "shift" : "U" }, # U 73 : { "code": "D08", "base" : "i", "shift" : "I" }, # I 79 : { "code": "D09", "base" : "o", "shift" : "O" }, # O 80 : { "code": "D10", "base" : "p", "shift" : "P" }, # P 219 : { "code": "D11", "base" : "[", "shift" : "{" }, # [ 221 : { "code": "D12", "base" : "]", "shift" : "}" }, # ] 49 : { "code": "E01", "base" : "1", "shift" : "!" }, # 1 50 : { "code": "E02", "base" : "2", "shift" : "@" }, # 2 51 : { "code": "E03", "base" : "3", "shift" : "#" }, # 3 52 : { "code": "E04", "base" : "4", "shift" : "$" }, # 4 53 : { "code": "E05", "base" : "5", "shift" : "%" }, # 5 54 : { "code": "E06", "base" : "6", "shift" : "^" }, # 6 55 : { "code": "E07", "base" : "7", "shift" : "&" }, # 7 56 : { "code": "E08", "base" : "8", "shift" : "*" }, # 8 57 : { "code": "E09", "base" : "9", "shift" : "(" }, # 9 48 : { "code": "E10", "base" : "0", "shift" : ")" }, # 0 189 : { "code": "E11", "base" : "-", "shift" : "_" }, # - 187 : { "code": "E12", "base" : "=", "shift" : "+" }, # = 192 : { "code": "E00", "base" : "`", "shift" : "~" }, # ` 220 : { "code": "C12", "base" : "\\","shift" : "|" }, # \ 226 : { "code": "B00", "base" : "<", "shift" : ">" }, # extra key on european keyboards 32 : { "code": "A03", "base" : " ", "shift" : " " }, # space 97 : { "code": "B51", "base" : "1", "shift" : "1" }, # "K_NP1" 98 : { "code": "B52", "base" : "2", "shift" : "2" }, # "K_NP2" 99 : { "code": "B53", "base" : "3", "shift" : "3" }, # "K_NP3" 100 : { "code": "C51", "base" : "4", "shift" : "4" }, # "K_NP4" 101 : { "code": "C52", "base" : "5", "shift" : "5" }, # "K_NP5" 102 : { "code": "C53", "base" : "6", "shift" : "6" }, # "K_NP6" 103 : { "code": "D51", "base" : "7", "shift" : "7" }, # "K_NP7" 104 : { "code": "D52", "base" : "8", "shift" : "8" }, # "K_NP8" 105 : { "code": "D53", "base" : "9", "shift" : "9" }, # "K_NP9" } def bytecheck(value, check): if bytes([value & check[0]]) == check: return True else: return False def get_nkey(file, fileContent, offset): nkey = NKey() data = struct.unpack_from(" 256: logging.error("error: suspiciously long string. ABORT.") sys.exit(5) if stringlength[0]: #don't read the null string terminator stringdata = file.read((stringlength[0]-1)*2) else: stringdata = file.read(0) return stringdata.decode('utf-16'), offset + 2 + (2 * stringlength[0]) def get_nbitmap(file, fileContent, offset): bitmap = None bitmaplength = struct.unpack_from(" binary fileContent = file.read() kvkstart = struct.unpack_from("<4s4cc", fileContent, 0) kvkData.version = (kvkstart[1], kvkstart[2], kvkstart[3], kvkstart[4]) kvkData.flags = kvkstart[5] kvkData.key102 = bytecheck(kvkData.flags[0], kvkk102key) kvkData.DisplayUnderlying = bytecheck(kvkData.flags[0], kvkkDisplayUnderlying) kvkData.UseUnderlying = bytecheck(kvkData.flags[0], kvkkUseUnderlying) kvkData.AltGr = bytecheck(kvkData.flags[0], kvkkAltGr) kvkData.AssociatedKeyboard, newoffset = get_nstring(file, fileContent, struct.calcsize("<4s4cc")) kvkData.AnsiFont, newoffset = get_nfont(file, fileContent, newoffset) kvkData.UnicodeFont, newoffset = get_nfont(file, fileContent, newoffset) numkeys = struct.unpack_from("I", fileContent, newoffset) kvkData.KeyCount = numkeys[0] newoffset = newoffset + struct.calcsize("I") for num in range(numkeys[0]): nkey, newoffset = get_nkey(file, fileContent, newoffset) nkey.number = num kvkData.Keys.append(nkey) return kvkData def convert_kvk_to_ldml(kvkfile): kvkData = parse_kvk_file(kvkfile) return convert_ldml(kvkData)keyman-config-11.0.103/keyman_config/list_installed_kmp.py000077500000000000000000000122301341361553100235330ustar00rootroot00000000000000#!/usr/bin/python3 import argparse import logging import os import json import gi from gi.repository import GObject from keyman_config.kmpmetadata import parsemetadata, parseinfdata from keyman_config.get_kmp import user_keyman_dir class InstallArea(GObject.GEnum): IA_OS = 1 IA_SHARED = 2 IA_USER = 3 IA_UNKNOWN = 99 def get_installed_kmp(area): """ Get list of installed kmp in an install area. Args: area (InstallArea): install area to check InstallArea.IA_USER: ~/.local/share/keyman InstallArea.IA_SHARED: /usr/local/share/keyman InstallArea.IA_OS: /usr/share/keyman Returns: list: Installed kmp dict: Keyboard id (str): Keyboard ID name (str): Keyboard name kmpname (str): Keyboard name in local version (str): Keyboard version kmpversion (str): path (str): base path where keyboard is installed description (str): Keyboard description """ check_paths = [] if area == InstallArea.IA_USER: check_paths = [ user_keyman_dir() ] elif area == InstallArea.IA_SHARED: check_paths = [ "/usr/local/share/keyman" ] elif area == InstallArea.IA_OS: check_paths = [ "/usr/share/keyman" ] return get_installed_kmp_paths(check_paths) def get_installed_kmp_paths(check_paths): """ Get list of installed kmp. Args: check_paths (list): list of paths to check Returns: list: Installed kmp dict: Keyboard packageID (str): kmp ID name (str): Keyboard name kmpname (str): Keyboard name in local version (str): Keyboard version kmpversion (str): path (str): base path of area where kmp is installed description (str): Keyboard description """ installed_keyboards = {} for keymanpath in check_paths: if os.path.isdir(keymanpath): for o in os.listdir(keymanpath): if os.path.isdir(os.path.join(keymanpath,o)) and o != "icons": name = md_name = version = md_version = keyboardID = description = kbdata = None info, system, options, keyboards, files = parsemetadata(os.path.join(keymanpath, o, "kmp.json")) if not info: info, system, options, keyboards, files = parseinfdata(os.path.join(keymanpath, o, "kmp.inf")) has_kbjson = False kbjson = os.path.join(keymanpath, o, o + ".json") if os.path.isfile(kbjson): with open(kbjson, "r") as read_file: kbdata = json.load(read_file) if kbdata: if 'description' in kbdata: description = kbdata['description'] version = kbdata['version'] name = kbdata['name'] has_kbjson = True if info: md_version = info['version']['description'] md_name = info['name']['description'] if keyboards: keyboardID = keyboards[0]['id'] else: keyboardID = o if not name: version = md_version name = md_name installed_keyboards[o] = { "packageID" : o, "keyboardID" : keyboardID, "name" : name, "kmpname" : md_name, "version" : version, "kmpversion" : md_version, "areapath" : keymanpath, "description" : description, "has_kbjson" : has_kbjson} return installed_keyboards def get_kmp_version(packageID): """ Get version of the kmp for a package ID. This return the highest version if installed in more than one area Args: packageID (dict): kmp ID Returns: str: kmp version if kmp ID is installed None: if not found """ version = None user_kmp = get_installed_kmp(InstallArea.IA_USER) shared_kmp = get_installed_kmp(InstallArea.IA_SHARED) os_kmp = get_installed_kmp(InstallArea.IA_OS) if packageID in os_kmp: version = os_kmp[keyboardid]['version'] if packageID in shared_kmp: shared_version = shared_kmp[packageID]['version'] if version: if version < shared_version: version = shared_version else: version = shared_version if packageID in user_kmp: user_version = user_kmp[packageID]['version'] if version: if version < user_version: version = user_version else: version = user_version return version def get_kmp_version_user(packageID): """ Get version of the kmp for a kmp ID. This only checks the user area. Args: packageID (dict): kmp ID Returns: str: kmp version if kmp ID is installed None: if not found """ user_kmp = get_installed_kmp_user() if packageID in user_kmp: return user_kmp[packageID]['version'] else: return None keyman-config-11.0.103/keyman_config/uninstall_kmp.py000077500000000000000000000074331341361553100225430ustar00rootroot00000000000000#!/usr/bin/python3 import logging import subprocess import sys import os.path from shutil import rmtree from keyman_config.get_kmp import user_keyboard_dir, user_keyman_font_dir from keyman_config.kmpmetadata import get_metadata from keyman_config.ibus_util import uninstall_from_ibus, get_ibus_bus, restart_ibus def uninstall_kmp_shared(packageID): """ Uninstall a kmp from /usr/local/share/keyman Args: packageID (str): Keyboard package ID """ kbdir = os.path.join('/usr/local/share/keyman', packageID) if not os.path.isdir(kbdir): logging.error("Keyboard directory for %s does not exist. Aborting", packageID) exit(3) kbdocdir = os.path.join('/usr/local/share/doc/keyman', packageID) kbfontdir = os.path.join('/usr/local/share/fonts/keyman', packageID) logging.info("Uninstalling shared keyboard: %s", packageID) if not os.access(kbdir, os.X_OK | os.W_OK): # Check for write access of keyman dir logging.error("You do not have permissions to uninstall the keyboard files. You need to run this with `sudo`") exit(3) if os.path.isdir(kbdocdir): if not os.access(kbdocdir, os.X_OK | os.W_OK): # Check for write access of keyman doc dir logging.error("You do not have permissions to uninstall the documentation. You need to run this with `sudo`") exit(3) rmtree(kbdocdir) logging.info("Removed documentation directory: %s", kbdocdir) else: logging.info("No documentation directory") if os.path.isdir(kbfontdir): if not os.access(kbfontdir, os.X_OK | os.W_OK): # Check for write access of keyman fonts logging.error("You do not have permissions to uninstall the font files. You need to run this with `sudo`") exit(3) rmtree(kbfontdir) logging.info("Removed font directory: %s", kbfontdir) else: logging.info("No font directory") # need to uninstall from ibus for all lang and all kmx in kmp info, system, options, keyboards, files = get_metadata(kbdir) if keyboards: uninstall_keyboards_from_ibus(keyboards, kbdir) else: logging.warning("could not uninstall keyboards from IBus") rmtree(kbdir) logging.info("Removed keyman directory: %s", kbdir) logging.info("Finished uninstalling shared keyboard: %s", packageID) def uninstall_keyboards_from_ibus(keyboards, packageDir): bus = get_ibus_bus() if bus: # install all kmx for first lang not just packageID for kb in keyboards: kmx_file = os.path.join(packageDir, kb['id'] + ".kmx") if "languages" in kb: logging.debug(kb["languages"][0]) keyboard_id = "%s:%s" % (kb["languages"][0]['id'], kmx_file) else: keyboard_id = kmx_file uninstall_from_ibus(bus, keyboard_id) restart_ibus(bus) else: logging.warning("could not uninstall keyboards from IBus") def uninstall_kmp_user(packageID): """ Uninstall a kmp from ~/.local/share/keyman Args: packageID (str): Keyboard package ID """ kbdir=user_keyboard_dir(packageID) if not os.path.isdir(kbdir): logging.error("Keyboard directory for %s does not exist. Aborting", packageID) exit(3) logging.info("Uninstalling local keyboard: %s", packageID) info, system, options, keyboards, files = get_metadata(kbdir) if keyboards: uninstall_keyboards_from_ibus(keyboards, kbdir) else: logging.warning("could not uninstall keyboards from IBus") rmtree(kbdir) logging.info("Removed user keyman directory: %s", kbdir) fontdir=os.path.join(user_keyman_font_dir(), packageID) if os.path.isdir(fontdir): rmtree(fontdir) logging.info("Removed user keyman font directory: %s", fontdir) logging.info("Finished uninstalling local keyboard: %s", packageID) def uninstall_kmp(packageID, sharedarea=False): """ Uninstall a kmp Args: packageID (str): Keyboard package ID sharedarea (str): whether to uninstall from shared /usr/local or ~/.local """ if sharedarea: uninstall_kmp_shared(packageID) else: uninstall_kmp_user(packageID) keyman-config-11.0.103/keyman_config/version.py000066400000000000000000000004051341361553100213350ustar00rootroot00000000000000#!/usr/bin/python3 # Store the version here so: # 1) we don't load dependencies by storing it in __init__.py # 2) we can import it in setup.py for the same reason # 3) we can import it into your module module __version__ = "11.0.103" __majorversion__ = "11.0" keyman-config-11.0.103/keyman_config/view_installed.py000077500000000000000000000322011341361553100226630ustar00rootroot00000000000000#!/usr/bin/python3 import logging import os.path import pathlib import gi gi.require_version('Gtk', '3.0') gi.require_version('Gdk', '3.0') from gi.repository import Gtk, Gdk, GdkPixbuf, GObject from keyman_config.list_installed_kmp import get_installed_kmp, InstallArea from keyman_config.welcome import WelcomeView from keyman_config.keyboard_details import KeyboardDetailsView from keyman_config.downloadkeyboard import DownloadKmpWindow from keyman_config.install_window import InstallKmpWindow, find_keyman_image from keyman_config.uninstall_kmp import uninstall_kmp from keyman_config.accelerators import bind_accelerator, init_accel from keyman_config.get_kmp import user_keyboard_dir, user_keyman_dir class ViewInstalledWindowBase(Gtk.Window): def __init__(self): self.accelerators = None Gtk.Window.__init__(self, title="Keyman Configuration") init_accel(self) def refresh_installed_kmp(self): pass def on_close_clicked(self, button): logging.debug("Close application clicked") Gtk.main_quit() def on_refresh_clicked(self, button): logging.debug("Refresh application clicked") self.refresh_installed_kmp() def on_download_clicked(self, button): logging.debug("Download clicked") w = DownloadKmpWindow(self) w.resize(800, 450) w.show_all() def on_installfile_clicked(self, button): logging.debug("Install from file clicked") dlg = Gtk.FileChooserDialog("Choose a kmp file..", self, Gtk.FileChooserAction.OPEN, (Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL, Gtk.STOCK_OPEN, Gtk.ResponseType.OK)) dlg.resize(640, 480) filter_text = Gtk.FileFilter() filter_text.set_name("KMP files") filter_text.add_pattern("*.kmp") dlg.add_filter(filter_text) response = dlg.run() if response == Gtk.ResponseType.OK: kmpfile = dlg.get_filename() w = InstallKmpWindow(kmpfile, viewkmp=self) w.resize(800, 450) if w.checkcontinue: w.show_all() else: w.destroy() dlg.destroy() class ViewInstalledWindow(ViewInstalledWindowBase): def __init__(self): ViewInstalledWindowBase.__init__(self) # window is split left/right hbox # right is ButtonBox # possibly 2 ButtonBox in a vbox # top one with _Remove, _About, ?_Welcome? or ?Read_Me? # bottom one with _Download, _Install, Re_fresh, _Close # left is GtkTreeView - does it need to be inside anything else apart from the hbox? # with liststore which defines columns # GdkPixbuf icon # gchararray name # gchararray version # gchararray packageID (hidden) # enum? area (user, shared, system) (icon or hidden?) # gchararray welcomefile (hidden) (or just use area and packageID?) # changing selected item in treeview changes what buttons are activated # on selected_item_changed signal set the data that the buttons will use in their callbacks # see https://developer.gnome.org/gtk3/stable/TreeWidget.html#TreeWidget hbox = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL) s = Gtk.ScrolledWindow() hbox.pack_start(s, True, True, 0) self.store = Gtk.ListStore(GdkPixbuf.Pixbuf, #icon str, # name str, # version str, # packageID int, # enum InstallArea (KmpArea is GObject version) str) # path to welcome file if it exists or None # add installed keyboards to the the store e.g. # treeiter = store.append([GdkPixbuf.Pixbuf.new_from_file_at_size("/usr/local/share/keyman/libtralo/libtralo.ico.png", 16, 16), \ # "LIBTRALO", "1.6.1", \ # "libtralo", KmpArea.SHARED, True]) self.refresh_installed_kmp() self.tree = Gtk.TreeView(self.store) renderer = Gtk.CellRendererPixbuf() column = Gtk.TreeViewColumn("Icon", renderer, pixbuf=0) self.tree.append_column(column) renderer = Gtk.CellRendererText() column = Gtk.TreeViewColumn("Name", renderer, text=1) self.tree.append_column(column) column = Gtk.TreeViewColumn("Version", renderer, text=2) self.tree.append_column(column) select = self.tree.get_selection() select.connect("changed", self.on_tree_selection_changed) s.add(self.tree) vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=12) bbox_top = Gtk.ButtonBox(spacing=12, orientation=Gtk.Orientation.VERTICAL) bbox_top.set_layout(Gtk.ButtonBoxStyle.START) self.uninstall_button = Gtk.Button.new_with_mnemonic("_Uninstall") self.uninstall_button.set_tooltip_text("Uninstall keyboard package") self.uninstall_button.connect("clicked", self.on_uninstall_clicked) bbox_top.add(self.uninstall_button) self.about_button = Gtk.Button.new_with_mnemonic("_About") self.about_button.set_tooltip_text("About keyboard package") self.about_button.connect("clicked", self.on_about_clicked) bbox_top.add(self.about_button) self.help_button = Gtk.Button.new_with_mnemonic("_Help") self.help_button.set_tooltip_text("Help for keyboard package") self.help_button.connect("clicked", self.on_help_clicked) bbox_top.add(self.help_button) vbox.pack_start(bbox_top, False, False, 12) bbox_bottom = Gtk.ButtonBox(spacing=12, orientation=Gtk.Orientation.VERTICAL) bbox_bottom.set_layout(Gtk.ButtonBoxStyle.END) button = Gtk.Button.new_with_mnemonic("_Refresh") button.set_tooltip_text("Refresh keyboard package list") button.connect("clicked", self.on_refresh_clicked) bbox_bottom.add(button) button = Gtk.Button.new_with_mnemonic("_Download") button.set_tooltip_text("Download and install a keyboard package from the Keyman website") button.connect("clicked", self.on_download_clicked) bbox_bottom.add(button) button = Gtk.Button.new_with_mnemonic("_Install") button.set_tooltip_text("Install a keyboard package from a file") button.connect("clicked", self.on_installfile_clicked) bbox_bottom.add(button) button = Gtk.Button.new_with_mnemonic("_Close") button.set_tooltip_text("Close window") button.connect("clicked", self.on_close_clicked) bind_accelerator(self.accelerators, button, 'q') bind_accelerator(self.accelerators, button, 'w') bbox_bottom.add(button) vbox.pack_end(bbox_bottom, False, False, 12) hbox.pack_start(vbox, False, False, 12) self.add(hbox) def addlistitems(self, installed_kmp, store, install_area): for kmp in sorted(installed_kmp): kmpdata = installed_kmp[kmp] if install_area == InstallArea.IA_USER: welcome_file = os.path.join(user_keyboard_dir(kmpdata['packageID']), "welcome.htm") icofile = os.path.join(user_keyboard_dir(kmpdata['packageID']), kmpdata['packageID'] + ".ico.png") if not os.path.isfile(icofile): icofile = os.path.join(user_keyboard_dir(kmpdata['packageID']), kmpdata['keyboardID'] + ".ico.png") elif install_area == InstallArea.IA_SHARED: welcome_file = os.path.join("/usr/local/share/keyman", kmpdata['packageID'], "welcome.htm") icofile = os.path.join("/usr/local/share/keyman", kmpdata['packageID'], kmpdata['packageID'] + ".ico.png") if not os.path.isfile(icofile): icofile = os.path.join("/usr/local/share/keyman", kmpdata['packageID'], kmpdata['keyboardID'] + ".ico.png") else: welcome_file = os.path.join("/usr/share/keyman", kmpdata['packageID'], "welcome.htm") icofile = os.path.join("/usr/share/keyman", kmpdata['packageID'], kmpdata['packageID'] + ".ico.png") if not os.path.isfile(icofile): icofile = os.path.join("/usr/share/keyman", kmpdata['packageID'], kmpdata['keyboardID'] + ".ico.png") if not os.path.isfile(icofile): icofile = find_keyman_image("icon_kmp.png") if not os.path.isfile(welcome_file): welcome_file = None treeiter = store.append([GdkPixbuf.Pixbuf.new_from_file_at_size(icofile, 16, 16), \ kmpdata['name'], \ kmpdata['version'], \ kmpdata['packageID'], \ install_area, \ welcome_file]) def refresh_installed_kmp(self): logging.debug("Refreshing listview") self.store.clear() self.incomplete_kmp = [] user_kmp = get_installed_kmp(InstallArea.IA_USER) for kmp in sorted(user_kmp): kmpdata = user_kmp[kmp] if kmpdata["has_kbjson"] == False: self.incomplete_kmp.append(kmpdata) self.addlistitems(user_kmp, self.store, InstallArea.IA_USER) shared_kmp = get_installed_kmp(InstallArea.IA_SHARED) for kmp in sorted(shared_kmp): kmpdata = shared_kmp[kmp] if kmpdata["has_kbjson"] == False: self.incomplete_kmp.append(kmpdata) self.addlistitems(shared_kmp, self.store, InstallArea.IA_SHARED) os_kmp = get_installed_kmp(InstallArea.IA_OS) for kmp in sorted(os_kmp): kmpdata = os_kmp[kmp] if kmpdata["has_kbjson"] == False: self.incomplete_kmp.append(kmpdata) self.addlistitems(os_kmp, self.store, InstallArea.IA_OS) def on_tree_selection_changed(self, selection): model, treeiter = selection.get_selected() if treeiter is not None: self.uninstall_button.set_tooltip_text("Uninstall keyboard package " + model[treeiter][1]) self.help_button.set_tooltip_text("Help for keyboard package " + model[treeiter][1]) self.about_button.set_tooltip_text("About keyboard package " + model[treeiter][1]) logging.debug("You selected %s version %s", model[treeiter][1], model[treeiter][2]) if model[treeiter][4] == InstallArea.IA_USER: logging.debug("Enabling uninstall button for %s in %s", model[treeiter][3], model[treeiter][4]) self.uninstall_button.set_sensitive(True) else: self.uninstall_button.set_sensitive(False) logging.debug("Disabling uninstall button for %s in %s ", model[treeiter][3], model[treeiter][4]) if model[treeiter][5]: self.help_button.set_sensitive(True) else: self.help_button.set_sensitive(False) def on_help_clicked(self, button): model, treeiter = self.tree.get_selection().get_selected() if treeiter is not None: logging.info("Open welcome.htm for %s if available", model[treeiter][1]) welcome_file = model[treeiter][5] if welcome_file and os.path.isfile(welcome_file): uri_path = pathlib.Path(welcome_file).as_uri() logging.info("opening" + uri_path) w = WelcomeView(uri_path, model[treeiter][3]) w.resize(800, 600) w.show_all() else: logging.info("welcome.htm not available") def on_uninstall_clicked(self, button): model, treeiter = self.tree.get_selection().get_selected() if treeiter is not None: logging.info("Uninstall keyboard " + model[treeiter][3] + "?") dialog = Gtk.MessageDialog(self, 0, Gtk.MessageType.QUESTION, Gtk.ButtonsType.YES_NO, "Uninstall keyboard?") dialog.format_secondary_text( "Are you sure that you want to uninstall the " + model[treeiter][1] + " keyboard") response = dialog.run() dialog.destroy() if response == Gtk.ResponseType.YES: logging.info("Uninstalling keyboard" + model[treeiter][1]) # can only uninstall with the gui from user area uninstall_kmp(model[treeiter][3]) logging.info("need to refresh window after uninstalling a keyboard") self.refresh_installed_kmp() elif response == Gtk.ResponseType.NO: logging.info("Not uninstalling keyboard " + model[treeiter][1]) def on_about_clicked(self, button): model, treeiter = self.tree.get_selection().get_selected() if treeiter is not None: logging.info("Show keyboard details of " + model[treeiter][1]) if model[treeiter][4] == InstallArea.IA_USER: areapath = user_keyman_dir() elif model[treeiter][4] == InstallArea.IA_SHARED: areapath = "/usr/local/share/keyman" else: areapath = "/usr/share/keyman" kmp = { "name" : model[treeiter][1], "version" : model[treeiter][2], "packageID" : model[treeiter][3], "areapath" : areapath} w = KeyboardDetailsView(kmp) w.resize(800, 450) w.show_all() if __name__ == '__main__': w = ViewInstalledWindow() w.connect("destroy", Gtk.main_quit) w.resize(576, 324) w.show_all() Gtk.main() keyman-config-11.0.103/keyman_config/welcome.py000066400000000000000000000050241341361553100213050ustar00rootroot00000000000000#!/usr/bin/python3 import gi import logging import subprocess import webbrowser import urllib.parse import webbrowser gi.require_version('Gtk', '3.0') gi.require_version('WebKit2', '4.0') from gi.repository import Gtk, WebKit2 from keyman_config.check_mime_type import check_mime_type from keyman_config.accelerators import bind_accelerator, init_accel class WelcomeView(Gtk.Window): def __init__(self, welcomeurl, keyboardname): self.accelerators = None kbtitle = keyboardname + " installed" self.welcomeurl = welcomeurl Gtk.Window.__init__(self, title=kbtitle) init_accel(self) vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=6) s = Gtk.ScrolledWindow() self.webview = WebKit2.WebView() self.webview.connect("decide-policy", self.doc_policy) self.webview.load_uri(welcomeurl) s.add(self.webview) vbox.pack_start(s, True, True, 0) hbox = Gtk.Box(spacing=12) vbox.pack_start(hbox, False, False, 6) button = Gtk.Button.new_with_mnemonic("Open in _Web browser") button.connect("clicked", self.on_openweb_clicked) button.set_tooltip_text("Open in the default web browser to do things like printing") hbox.pack_start(button, False, False, 12) button = Gtk.Button.new_with_mnemonic("_OK") button.connect("clicked", self.on_ok_clicked) hbox.pack_end(button, False, False, 12) bind_accelerator(self.accelerators, button, 'w') self.add(vbox) def doc_policy(self, web_view, decision, decision_type): logging.info("Checking policy") logging.debug("received policy decision request of type: {0}".format(decision_type.value_name)) if decision_type == WebKit2.PolicyDecisionType.NAVIGATION_ACTION or decision_type == WebKit2.PolicyDecisionType.NEW_WINDOW_ACTION: nav_action = decision.get_navigation_action() request = nav_action.get_request() uri = request.get_uri() logging.debug("nav request is for uri %s", uri) if not "welcome.htm" in uri: logging.debug("opening uri %s in webbrowser") webbrowser.open(uri) decision.ignore() return True return False def on_openweb_clicked(self, button): logging.info("\"Open in Web browser\" button was clicked") webbrowser.open(self.welcomeurl) def on_ok_clicked(self, button): logging.info("Closing welcome window") self.close() keyman-config-11.0.103/km-config000077500000000000000000000020751341361553100162720ustar00rootroot00000000000000#!/usr/bin/python3 import argparse import logging import gi gi.require_version('Gtk', '3.0') from gi.repository import Gtk from keyman_config import __version__ if __name__ == '__main__': parser = argparse.ArgumentParser(description='Keyman keyboards installation and information') parser.add_argument('--version', action='version', version='%(prog)s version '+__version__) parser.add_argument('-v', '--verbose', action='store_true', help='verbose logging') parser.add_argument('-vv', '--veryverbose', action='store_true', help='very verbose logging') args = parser.parse_args() if args.verbose: logging.basicConfig(level=logging.INFO, format='%(levelname)s:%(message)s') elif args.veryverbose: logging.basicConfig(level=logging.DEBUG, format='%(levelname)s:%(message)s') else: logging.basicConfig(format='%(levelname)s:%(message)s') from keyman_config.view_installed import ViewInstalledWindow w = ViewInstalledWindow() w.resize(576, 324) w.connect("destroy", Gtk.main_quit) w.show_all() Gtk.main() keyman-config-11.0.103/km-kvk2ldml000077500000000000000000000043211341361553100165470ustar00rootroot00000000000000#!/usr/bin/python3 import argparse import logging import os.path import sys from keyman_config import __version__ def main(): parser = argparse.ArgumentParser(description='Convert a Keyman kvk on-screen keyboard file to an LDML file. Optionally print the details of the kvk file.') parser.add_argument('-p', "--print", help='print kvk details', action="store_true") parser.add_argument('-k', "--keys", help='if printing also print all keys', action="store_true") parser.add_argument('kvkfile', help='kvk file') parser.add_argument('-o', '--output', metavar='LDMLFILE', help='output LDML file location') parser.add_argument('--version', action='version', version='%(prog)s version '+__version__) parser.add_argument('-v', '--verbose', action='store_true', help='verbose logging') parser.add_argument('-vv', '--veryverbose', action='store_true', help='very verbose logging') args = parser.parse_args() if args.verbose: logging.basicConfig(level=logging.INFO, format='%(levelname)s:%(message)s') elif args.veryverbose: logging.basicConfig(level=logging.DEBUG, format='%(levelname)s:%(message)s') else: logging.basicConfig(format='%(levelname)s:%(message)s') from keyman_config.kvk2ldml import parse_kvk_file, print_kvk, convert_ldml, output_ldml name, ext = os.path.splitext(args.kvkfile) # Check if input file extension is kvk if ext != ".kvk": logging.error("km-kvk2ldml: error, input file %s is not a kvk file.", args.kvkfile) logging.error("km-kvk2ldml [-h] [-k] [-p] [-o ] ") sys.exit(2) # Check if input kvk file exists if not os.path.isfile(args.kvkfile): logging.error("km-kvk2ldml: error, input file %s does not exist.", args.kvkfile) logging.error("km-kvk2ldml [-h] [-k] [-p] [-o ] ") sys.exit(2) kvkData = parse_kvk_file(args.kvkfile) if args.print: print_kvk(kvkData, args.keys) if args.output: outputfile = args.output else: outputfile = name + ".ldml" with open(outputfile, 'wb') as ldmlfile: ldml = convert_ldml(kvkData) output_ldml(ldmlfile, ldml) if __name__ == "__main__": main() keyman-config-11.0.103/km-kvk2ldml.bash-completion000066400000000000000000000006521341361553100216320ustar00rootroot00000000000000#/usr/bin/env bash _km-kvk2ldml_completions() { local cur prev opts COMPREPLY=() cur="${COMP_WORDS[COMP_CWORD]}" prev="${COMP_WORDS[COMP_CWORD-1]}" opts="-h --help -p --print -k --keys -o --output -v --verbose -vv --veryverbose --version" if [[ ${cur} == -* ]] ; then COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) ) return 0 fi } complete -F _km-kvk2ldml_completions km-kvk2ldml keyman-config-11.0.103/km-package-get000077500000000000000000000021011341361553100171630ustar00rootroot00000000000000#!/usr/bin/python3 import argparse import logging import os import sys from keyman_config import __version__ def main(): parser = argparse.ArgumentParser(description='Download Keyman keyboard package to ~/.cache/keyman') parser.add_argument('id', help='Keyman keyboard id') parser.add_argument('--version', action='version', version='%(prog)s version '+__version__) parser.add_argument('-v', '--verbose', action='store_true', help='verbose logging') parser.add_argument('-vv', '--veryverbose', action='store_true', help='very verbose logging') args = parser.parse_args() if args.verbose: logging.basicConfig(level=logging.INFO, format='%(levelname)s:%(message)s') elif args.veryverbose: logging.basicConfig(level=logging.DEBUG, format='%(levelname)s:%(message)s') else: logging.basicConfig(format='%(levelname)s:%(message)s') from keyman_config.get_kmp import get_kmp, keyman_cache_dir get_kmp(args.id) if os.path.exists(os.path.join(keyman_cache_dir(), 'kmpdirlist')): os.remove(os.path.join(keyman_cache_dir(), 'kmpdirlist')) if __name__ == "__main__": main() keyman-config-11.0.103/km-package-get.bash-completion000066400000000000000000000023071341361553100222530ustar00rootroot00000000000000#/usr/bin/env bash _km-package-get_completions() { local cur prev opts COMPREPLY=() cur="${COMP_WORDS[COMP_CWORD]}" prev="${COMP_WORDS[COMP_CWORD-1]}" opts="-h --help -v --verbose -vv --veryverbose --version" if [[ ${cur} == -* ]] ; then COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) ) return 0 fi words="" if [[ ! -e ~/.cache/keyman/kmpdirlist ]] ; then if [[ -e ./km-package-install ]]; then python3 -c "from imp import load_source;load_source('km_package_install', './km-package-install');from km_package_install import list_keyboards;list_keyboards()" else python3 -c "from imp import load_source;load_source('km_package_install', '/usr/bin/km-package-install');from km_package_install import list_keyboards;list_keyboards()" fi fi if [[ -r ~/.cache/keyman/kmpdirlist ]] ; then for file in `cat ~/.cache/keyman/kmpdirlist`; do words="${words} ${file}"; done COMPREPLY=($(compgen -W "${words}" -- ${cur})) return 0 fi } complete -F _km-package-get_completions km-package-get keyman-config-11.0.103/km-package-install000077500000000000000000000127161341361553100200670ustar00rootroot00000000000000#!/usr/bin/python3 import argparse import logging import sys import os from keyman_config import __version__ import datetime import time def get_languages(): import json import requests import requests_cache from keyman_config.get_kmp import keyman_cache_dir logging.info("Getting language list from search") api_url = "https://api.keyman.com/search?q=l:*&platform=linux" logging.debug("At URL %s", api_url) cache_dir = keyman_cache_dir() current_dir = os.getcwd() expire_after = datetime.timedelta(days=7) if not os.path.isdir(cache_dir): os.makedirs(cache_dir) os.chdir(cache_dir) requests_cache.install_cache(cache_name='keyman_cache', backend='sqlite', expire_after=expire_after) now = time.ctime(int(time.time())) try: response = requests.get(api_url) logging.debug("Time: {0} / Used Cache: {1}".format(now, response.from_cache)) os.chdir(current_dir) requests_cache.core.uninstall_cache() if response.status_code == 200: return response.json() else: return None except: return None def get_keyboard_list(): languages = get_languages() keyboards = [] if "languages" in languages: for lang in languages["languages"]: if "keyboards" in lang: for kb in lang["keyboards"]: kbnospace = kb.replace(" ", "%20") if kbnospace not in keyboards: keyboards.append(kbnospace) if keyboards: keyboards.sort() return keyboards def write_kmpdirlist(kmpdirfile): with open(kmpdirfile, 'wt') as kmpdirlist: keyboards = get_keyboard_list() if keyboards: for kb in get_keyboard_list(): print(kb, file=kmpdirlist) def list_keyboards(): from keyman_config.get_kmp import keyman_cache_dir kmpdirfile = os.path.join(keyman_cache_dir(), 'kmpdirlist') if not os.path.exists(kmpdirfile): write_kmpdirlist(kmpdirfile) else: logging.debug("kmpdirlist already exists") # file empty if os.path.getsize(kmpdirfile) == 0: write_kmpdirlist(kmpdirfile) def main(): parser = argparse.ArgumentParser(description='Install a Keyman keyboard package, either a local .kmp file or specify a keyboard id to download and install') parser.add_argument('-s', '--shared', action='store_true', help='Install to shared area /usr/local') parser.add_argument('-f', '--file', metavar='', help='Keyman kmp file') parser.add_argument('-p', '--package', metavar='', help='Keyman package id') parser.add_argument('--version', action='version', version='%(prog)s version '+__version__) parser.add_argument('-v', '--verbose', action='store_true', help='verbose logging') parser.add_argument('-vv', '--veryverbose', action='store_true', help='very verbose logging') args = parser.parse_args() if args.verbose: logging.basicConfig(level=logging.INFO, format='%(levelname)s:%(message)s') elif args.veryverbose: logging.basicConfig(level=logging.DEBUG, format='%(levelname)s:%(message)s') else: logging.basicConfig(format='%(levelname)s:%(message)s') if args.package and args.file: logging.error("km-package-install: error: too many arguments: either install a local kmp file or specify a keyboard id to download and install.") sys.exit(2) from keyman_config.install_kmp import install_kmp, InstallError, InstallStatus from keyman_config.list_installed_kmp import get_kmp_version from keyman_config.get_kmp import get_keyboard_data, get_kmp, keyman_cache_dir if os.path.exists(os.path.join(keyman_cache_dir(), 'kmpdirlist')): os.remove(os.path.join(keyman_cache_dir(), 'kmpdirlist')) def try_install_kmp(inputfile, arg, online=False, sharedarea=False): try: install_kmp(inputfile, online, sharedarea) except InstallError as e: if e.status == InstallStatus.Abort: logging.error("km-package-install: error: Failed to install %s", arg) logging.error(e.message) sys.exit(3) else: logging.warning(e.message) if args.file: name, ext = os.path.splitext(args.file) if ext != ".kmp": logging.error("km-package-install: Input file %s is not a kmp file.", args.file) logging.error("km-package-install -f ") sys.exit(2) if not os.path.isfile(args.file): logging.error("km-package-install: Keyman kmp file %s not found.", args.file) logging.error("km-package-install -f ") sys.exit(2) try_install_kmp(args.file, "file " + args.file, False, args.shared) elif args.package: installed_kmp_ver = get_kmp_version(args.package) kbdata = get_keyboard_data(args.package) if not kbdata: logging.error("km-package-install: error: Could not download keyboard data for %s", args.package) sys.exit(3) if installed_kmp_ver: if kbdata['version'] == installed_kmp_ver: logging.error("km-package-install: The %s version of the %s keyboard package is already installed.", installed_kmp_ver, args.package) sys.exit(1) elif float(kbdata['version']) > float(installed_kmp_ver): logging.error("km-package-install: A newer version of %s keyboard package is available. Uninstalling old version %s then downloading and installing new version %s.", args.package, installed_kmp_ver, kbdata['version']) uninstall_kmp(args.package, args.shared) kmpfile = get_kmp(args.package) if kmpfile: try_install_kmp(kmpfile, "keyboard package " + args.package, True, args.shared) else: logging.error("km-package-install: error: Could not download keyboard package %s", args.package) sys.exit(2) else: logging.error("km-package-install: error: no arguments: either install a local kmp file or specify a keyboard package id to download and install.") sys.exit(2) if __name__ == "__main__": main() keyman-config-11.0.103/km-package-install.bash-completion000066400000000000000000000025271341361553100231460ustar00rootroot00000000000000#/usr/bin/env bash _km-package-install_completions() { local cur prev opts COMPREPLY=() cur="${COMP_WORDS[COMP_CWORD]}" prev="${COMP_WORDS[COMP_CWORD-1]}" opts="-h --help -v --verbose -vv --veryverbose --version -p --package -f --file -s --shared" if [[ ${cur} == -* ]] ; then COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) ) return 0 fi case "${prev}" in "-k"|"--keyboardid") words="" if [[ ! -s ~/.cache/keyman/kmpdirlist ]] ; then if [[ -e ./km-package-install ]]; then python3 -c "from imp import load_source;load_source('km_package_install', './km-package-install');from km_package_install import list_keyboards;list_keyboards()" else python3 -c "from imp import load_source;load_source('km_package_install', '/usr/bin/km-package-install');from km_package_install import list_keyboards;list_keyboards()" fi fi if [[ -r ~/.cache/keyman/kmpdirlist ]] ; then for file in `cat ~/.cache/keyman/kmpdirlist`; do words="${words} ${file}"; done COMPREPLY=($(compgen -W "${words}" -- ${cur})) return 0 fi ;; *) ;; esac } complete -F _km-package-install_completions km-package-install keyman-config-11.0.103/km-package-list-installed000077500000000000000000000055751341361553100213560ustar00rootroot00000000000000#!/usr/bin/python3 import argparse import logging import os from keyman_config import __version__ def main(): parser = argparse.ArgumentParser(description='Show installed Keyman keyboards with name, version, id.') parser.add_argument('-l', "--long", help='long format also shows description', action="store_true") parser.add_argument('-s', "--shared", help='show those installed in shared areas', action="store_true") parser.add_argument('-o', "--os", help='show those installed by the OS', action="store_true") parser.add_argument('-u', "--user", help='show those installed in user areas', action="store_true") parser.add_argument('--version', action='version', version='%(prog)s version '+__version__) parser.add_argument('-v', '--verbose', action='store_true', help='verbose logging') parser.add_argument('-vv', '--veryverbose', action='store_true', help='very verbose logging') args = parser.parse_args() if args.verbose: logging.basicConfig(level=logging.INFO, format='%(levelname)s:%(message)s') elif args.veryverbose: logging.basicConfig(level=logging.DEBUG, format='%(levelname)s:%(message)s') else: logging.basicConfig(format='%(levelname)s:%(message)s') all = not args.user and not args.shared and not args.os from keyman_config.list_installed_kmp import get_installed_kmp, InstallArea if args.user or all: installed_kmp = get_installed_kmp(InstallArea.IA_USER) print("--- Installed user keyboards ---") print_keyboards(installed_kmp, args.long) if args.shared or all: installed_kmp = get_installed_kmp(InstallArea.IA_SHARED) print("--- Installed shared keyboards ---") print_keyboards(installed_kmp, args.long) if args.os or all: installed_kmp = get_installed_kmp(InstallArea.IA_OS) print("--- Installed OS keyboards ---") print_keyboards(installed_kmp, args.long) def print_keyboards(installed_kmp, verbose): for packageID in sorted(installed_kmp): print(installed_kmp[packageID]['name'] + ", version:", installed_kmp[packageID]['version'] + ", id:", packageID) if verbose: if installed_kmp[packageID]['version'] != installed_kmp[packageID]['kmpversion']: print("Version mismatch. Installed keyboard is %s. Website says it is %s." % (installed_kmp[packageID]['kmpversion'], installed_kmp[packageID]['version'])) if installed_kmp[packageID]['name'] != installed_kmp[packageID]['kmpname']: print("Name mismatch. Installed keyboard is %s. Website says it is %s." % (installed_kmp[packageID]['name'], installed_kmp[packageID]['kmpname'])) if installed_kmp[packageID]['description']: print(installed_kmp[packageID]['description']) else: print("No description") print(os.linesep) if __name__ == "__main__": main() keyman-config-11.0.103/km-package-list-installed.bash-completion000066400000000000000000000007331341361553100244250ustar00rootroot00000000000000#/usr/bin/env bash _km-package-list-installed_completions() { local cur prev opts COMPREPLY=() cur="${COMP_WORDS[COMP_CWORD]}" prev="${COMP_WORDS[COMP_CWORD-1]}" opts="-h --help -l --long -v --verbose -vv --veryverbose --version -u --user -o --os -s --shared" if [[ ${cur} == -* ]] ; then COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) ) return 0 fi } complete -F _km-package-list-installed_completions km-package-list-installed keyman-config-11.0.103/km-package-uninstall000077500000000000000000000020351341361553100204230ustar00rootroot00000000000000#!/usr/bin/python3 import argparse import logging import sys from keyman_config import __version__ def main(): parser = argparse.ArgumentParser(description='Uninstall Keyman keyboard package.') parser.add_argument('id', help='Keyman keyboard id') parser.add_argument('-s', '--shared', action='store_true', help='Uninstall from shared area /usr/local') parser.add_argument('--version', action='version', version='%(prog)s version '+__version__) parser.add_argument('-v', '--verbose', action='store_true', help='verbose logging') parser.add_argument('-vv', '--veryverbose', action='store_true', help='very verbose logging') args = parser.parse_args() if args.verbose: logging.basicConfig(level=logging.INFO, format='%(levelname)s:%(message)s') elif args.veryverbose: logging.basicConfig(level=logging.DEBUG, format='%(levelname)s:%(message)s') else: logging.basicConfig(format='%(levelname)s:%(message)s') from keyman_config.uninstall_kmp import uninstall_kmp uninstall_kmp(args.id, args.shared) if __name__ == "__main__": main() keyman-config-11.0.103/km-package-uninstall.bash-completion000066400000000000000000000020151341361553100235010ustar00rootroot00000000000000#/usr/bin/env bash _km-package-uninstall_completions() { local cur prev opts COMPREPLY=() cur="${COMP_WORDS[COMP_CWORD]}" prev="${COMP_WORDS[COMP_CWORD-1]}" opts="-h --help -s --shared -v --verbose -vv --veryverbose --version" if [[ ${cur} == -* ]] ; then COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) ) return 0 fi if [[ "${#COMP_WORDS[@]}" != "2" ]]; then if [[ ${prev} != -* ]]; then return 0 fi fi words="" shared="" case "${prev}" in "-s"|"--shared") for file in `ls -d /usr/local/share/keyman/*/`; do kbid="`basename ${file}`"; shared="${shared} ${kbid}"; done COMPREPLY=($(compgen -W "${shared}" -- ${cur})) ;; *) for file in `ls -d ~/.local/share/keyman/*/`; do kbid="`basename ${file}`"; words="${words} ${kbid}"; done COMPREPLY=($(compgen -W "${words}" -- ${cur})) ;; esac } complete -F _km-package-uninstall_completions km-package-uninstall keyman-config-11.0.103/setup.cfg000066400000000000000000000000461341361553100163070ustar00rootroot00000000000000[egg_info] tag_build = tag_date = 0 keyman-config-11.0.103/setup.py000066400000000000000000000017431341361553100162050ustar00rootroot00000000000000#!/usr/bin/python3 from setuptools import setup, find_packages exec(open('keyman_config/version.py').read()) setup( name="keyman_config", version=__version__, packages=find_packages(), scripts=['km-config', 'km-package-get', 'km-package-install', 'km-kvk2ldml', 'km-package-uninstall', 'km-package-list-installed', ], install_requires=[ 'lxml', 'numpy', 'Pillow', 'requests', 'requests-cache', 'python-magic', ], # metadata to display on PyPI author="Daniel Glassey", author_email="wdg@debian.org", description="Keyman for Linux configuration", license="MIT", keywords="keyman, keyman-config, keyboard", url="http://www.keyman.com/", # project home page, if any project_urls={ "Bug Tracker": "https://github.com/keymanapp/issues", "Source Code": "https://github.com/keymanapp/keyman/linux/tree/master/linux/keyman-config", }, # include_package_data=True, )