From 143a96463c7b76b8e81b6955f77e29596de1771d Mon Sep 17 00:00:00 2001 From: Rokas Puzonas Date: Sun, 5 Feb 2023 14:39:17 +0200 Subject: [PATCH] add icon to executable --- Cargo.lock | 11 ++++ Cargo.toml | 16 ++++- assets/icon.ase | Bin 0 -> 1430 bytes assets/icon.ico | Bin 4286 -> 51262 bytes assets/icon.png | Bin 1315 -> 0 bytes assets/small-icon.ase | Bin 0 -> 1023 bytes build.rs | 9 +++ src/environment.rs | 15 +++++ src/main.rs | 37 +++--------- src/platforms/mod.rs | 3 + src/platforms/win.rs | 132 ++++++++++++++++++++++++++++++++++++++++++ 11 files changed, 194 insertions(+), 29 deletions(-) create mode 100644 assets/icon.ase delete mode 100644 assets/icon.png create mode 100644 assets/small-icon.ase create mode 100644 build.rs create mode 100644 src/environment.rs create mode 100644 src/platforms/mod.rs create mode 100644 src/platforms/win.rs diff --git a/Cargo.lock b/Cargo.lock index c2646dc..1533a2c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1310,6 +1310,8 @@ dependencies = [ "serde", "toml", "ureq", + "windows 0.43.0", + "winres", ] [[package]] @@ -2928,6 +2930,15 @@ dependencies = [ "x11-dl", ] +[[package]] +name = "winres" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b68db261ef59e9e52806f688020631e987592bd83619edccda9c47d42cde4f6c" +dependencies = [ + "toml", +] + [[package]] name = "wio" version = "0.2.2" diff --git a/Cargo.toml b/Cargo.toml index 7459105..1747cb0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,6 +2,10 @@ name = "ktu-timetable" version = "0.1.0" edition = "2021" +build = "build.rs" + +[build-dependencies] +winres = "0.1" [profile.release] opt-level = 'z' # Optimize for size @@ -22,4 +26,14 @@ lazy-regex = "2.4.1" directories-next = "2.0.0" toml = "0.5.11" serde = { version = "1.0.152", features = ["derive"]} -lazy_static = "1.4.0" \ No newline at end of file +lazy_static = "1.4.0" +winres = "0.1.12" + +[dependencies.windows] +version = "0.43.0" +features = [ + "Win32_UI_WindowsAndMessaging", + "Win32_Foundation", + "Win32_System_LibraryLoader", + "Win32_Graphics_Gdi" +] \ No newline at end of file diff --git a/assets/icon.ase b/assets/icon.ase new file mode 100644 index 0000000000000000000000000000000000000000..49b04fd343713abf012a66093162ea97488a71f9 GIT binary patch literal 1430 zcmd^-eM}p57{`xEx5Owc{=wW^T3<5F5?v{88-;aK2U9fNaLi%FYH+KRl}1)9v|{H= zGBdU{nlY>$Fo!@kXfSlpO@wo5;*xgOQf*h*$`r7)Fwl9WLb?0-y-l5QfBToufEdg=S^*Z6tpf`S9t0&h z8$g*%2Z~>M8&p*M2$ot2sJu7^I)@iPf}$v^%>lmq<^ixwtp_?f$%sWJ0+EKO)qIlZ4)Cvn0wtjGDqq&@UWMiDHxsEHyQ_5y*a<{8A0mrxUa1?u zM)mP){`->lZ(5poK2F+`H;Z?Dy{M%A(8wo0OxHeNUi_-w8@u#02}@mkP34XbGg>eG z49R5wcZ=S3_Tr5p$bNe$JtW^dQ%TKA%Y=_&oB|Q%i@I&Jmpv6N;-#BB3qT1-$ejcQ&+w8&lnaz66W6~P_`E~hMuI!I3MCJrG_t|@E zlJdryYGT8}!>*I9_1wtE`V?CuLy~gs?_~~}a&7jBu(!`0%wXJJNu6s}HCd$TBHPEZ zn|kDIXfC}@I@D}a^z?K2D*M?EHZW8)JZWMoQuMtt1+6x+=8Zi*GG~bQaWdytGT~yB z`2{Mrxnq2ldtmVdeZ<#mnMq*%o@{gdJgsP{9FY&E)pf?caAh#EL6f%MexW+q73pG? z#i4Cc)h~h69dDRwKG0qx;q`_W`<#2%kEZR4e>laZZ9LE;TxM~uWxI~#@8jq8d&f_X zGo4X3*wB96p=14pEPbXkBWhl$X|DM;BFmS*BejN=S{?VkMWuX}5oTrgZMEAqOjmu? zmBZ6nhMak`CDkF1e^2}FWQ`=)-NtE7hkV_1WagyQYtq`|s?`l#>DrLbzK$)QpdA6$ zsLUFWpYJhNmV>p`(0b?g~=;x2J^@YPm{jHs))6kE}!;~d${G_ z(AKFynAA!_$1*}4Rg8OCW6{rtqD%NDZq@YIz-a%uBN F=pTTinpyw= literal 0 HcmV?d00001 diff --git a/assets/icon.ico b/assets/icon.ico index 5e252b2bfd4617402119fb0ca25798e4938346dc..55935698be7b1d151f22b3bc986dafba252631a0 100644 GIT binary patch literal 51262 zcmeH@+pQcn3`93g1S!E~y1aB{%Mm~jN1{fQv|r&~>;;FMhju=nzuMo=pP%;spU;nf zKcBxppU;o>ZL2@a|Mq{zKezf+1Jyt^Pz_WA)j%~+4O9cwKsCSye%0q`1MFAfr{QJx zOZClrRra<4_N(yQ^m0>u^Ina;ZGinM{5HMZRNuT;V{aQ^zY4!iFE`aU@737b2H3B{ zZ_~?7_04-V_O=1`tMJ?Oa#MZtUX8tNfc+}`Hoe?b-@I32ZyR903cpP+H`O=q)!5qx z*ssEG)5}fu&3iTWwgL95@Z0oqQ+@MZjlFGv{VM!6z1&payjNpy8(_Z*zfCVU)i>|e z*xLr!uflKB%T4vodo}j90rso#+jQ?XP4)!L;N!Z0kr4YGxJcPN?WB0lrfrJm{iAtm z2=bIOE+ZB8J8(45Zgx^UC+jcy-h4feR^X3>akG%%v;#-~>}Ds$bF%(xzIWc4;RXq= zQ@D`ev;(IRspU?J=cLTnWN+JN@Fk(~!GQxH$PIb|uKx7B8KDxsddV0xb(S;`rY5#r zlh;=>c{?Cf!dK}CTG+0cxTYXNp~dw}5TO!2c?MFX=bDe}CLBW{90}jpAXLI9Z$OIl z?EHuZw@o;@K{yh=u|cSWPu_r(druhdz%dl6k?@TTLM43i2Bh43N<&F#Z`k47KNSuL zl@RhXh?FxEuabt6(A;)7KJ>|qPzk>g`rLCg#P1pjZL`Cf3U8JOmGCCZv`Df2&9LxX zoP6RY%}efQgi3gy*c_1ZZO_fPP1=*!nh8QBG~vyFlp8Cr^|m{E&uO!lw;9(jL4->9 zl!jd$)>ziIO*vCTfyB=-YCCA?4U z4hUMBm|Kce3s|2$k?Y5e_oZ2oNryN&K+j_md~LS@NF{ z5Gvu5P?*TjY#XB?5~mQVDd+hTp%OlSfEiAs?V1`!s~tI1JCb`2LM6OUEC=6!EF1){ zt@*wI)GOhAVmbH*WZ@uqZO!)$pk4{@6U)IjAPWb_qdrA{e?sFsoGomf_a4@)#T%IgqbGVG_a-X#}^h}#0i4pn` z(WZN7BRJLGETf=kf0JceZ*I%!;F*1%()tqQ1rsL-*AUuExXv1{mvLS0Z5M`~*5?Je zoxop{?F!WnH0}Mc6X!YiiOpft(9`<7P6vNY?pH{rDI;gcd6P4muQ!{vYnqRUn4Z(; zh-rIBY`+*bC7ZNGP;`Q`gpp5b?^C8iPw4ZMh9y+EPT{^}pE(VRmavsD@=5J|%2enH zeV)>=gbLRw+?VV#r$NyYwh~4@sXbDl#kD~YA_!qb^LsDx`*HTn{U?n+*uYjo$j{)I z;(gXv&%_KNt|LtGQ{*u#+Q3%A$S1X*rgVq#wC@b96sGtovWzwYY$Y_|%@CzDPn=RI zS#64+BJV#Nwi50;FPJjL2n&<(WU4-l1Z-0L6d6Ga2wMpkR{JNJ(h2*9oe5hhPw`V^ z8EpjEN@&8HL8dgqfj?}eLyDgw%V;CORzefr3_hj#cxdy1ziIm*9a8)hSwMn1JaYd{tb!dXhGq6Sc}gpp6} z&l-@0gK(Bos;B|fD`DhQ`?CgQ;UJu)lqzZf^-37|)c&jiSvUx1DW!@UK)n)1KD9q< zKo$77oH$N~xj-P_KlMPwmedkcESAmQt#y0n{sDgZ2C9K-pc<$Ks)1^t8u%YH@Ee~!IKlt` literal 4286 zcmc(iOKVkE6vuaR^L{6d7Q}&$(rE|MnP~`ugCZUF>q#XD(oZ0M1aTS=q|k{JH1Bur zJzc-$`JL|EptrMb*>~@=*INJeUyrkoDP=7FJ$O*~EiYF}xm!y4A!ZzvpJQ_Vwee(o zyP2MxY{pB;wKq1_OpK4`^W4l#u1gCGxz0>YrA+x`uh&dXOr(r6o|(##iM=p4*PVld zKXiY3^r%6`^Xl?)t~{$VKRcUx;LJ`>ryO0TK9?64Z=56J41D%$D=WFehxnMBvo6lh z=N|0y)6<6GdVP7B&!@-7x$-l(zP`2ZUtM))ZtAm>qa%-V1;be3!6fF|rs?-PUH8pk z@C7Gim*?l5K6}Q+*;!8Zo}~?TVUzeR20qCjIWYg&p)Zd*Zbas#&H?9)&fn0!<1F{o zZqhIQ?G@_407pNqYP0|Ov)dc%*~v*hTPN0fud?|kE++UZOG}w|@?f8nKgZF*LDwhu z@7?QkkzK7jH7Z)Ah z39-RJ-}lFNk$4#7-JDrJa2)zo#{F@1aiKVCRPz@fg@e7lu7=>?Qy%QaK_)&}%8;`c z-xX~8miW0>ya(_PmhTyI^JOi-^^7%nL@w!9aO>Q$=S&&Cj}H%7Zt;hQ`9iJ_Y&oy{ z6kYPe&<5V*hZ@6)$=;qI7wE{x_Yr%2qtCO$SgbL!x<>lf4j;8Wd(Xu97B>2K*VnTz zt&#DA;~e&Z|6BK0I6{y9w!XyKjrLwehuqSe_E)FVZqN~fJ$E*D3H@JxH;}=FyfMuS zy=UHt$GlSG@9*5XQA1+noaud4_Z)f2o`YNTomkd0DRaj%9kDl`9JvPv&hV@6fi!FL z17GrH{Kh0S_Vy2ZzPCBTr@&};ZuV;E z)v=`$eyDehyhB5W;XPRQ4*8M4*x4r-)EGR+@VhTBbnD!uZR-zx{3iz<+Cj{``{tf} z)B_&GWX#C$>pSSmx1#2%AM;@^C3edR8}A%e;wK+sqDzne@#nq zU+(Pu8-MGA+#sjFe}De`i#@qS9+Pz~2n!kF-9{OFk{?3i%&>|1hps;4ch-{p62-~RaK_3Ph$efI2^IDZp! zH~wDQ4c{N&gRc336Id9Me8*qK8Yw*Tuj&XPxhUzeIoHC;i(q=n;cB=u?OPBBwqrX4=P{ohums!|0XA Lk2lX_UdQ|gDOt*6 diff --git a/assets/icon.png b/assets/icon.png deleted file mode 100644 index 2256e9dda3c777436c9ed1126a38e742927cbc1e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1315 zcmex=d3)B>(?5gEIp+Cnq}?adHBgAi&MZ&CSCN#JmDLf_%JuJVKH}s#3D5 zvXb&Lnwny&=B8#+T1Ixdng){Aiozg67}+^EfVxDvxkYtkC1rI;2LBH*2y!qeFeorH zDlsq#GBOJ?{y)MX4|F*z(8q931NltMEUawo9GqO-Km}U`7?>EDnVDFaSy@?tUT3TY z$}_MCvI;30I#U-U>$dGXcJ4ZK_{h;?$4{I*b?NeztJkjIxOwa0qsLF4K70P+ z<*SdMK7aZ8?fZ|Pzd-(CWMGDP3+^*Ce+dHp#l*tG%)$=x7b8VP*tI z9m<}It= zcXB?cT)SpVtyw&uta*g{-bpUvN;mI4xMLAqvRupi?ceK(2mk5*XK1^go%kSM88C`^KOYHEJ|dXpRd`okzMkb-OJaPmS6g(6m|aLd!cyehc|NHZsGW{V$vz?EuYf* zewA&%u{-zBikNh1zN$hWzI3mBKPJp$pLOfB`{mRR4=}Kk>>)QOCUcCSP?X!C;&OPy-@SmZ!>p#P)mp`w& ziXZWgs8O4B#7?%&I^|mZhG74$w?d-*Z{}y-TeIzV;UDX=I@^_{Z~g8^{D>@H5q-lo zk>zyO#K=RZE*{vBb@6tqQ%AGBUfqk=ZznN&Km2S}WA}0L@#1fhVzrr*UK_2yIU{p| zpxF!2CDK)YR{6^?Wt)5My&8R3&U)gX+;5#p{|D^WXWq-Zs}!!CYe z^l}TC%X>H<>9fC#lezZAU*KM)Zsm%2oooMG-^@$ha&1HOhR?bIk1Y}pGyi!V#q0eb zbnUd2%N@;5+XlbcpVytX?_T-M6r-b`H%*gJw-KE8a{*8NwpaT&~O_nYc}XTSCBw=>KAeqMCrN>7{poO&wrgZ^Efx@Yh8+wY!y{HzltHvtAiBclKS diff --git a/assets/small-icon.ase b/assets/small-icon.ase new file mode 100644 index 0000000000000000000000000000000000000000..7bb501274d0dd79c64cdac4d0282c77b9965fdc1 GIT binary patch literal 1023 zcmey*%)szqDI-Gz5GpV*GB7Zt05Jjp#Xy2I1NA`re|=q zC@Gi#NmmUUAem-q2qgQ%tbyeHLNy?Hb3q!Ae0O*jko^703`ov=8UQ4-_89<4^C@{i z(mPWhNCtVU07)YoOCV`)Ujifz5}JTyblyWCnLPVHkc>b31W2B~{Rc??2Lc9$*=x50 z$(FiyAeot!1tcTGf`DY1Qx}k2V`Ky*Z-yrW$?Mg-fMox4FCf{KZ}2}0=oc1H2!lKa z^aRicK<5M94s9kE&`zL*K-+*;$%1TB00t}^0G+_V%HUR%n42mCWMWFPFodL5ltcqv$EpC) z^`8ksGk}vu#hka-75N$*1XvFK{=b~x(IDaoQ|#29`%A9AtBuNz%qYM7{f5u~m)2)M z;EnC$+h6i8@Av(0_IJcFwk`jA790rnpVdvC`{%qbV)$mADL)+;nyd=I@C5>lpuObC z)eykreDL1?OQHvR6Mn9nEqQ`7Zq=&S)o)C`UbAn0cjwFbW#(YLWDy_2?UjT1lee$j W{ZG>G@81OTznk}3#ZS(6-w6Q8Xp8~? literal 0 HcmV?d00001 diff --git a/build.rs b/build.rs new file mode 100644 index 0000000..d83fa12 --- /dev/null +++ b/build.rs @@ -0,0 +1,9 @@ +extern crate winres; + +fn main() { + if cfg!(target_os = "windows") { + let mut res = winres::WindowsResource::new(); + res.set_icon_with_id("assets/icon.ico", "window-icon"); + res.compile().unwrap(); + } +} \ No newline at end of file diff --git a/src/environment.rs b/src/environment.rs new file mode 100644 index 0000000..e47263a --- /dev/null +++ b/src/environment.rs @@ -0,0 +1,15 @@ +use crate::{timetable::{TimetableGetter, BlockingTimetableGetter}, config::{ConfigStore, TomlConfigStore}}; + +pub struct Environment { + pub timetable_getter: Box, + pub config_store: Box +} + +impl Default for Environment { + fn default() -> Self { + Self { + config_store: Box::new(TomlConfigStore::default()), + timetable_getter: Box::new(BlockingTimetableGetter::default()) + } + } +} \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index 375131a..6cd4dde 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,13 +5,14 @@ mod app; mod config; mod events_table; mod utils; +mod platforms; +mod environment; #[macro_use] extern crate lazy_static; -use app::MainApp; use config::TomlConfigStore; -use eframe::egui; +use environment::Environment; use timetable::BlockingTimetableGetter; // TODO: show errors when loading config @@ -19,7 +20,7 @@ use timetable::BlockingTimetableGetter; // TODO: use "confy" for config loading? // TODO: Setup pipeline -fn main() -> Result<(), ureq::Error> { +fn main() { let config_store = TomlConfigStore::default(); // let config_store = MemoryConfigStore::new(Config { // vidko: None//Some("E1810".into()) @@ -41,29 +42,9 @@ fn main() -> Result<(), ureq::Error> { // ] // }); - let mut native_options = eframe::NativeOptions::default(); - native_options.decorated = true; - native_options.resizable = true; - native_options.min_window_size = Some(egui::vec2(480.0, 320.0)); - native_options.initial_window_size = Some(egui::vec2(500.0, 320.0)); - native_options.icon_data = Some(eframe::IconData { - rgba: image::load_from_memory(include_bytes!("../assets/icon.png")) - .expect("Failed to load icon") - .into_rgb8() - .into_raw(), - width: 32, - height: 32, - }); - let mut app = MainApp::new(Box::new(config_store), Box::new(timetable_getter)); - - eframe::run_native( - "KTU timetable", - native_options, - Box::new(move |cc| { - app.init(cc); - Box::new(app) - }) - ); - - Ok(()) + platforms::run_windows_app(Environment { + timetable_getter: Box::new(timetable_getter), + config_store: Box::new(config_store) + }) } + diff --git a/src/platforms/mod.rs b/src/platforms/mod.rs new file mode 100644 index 0000000..5983e1c --- /dev/null +++ b/src/platforms/mod.rs @@ -0,0 +1,3 @@ +mod win; + +pub use win::run_windows_app; \ No newline at end of file diff --git a/src/platforms/win.rs b/src/platforms/win.rs new file mode 100644 index 0000000..e307297 --- /dev/null +++ b/src/platforms/win.rs @@ -0,0 +1,132 @@ +use eframe::IconData; +use windows::{ + w, + Win32::{ + Graphics::Gdi::{ + CreateCompatibleDC, DeleteDC, GetDIBits, GetObjectA, SelectObject, BITMAP, BITMAPINFO, + BITMAPINFOHEADER, BI_RGB, DIB_RGB_COLORS, + }, + System::LibraryLoader::GetModuleHandleW, + UI::WindowsAndMessaging::{ + GetIconInfo, LoadImageW, HICON, ICONINFO, IMAGE_ICON, LR_DEFAULTCOLOR, + }, + }, +}; + +use crate::{environment::Environment, app::MainApp}; + +// Yoinked from https://github.com/emilk/egui/issues/920#issuecomment-1364446538 +fn load_app_icon() -> IconData { + let (mut buffer, width, height) = unsafe { + let h_instance = GetModuleHandleW(None).expect("Failed to get HINSTANCE"); + let icon = LoadImageW( + h_instance, + w!("window-icon"), + IMAGE_ICON, + 64, + 64, + LR_DEFAULTCOLOR, + ) + .expect("Failed to load icon"); + + let mut icon_info = ICONINFO::default(); + let res = GetIconInfo(HICON(icon.0), &mut icon_info as *mut _).as_bool(); + if !res { + panic!("Failed to load icon info"); + } + + let mut bitmap = BITMAP::default(); + GetObjectA( + icon_info.hbmColor, + std::mem::size_of::() as i32, + Some(&mut bitmap as *mut _ as *mut _), + ); + + let width = bitmap.bmWidth; + let height = bitmap.bmHeight; + + let b_size = (width * height * 4) as usize; + let mut buffer = Vec::::with_capacity(b_size); + + let h_dc = CreateCompatibleDC(None); + let h_bitmap = SelectObject(h_dc, icon_info.hbmColor); + + let mut bitmap_info = BITMAPINFO::default(); + bitmap_info.bmiHeader.biSize = std::mem::size_of::() as u32; + bitmap_info.bmiHeader.biWidth = width; + bitmap_info.bmiHeader.biHeight = height; + bitmap_info.bmiHeader.biPlanes = 1; + bitmap_info.bmiHeader.biBitCount = 32; + bitmap_info.bmiHeader.biCompression = BI_RGB; + bitmap_info.bmiHeader.biSizeImage = 0; + + let res = GetDIBits( + h_dc, + icon_info.hbmColor, + 0, + height as u32, + Some(buffer.spare_capacity_mut().as_mut_ptr() as *mut _), + &mut bitmap_info as *mut _, + DIB_RGB_COLORS, + ); + if res == 0 { + panic!("Failed to get RGB DI bits"); + } + + SelectObject(h_dc, h_bitmap); + DeleteDC(h_dc); + + assert_eq!( + bitmap_info.bmiHeader.biSizeImage as usize, + b_size, + "returned biSizeImage must equal to b_size" + ); + + // set the new size + buffer.set_len(bitmap_info.bmiHeader.biSizeImage as usize); + + (buffer, width as u32, height as u32) + }; + + // RGBA -> BGRA + for pixel in buffer.as_mut_slice().chunks_mut(4) { + pixel.swap(0, 2); + } + + // Flip the image vertically + let row_size = width as usize * 4; // number of pixels in each row + let row_count = buffer.len() as usize / row_size; // number of rows in the image + for row in 0..row_count / 2 { + // loop through half of the rows + let start = row * row_size; // index of the start of the current row + let end = (row_count - row - 1) * row_size; // index of the end of the current row + for i in 0..row_size { + buffer.swap(start + i, end + i); + } + } + + IconData { + rgba: buffer, + width, + height, + } +} + +pub fn run_windows_app(env: Environment) { + let mut native_options = eframe::NativeOptions::default(); + native_options.decorated = true; + native_options.resizable = true; + native_options.min_window_size = Some(egui::vec2(480.0, 320.0)); + native_options.initial_window_size = Some(egui::vec2(500.0, 320.0)); + native_options.icon_data = Some(load_app_icon()); + let mut app = MainApp::new(env.config_store, env.timetable_getter); + + eframe::run_native( + "KTU timetable", + native_options, + Box::new(move |cc| { + app.init(cc); + Box::new(app) + }) + ); +} \ No newline at end of file